Debug functional component re-rendering issue

  • Check how many times the functional component is rendering
  • When to use shallowEqual with useSelector
  • Avoid using multiple useSelectors
  • Destructuring vs direct accessing redux states in useSelector

Working with functional components not new to React dev but when we work with hooks it needs special attention to understand before we going to implementing any hook.

Let me introduce you to a common state handling with the below FunComponent snippet.

Ex: we have the redux states as below,

const initialState = {
ld_recoveryList: false,
er_recoveryList: null,
recoverylist: {
list: [],
listLength: 0,
isPaginatedEnd: false
}
}

and my functional component as below,

const FunComponent = ({ listType = ''}) => {
const { ld_recoveryList, er_recoveryList, recoverylist } = useSelector((state) =>
state.recoveryData
);

useEffect(()=>{
APICALL();
},[])

const ConditionalRender = () => {
if (ld_recoveryList) {
return <VirticalListSkeleton />;
}
if (er_recoveryList) {
return <ListError />;
}
if (get(recoverylist, 'listLength', 0) > 0) {
return (
<SectionList listType={listType} />
);
}
return null;
};
return (
<View rt style={{ flex: 1, paddingHorizontal: 16 }}>
<ConditionalRender />
</View>
);
}
  • Check how many times the functional component is rendering

You need to check like

useEffect(()=>{
console.log(“Rendering“)
});

Yes, you are right, this is similar to componentDidUpdate which will be called for every re-render, so we get to know how many times our component is rendering.

  • When to use shallowEqual with useSelector

If you observe the above useSelector, here we have two primitive and one non-primitive data type params, whenever we have to work with non-primitive type values use shallowEqual.

(Note: shallowEqual — is a function that checks whether two objects are equal based on the first level of key <-> value pairs ).

const { ld_recoveryList, er_recoveryList, recoverylist } = useSelector((state) => 
state.recoveryData,
shallowEqual
);

I suggest the above format, but you might have a point here, why primitives also need to wrap with single useSelector like

const { ld_recoveryList, er_recoveryList } = useSelector((state) => state.recoveryData);const { recoverylist } = useSelector((state) => state.recoveryData, shallowEqual);

to conclude this point, bear me, keep going!.

  • Avoid using multiple useSelectors

If we have a component with multiple useSelector, find out how can we trim multiple useSelectors into one, because every useSelector does its job to check and reload the state when that state changes.

Let’s say, when a parent component has multiple child components, here we have defined child dependent useSelector states inside parent and trying to pass down from parent to child, this should be avoided and defined directly into child component.

const ParentComponent = () => {
const { useData, paymentsData, recoverylist } = useSelector((state) =>
state,
shallowEqual
);
return (
<View>
<A useData={useData} />
<B paymentsData={paymentsData} recoverylist={recoverylist} />
<C/>
</View>
)
}
const A = ({ useData = {} }) => <View>{useData}</View>;const B = ({ paymentsData = {}, recoverylist = [] }) => <View{paymentsData}{recoverylist}</View>const C = () => <View />

Here ParentComponent un-necessarily importing states which are not using inside ParentComponent, so it’s a good decision to move the states to child components directly rather defining in parent component like,

const ParentComponent = () => {
return (
<View>
<A/>
<B/>
<C/>
</View>
)
}
const A = () => {
const { useData } = useSelector((state) => state, shallowEqual);
return <VView>{useData}</VView>;
}
const B = () => {
const { paymentsData, recoverylist } = useSelector((state) => state, shallowEqual);
return <View>{paymentsData}{recoverylist}</View>
}
const C = () => <View />

This way, we ensure the parent component safe from unnecessary rendering and avoid passing params from the Parent to Child component when the parent has nothing to do with those params.

  • Destructuring vs direct accessing redux states in useSelector

Here I had shallowEqual but it didn’t help to prevent the re-rendering component, what I had noticed here is that accessing the params directly from the required state object is a good step to prevent re-rendering.

const { ld_recoveryList, er_recoveryList, recoverylist } =   useSelector((state) => 
state.recoveryData,
shallowEqual
);

To

const [ ld_recoveryList, er_recoveryList, recoverylist ] =                         useSelector((state) => [
state.recoveryData.ld_recoveryList,
state.recoveryData.er_recoveryList,
state.recoveryData.recoverylist
],
shallowEqual
);

We changed the object format into array format by accessing the direct params from the store states rather than importing the complete state.

Thanks for reading, Cheers 🍻.