简体   繁体   中英

How to stop making api call on re-rendering in React?

In my homepage, I have code something like this

{selectedTab===0 && <XList allItemList={some_list/>}
{selectedTab===1 && <YList allItemList={some_list2/>}

Now, In XList, I have something like this:

{props.allItemList.map(item => <XItem item={item}/>)}

Now, Inside XItem, I am calling an api to get the image of XItem.

Now my problem is When In homepage, I switched the tab from 0 to 1 or 1 to 0, It is calling all the API's in XItem again. Whenever I switched tab it calls api again. I don't want that. I already used useEffect inside XItem with [] array as second parameter.

I have my backend code where I made an api to get the image of XItem. The API is returning the image directly and not the url, so I can't call all api once.

I need some solution so that I can minimize api call. Thanks for help.

The basic issue is that with the way you select the selected tab you are mounting and unmounting the components. Remounting the components necessarily re-runs any mounting useEffect callbacks that make network requests and stores any results in local component state. Unmounting the component necessarily disposes the component state.

{selectedTab === 0 && <XList allItemList={some_list} />}
{selectedTab === 1 && <YList allItemList={some_list2} />}

One solution could be to pass an isActive prop to both XList and YList and set the value based on the selectedTab value. Each component conditionally renders its content based on the isActive prop. The idea being to keep the components mounted so they only fetch the data once when they initially mounted.

<XList allItemList={some_list} isActive={selectedTab === 0} />
<YList allItemList={some_list2} isActive={selectedTab === 1} />

Example XList

const XList = ({ allItemList, isActive }) => {
  useEffect(() => {
    // expensive network call
  }, []);

  return isActive 
    ? props.allItemList.map(item => <XItem item={item}/>)
    : null;
};

Alternative means include lifting the API requests and state to the parent component and passing down as props. Or using a React context to do the same and provide out the state via the context. Or implement/add to a global state management like Redux/Thunks.

Just to quickly expand on Drew Reese 's answer, consider having your tabs be children of a Tabs-component. That way, your components are (slightly) more decoupled.

const MyTabulator = ({ children }) => {
  const kids = React.useMemo(() => React.Children.toArray(children), [children]);
  const [state, setState] = React.useState(0);

  return (
    <div>
      {kids.map((k, i) => (
        <button key={k.props.name} onClick={() => setState(i)}>
          {k.props.name}
        </button>
      ))}
      {kids.map((k, i) =>
        React.cloneElement(k, {
          key: k.props.name,
          isActive: i === state
        })
      )}
    </div>
  );
};

and a wrapper to handle the isActive prop

const Tab = ({ isActive, children }) => <div hidden={!isActive}>{children}</div>

Then render them like this


<MyTabulator>
   <Tab name="x list"><XList allItemList={some_list} /></Tab>
   <Tab name="y list"><YList allItemList={some_list2} /></Tab>
</MyTabulator>

My take on this issue. You can wrap XItem component with React.memo :

const XItem = (props) => {
   ...
}

const areEqual = (prevProps, nextProps) => {
  /*
  Add your logic here to check if you want to rerender XItem 
  return true if you don't want rerender
  return false if you want a rerender
  */
}

export default React.memo(XItem, areEqual);

The logic is the same if you choose to use useMemo .

If you want to use useCallback (my default choice) would require only to wrap the call you make to the api.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM