1

I'm trying to create component with Hook, but I'm faced with a strange problem.
I use mapbox-gl in my code. In order to init mapbox-gl, I have to wait until dom component is loaded. (useLayoutEffect or useEffect)
There is no problem in the initial display, but when I push the button(L72), the canvas which is created by mapbox-gl is unmounted with no console error.
I tried to move MyMap component outside the Tile component(L35-L45), then the above problem wasn't happened.

Am I using Hook incorrectly?

My sample full code is following.
[CodeSandbox]

This is an excerpt in Map.tsx:

export const Tile: React.FunctionComponent<PropsType> = ({
  mapComponentLoaded,
  trigger,
  triggered
}) => {
  const classes = useStyles({});

  React.useLayoutEffect(() => { // or useEffect
    // init mapbox-gl
    mapComponentLoaded();
  }, []); // run at once

  // it doesn't works. if you clicked the button, the canvas under div#map would unmount.
  const MyMap = (props: { triggered: boolean }) => (
    <Paper className={classes.content}>
      <div id="map" className={classes.map} />
      <Typography>{props.triggered ? "fired" : "not fired"}</Typography>
    </Paper>
  );

  return (
    <div className={classes.root}>
      <Grid container spacing={1} className={classes.root}>
        <Grid item xs={12}>
          <Button variant="contained" color="primary" onClick={() => trigger()}>
            Add Boundary
          </Button>
        </Grid>
        <Grid item xs={12} className={classes.map}>
          <MyMap triggered={triggered} />
        </Grid>
      </Grid>
    </div>
  );
};

Thanks.

1 Answers1

1

When you define a component inside a functional component, on each render a new reference of it is created and hence instead of re-rendering, react remounts it as it things a new component has been created

When you take the component out of the functional component, the reference of the function doesn't change and hence react renders it correctly

Now another way this would work is if instead of rendering MyMap as a component, you call it as a function.

export const Tile: React.FunctionComponent<PropsType> = ({
  mapComponentLoaded,
  trigger,
  triggered
}) => {
  const classes = useStyles({});

  React.useLayoutEffect(() => { // or useEffect
    // init mapbox-gl
    mapComponentLoaded();
  }, []); // run at once

  // it doesn't works. if you clicked the button, the canvas under div#map would unmount.
  const MyMap = (props: { triggered: boolean }) => (
    <Paper className={classes.content}>
      <div id="map" className={classes.map} />
      <Typography>{props.triggered ? "fired" : "not fired"}</Typography>
    </Paper>
  );

  return (
    <div className={classes.root}>
      <Grid container spacing={1} className={classes.root}>
        <Grid item xs={12}>
          <Button variant="contained" color="primary" onClick={() => trigger()}>
            Add Boundary
          </Button>
        </Grid>
        <Grid item xs={12} className={classes.map}>
          {MyMap({triggered})}
        </Grid>
      </Grid>
    </div>
  );
};

Working demo with second approach

P.S. However taking the definition out of function definition is a much better approach as it gives you the flexibility to add more performance optimizations using React.memo and also to use hooks within this component

Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
  • Thank you for your detailed answer. I think I got the point of it, but I have one of the question. What is the difference between JSX style and function call? I think that the both will be probably compiled to `React.createElement()`. – Michihito Osanai May 14 '20 at 16:00
  • Probably that when you render it as a component, react cares about the reference when you return from a function it compares the element type and props along with the key. Although I am yet to check this thing in the React code – Shubham Khatri May 14 '20 at 16:15