React functional component rerenders when props don't change (using useSelector)

Viewed 45

I'm trying to understand why a change in my component B triggers a re-render of component A and his children.

component A has a useSelector for store.A.
component B has a useSelector for store.B.
store A and store B are under the same AppStore.

component A has several children components that are not connected to the store (component A retrieves all the data needed from the store and passes it as props).

When component B calls an action that modifies store B, useSelector is called in both components.

Here comes the catch:
If I write a useEffect that watches all the data retrieved via useSelector in component A, it never triggers when interacting with component B.

But it re-renders all the child components all the same.

I've tried to React.memo a child component of component A, and no change, it always re-renders component A and all his children, even when its props doesn't change (or so is telling me the useEffect watching all the component props).

Note 1: all components are functional.
Note 2: if component B only changes its local state and doesn't call any redux actions, component A is never re-rendered.

I also double checked that all the functions passed as props are using useCallback.

I really don't understand what is going on.
I thought that if a component props don't change, they shouldn't re-render.

2 Answers

Could you provide piece of your code (preferably the impacted component and slice) so we can have a better understanding? You mentioned useEffect, what do you have in your dependency array? Are there any extra reducers that you would be calling with your useSelector?

For starters I would try using an empty dependency array with your useEffect and debug from there.

useEffect(() => { // Your code here }, [//leave this empty for now and check console after refresh])

As asked in the comment of the question, here is my answer:

It was the main App component that was using a function with the result of a useState, forcing the re-render of both components whenever the state changed.

Even with my other error fixed, if you are doing:

type ReduxProps = {
  propertyA: string | null;
  propertyA: string | null;
};

const { propertyA, propertyB } = useSelector<AppState, ReduxProps>(state => ({
  propertyA: state.A.property,
  propertyB: state.B.property
}));

it will trigger a re-render, because it wraps the properties in a new object every time.

Prefer doing:

  const propertyA = useSelector<AppState, string | null>(state => state.A.property);
  const propertyB = useSelector<AppState, string | null>(state => state.B.property);

(if I'm wrong please correct me because this was what we discovered).

Related