React-Material UI-D3 Promise.all does not show chart without double click on menu

430 Views Asked by At

I am a newbie in React Router and State, i don't know why that my simple D3 Chart not show if i use condition/steps below

  1. Load data on Apps using Promise.all and store in State (the reason i load data in here is because i try to avoid to load data on each chart page)

  2. Call Dashboard page from App with Navigation

  3. First time it will Load the Dashboard page with my BarChart

  4. When i click on next menu to display the BarChart, it does not show

  5. If i click again on the same menu, it will show again my BarChart

Is it because of Async function (Promise.all) ? How to resolve it?

alphabet.csv sample data

letter,frequency A,0.08167

B,0.01492

C,0.02782

D,0.04253

E,0.12702

import React, { useState, useEffect } from 'react';
import * as d3 from 'd3';
import { Route, BrowserRouter as Router } from 'react-router-dom';
import Dashboard from './components/Dashboard';
import Dashboard1 from './components/Dashboard1';

export default function App() {
  const [result, setResult] = useState([])

  const mydata = () => {
    const data = Promise.all([
      d3.csv("alphabet.csv", d3.autoType),
    ]).then((result) => {
      setResult([result]);
    })

    return data
  }

  useEffect(() => mydata(), []);

  return (
    <Router>
      <Route path="/" exact render={(props) => <Dashboard {...props} data={result} />} />
      <Route path="/Dashboard" render={(props) => <Dashboard {...props} data={result} />} />
      <Route path="/Dashboard1" render={(props) => <Dashboard {...props} data={result} />} />
    </Router>
  )
}
import React from 'react';
import clsx from 'clsx';
import { makeStyles } from '@material-ui/core/styles';
import Container from '@material-ui/core/Container';
import Grid from '@material-ui/core/Grid';
import Paper from '@material-ui/core/Paper';
import Toolbar from '@material-ui/core/Toolbar';
import Navigation from './Navigation';
import BarChart from './BarChart';

const useStyles = makeStyles((theme) => ({
    root: {
        display: 'flex',
    },
    title: {
        flexGrow: 1,
    },
    content: {
        flexGrow: 1,
        height: '100vh',
        overflow: 'auto',
    },
    container: {
        paddingLeft: theme.spacing(2),
        paddingTop: theme.spacing(2),
        paddingBottom: theme.spacing(2),
        paddingRight: theme.spacing(2),
    },
    paper: {
        padding: theme.spacing(2),
        display: 'flex',
        overflow: 'auto',
        flexDirection: 'column',
    },
    fixedHeight: {
        height: 350,
    },
}));

export default function Dashboard(props) {
    const classes = useStyles();
    const fixedHeightPaper = clsx(classes.paper, classes.fixedHeight);

    return (
        <div className={classes.root}>
            <Navigation />
            <main className={classes.content}>
                <Toolbar />
                <div className={classes.appBarSpacer} />
                <Container maxWidth="lg" className={classes.container}>
                    <Grid container spacing={3}>
                        <Grid item xs={12} md={8} lg={9}>
                            <Paper className={fixedHeightPaper}>
                                Dashboard<BarChart data={props.data} />
                            </Paper>
                        </Grid>
                    </Grid>
                </Container>
            </main>
        </div>
    )
}
import React from 'react';
import clsx from 'clsx';
import { Link } from 'react-router-dom';
import { makeStyles } from '@material-ui/core/styles';
import Drawer from '@material-ui/core/Drawer';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import List from '@material-ui/core/List';
import Typography from '@material-ui/core/Typography';
import Divider from '@material-ui/core/Divider';
import IconButton from '@material-ui/core/IconButton';
import ChevronLeftIcon from '@material-ui/icons/ChevronLeft';
import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import DashboardIcon from '@material-ui/icons/Dashboard';
import BarChartIcon from '@material-ui/icons/BarChart';

const drawerWidth = 55;

const useStyles = makeStyles((theme) => ({
  root: {
    display: 'flex',
  },
  toolbar: {
    paddingRight: 24, // keep right padding when drawer closed
  },
  toolbarIcon: {
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'flex-end',
    padding: '0 8px',
    ...theme.mixins.toolbar,
  },
  appBar: {
    zIndex: theme.zIndex.drawer + 1,
    transition: theme.transitions.create(['width', 'margin'], {
      easing: theme.transitions.easing.sharp,
      duration: theme.transitions.duration.leavingScreen,
    }),
  },
  menuButton: {
    marginRight: 36,
  },
  menuButtonHidden: {
    display: 'none',
  },
  title: {
    flexGrow: 1,
  },
  drawerPaper: {
    position: 'relative',
    whiteSpace: 'nowrap',
    width: drawerWidth,
    transition: theme.transitions.create('width', {
      easing: theme.transitions.easing.sharp,
      duration: theme.transitions.duration.enteringScreen,
    }),
  },
  drawerPaperClose: {
    overflowX: 'hidden',
    transition: theme.transitions.create('width', {
      easing: theme.transitions.easing.sharp,
      duration: theme.transitions.duration.leavingScreen,
    }),
    width: theme.spacing(7),
    [theme.breakpoints.up('sm')]: {
      width: theme.spacing(7),
    },
  },
  appBarSpacer: theme.mixins.toolbar,
}));

export default function Navigation() {
  const classes = useStyles();

  return (
    <div className={classes.root}>
      <AppBar position="absolute" className={clsx(classes.appBar)}>
        <Toolbar className={classes.toolbar}>
          <Typography component="h1" variant="h6" color="inherit" noWrap className={classes.title}>
            Testing
          </Typography>
        </Toolbar>
      </AppBar>
      <Drawer
        variant="permanent"
        classes={{
          paper: clsx(classes.drawerPaper, classes.drawerPaperClose),
        }}
      >
        <div className={classes.toolbarIcon}>
          <IconButton>
            <ChevronLeftIcon />
          </IconButton>
        </div>
        <Divider />
        <List>
          <ListItem button>
            <ListItemIcon>
              <Link to='/Dashboard'><DashboardIcon /></Link>
            </ListItemIcon>
            <ListItemText primary="Dashboard" />
          </ListItem>
          <ListItem button>
            <ListItemIcon>
              <Link to='/Dashboard1'><BarChartIcon /></Link>
            </ListItemIcon>
            <ListItemText primary="Dashboard1" />
          </ListItem>          
        </List>
      </Drawer>
    </div >
  )
}
import React, { useRef } from 'react';
import * as d3 from 'd3';

export default function BarChart(props) {

    const ref = useRef();
    const pdata = props.data;
    const margin = ({ top: 30, right: 30, bottom: 60, left: 100 });
    const width = window.innerWidth - margin.right - margin.left;
    const height = window.innerHeight - margin.top - margin.bottom + 100;
    const color = "steelblue";

    if (pdata.length > 0) {
        const data = pdata[0];

        const x = d3.scaleBand()
            .domain(d3.range(data[0].length))
            .range([margin.left, width - margin.right])
            .padding(0.1);

            const xAxis = g => g
            .attr("transform", `translate(0,${height - margin.bottom})`)
            .call(d3.axisBottom(x).tickFormat(i => data[0][i].letter).tickSizeOuter(0))
            .attr('font-size', '18px')
            .call(g => g.append("text")
                .attr("x", width / 2)
                .attr("y", margin.bottom)
                .attr("fill", "currentColor")
                .text("Letter"));

        const y = d3.scaleLinear()
            .domain([0, d3.max(data[0], d => d.frequency)]).nice()
            .range([height - margin.bottom, margin.top]);

        const yAxis = g => g
            .attr("transform", `translate(${margin.left},0)`)
            .call(d3.axisLeft(y).ticks(20, data.format))
            .attr('font-size', '18px')
            .call(g => g.append("text")
                .attr("transform", "rotate(-90)")
                .attr("x", -height / 2)
                .attr("y", -margin.left)
                .attr("fill", "currentColor")
                .text("Frequency"));

        const svg = d3.select(ref.current)
            .attr("viewBox", [0, 0, width, height]);

        svg.append("g")
            .call(xAxis);

        svg.append("g")
            .call(yAxis);

        svg.append("g")
            .attr("fill", color)
            .selectAll("rect")
            .data(data[0])
            .join("rect")
            .attr("x", (d, i) => x(i))
            .attr("y", d => y(d.frequency))
            .attr("height", d => y(0) - y(d.frequency))
            .attr("width", x.bandwidth());
    }

    return <svg ref={ref} />
}
1

There are 1 best solutions below

0
On

Not sure if this was answered, but this line

      setResult([result]);

could be the culprit. Is the result already an array? If yes, then just do

      setResult(result);

because you might not be mapping over the correct data.