5

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.

3
  • 2
    you need to learn about useMemo and useCallback react hook, just google it. Commented Dec 23, 2021 at 6:27
  • 1
    Take a look at react-query, it solves problems like these elegantly. Commented Dec 23, 2021 at 6:37
  • State Management Commented Dec 23, 2021 at 6:54

3 Answers 3

4

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Hi @Drew, Thanks for your answer. Your first solution works all good. Just wondering how do I achieve this using React Context. If I make a context, then this means I need to call all the api for all XItem at once. As I said I am calling an api to get the image and the server is sending image in binary file and not url, so it may takes a good amount of time. Currently when I load, all XItem is loaded, and in regular interval images shown. But after using context, It will wait for all the image to load once. This is my understanding. correct me if i am wrong.
0

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>

Comments

0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.