9

I have this component, that needs to fetch data, set it to state and then pass it to the children. Some of the data also needs to be set in context. My problem is that using useEffect, once called the API, it will re-render for each setvalue() function I need to execute. I have tried passing to useEffect an empty [] array, still getting the same number of re-renders, due to the fact that the state is changing. At the moment the array is containg the set...functions to prevent eslint to throw warnings.

Is there a better way to avoid this many re-renders ?

const Home = (props) => {

  console.log("TCL: Home -> props", props);
  const classes = useStyles();
  const [value, setValue] = React.useState(0);


  //CONTEXT
  const { listSavedJobs, setListSavedJobs, setIsFullView} = useContext(HomeContext);
  const {
    setUserName,
    setUserLastName,
    setUserEmail,
    setAvatarProfile,
  } = useContext(UserContext);

  // STATE
  const [searchSettings, setSearchSettings] = useState([]);
  const [oppData, setOppData] = useState([]);
  const handleChange = (event, newValue) => {
    setValue(newValue);
  };


  const handleChangeIndex = index => {
    setValue(index);
  };


  //API CALLS
  useEffect(() => {
    const triggerAPI = async () => {

      setIsFullView(false);

      const oppResponse = await API.getOpportunity();
      if(oppResponse){
        setOppData(oppResponse.response);
      }
      const profileResponse = await API.getUserProfile();
      if(profileResponse){
        setUserName(profileResponse.response.first_name);
        setUserLastName(profileResponse.response.last_name);
        setUserEmail(profileResponse.response.emailId);
      }
      const profileExtData = await API.getUserProfileExt();
      if(profileExtData){
        setAvatarProfile(profileExtData.response.avatar);
        setListSavedJobs(profileExtData.response.savedJobs);
        setSearchSettings(profileExtData.response.preferredIndustry);
      }
    };
    triggerAPI();

  }, [ 
    setOppData,
    setUserName,
    setUserLastName,
    setUserEmail,
    setAvatarProfile,
    setListSavedJobs,
    setIsFullView,
  ]);

...```




Andrea
  • 351
  • 1
  • 3
  • 9
  • 1
    https://stackoverflow.com/a/53048903/9787887 *if the state changes are triggered asynchronously (e.g. wrapped in a promise), they will not be batched;*. You likely used `set` too much in an `async` function, each `set` will cause you a rerender, that's *too much*. I don't know if you said *too much* mean *too much* or *infinite*? – Loi Nguyen Huynh Jan 06 '20 at 02:57
  • If `API.getOpportunity()`, `API.getUserProfile()` and `API.getUserProfileExt()` are **3 independent api calls**, it means the result will come asynchronously, then **it's fine** to rerender 3 times (each rerender for each result have got) for 3 api calls *(but in yours it's likely 8)*? How much is *too much rerender* you mean? – Loi Nguyen Huynh Jan 06 '20 at 03:29
  • There's some problem: If `setUserName`, `setUserLastName`, `setUserEmail` are always being set together, you should use **only one state object** called `user` **instead of 3 state variables `userName` ,`userLastName`, `userEmail`**, it'll save you 2 rerenders in this case. Another problem is the way you used `await` would cause 3 api calls to run synchronously instead of in parallel, it'll not cause more rerender but'll cost you a little bit more performance, to fix you need `Promise.all`. – Loi Nguyen Huynh Jan 06 '20 at 03:33
  • 1
    @LoiNguyenHuynh thank you, those are very precious suggestions that I'm gonna implement. – Andrea Jan 06 '20 at 03:52

3 Answers3

9

Pass just an empty array to second parameter of useEffect.

Note

React guarantees that setState function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the useEffect or useCallback dependency list. Source

Edit: Try this to avoid rerenders. Use with caution

4

Only Run on Mount and Unmount

You can pass the special value of empty array [] as a way of saying “only run on mount and unmount”. So if we changed our component above to call useEffect like this:

useEffect(() => {
  console.log('mounted');
  return () => console.log('unmounting...');
}, [])

Then it will print “mounted” after the initial render, remain silent throughout its life, and print “unmounting…” on its way out.

Prevent useEffect From Running Every Render

If you want your effects to run less often, you can provide a second argument – an array of values. Think of them as the dependencies for that effect. If one of the dependencies has changed since the last time, the effect will run again. (It will also still run after the initial render)

const [value, setValue] = useState('initial');

useEffect(() => {
  // This effect uses the `value` variable,
  // so it "depends on" `value`.
  console.log(value);
}, [value])

For more clarification useEffect

akhtarvahid
  • 9,445
  • 2
  • 26
  • 29
0

If you are using React 18, this won't be a problem anymore as the new auto batching feature: https://reactjs.org/blog/2022/03/29/react-v18.html#new-feature-automatic-batching

If you are using an old version, can refer to this solution: https://statics.teams.cdn.office.net/evergreen-assets/safelinks/1/atp-safelinks.html

iqoOopi
  • 160
  • 8