useEffect not working when dependency value changed

12,030

Solution 1

Your both dispatch are called after first render so even before your second render value is 0 so your second useEffect won't be able detect change as there is no change.

Let's see what is happening in your render method

First Render:

a = 0

first useEffect: dispatch({ a : 1 })

second useEffect: dispatch({ a : 0 })

so now in your redux store a is 0.

Second Render

a = 0

first useEffect: doesn't run as there is no dependency

second useEffect: doesn't run as a hans't changed.

Solution 2

PLEASE, stop using

 useSelector(state => state.mainReducer);

it doesn't make any sense

there should be a simple state transformation (subselection)

const a = useSelector(state => state.a)

taken directly from redux docs:

const counter = useSelector(state => state.counter)  

update

you can see effect (from store change) with slightly changed component

function MyComponent(props) {
  const a = useSelector(state => state.a);
  const dispatch = useDispatch(); 

  console.log('render: ', a);

  useEffect(() => {
    console.log('use effect: ', a);
    dispatch({ type: 'A', payload: a });
  }, [a]) 

  useEffect(() => {
    console.log('did mount: ', a);
    dispatch({ type: 'A', payload: 1 })
  }, []); 

  return (<View style={styles.container}>
    <Text style={styles.text}>{a}</Text>
  </View>);
};

It should result in log:

  • render: 0 // initial state
  • use effect: 0 // first effect run
  • // dispatch 0 ... processed in store by reducer but results in the same state ...
    // ... and in our rendering process we still working on an 'old' a readed from state on the beginning of render
  • did mount: 0 // 'old' a
    // dispatch 1 ... changed state in redux store
  • .... rendered text 0
    ...
    ...

  • // useSelector forces rerendering - change detected

  • render: 1 // latest dispatched value, processed by reducers into new state, rereaded by selector
  • use effect: 1 // useEffect works AS EXPECTED as an effect of a change
  • .... rendered text 1

...
...

  • no more rerenderings - latest dispach not changed state

Of course dispatch from other component will force update in this component ... if value will be different.

Share:
12,030
Nam Le
Author by

Nam Le

Updated on June 10, 2022

Comments

  • Nam Le
    Nam Le about 2 years

    I have experienced useEffect function from React hook. When I use it with useSelector from React-redux, it worked so weird.

    My reducer:

    const initialState = { a: 0 };
    
    function mainReducer(state = initialState, action) {
      const { type, payload } = action;
      switch (type) {
        case 'A': return { ...state, a: payload }
        default:
          return state;
      }
    }
    

    My Component:

    function MyComponent(props) {
      const { a } = useSelector(state => state.mainReducer);
      const dispatch = useDispatch(); 
      
      useEffect(() => {
        console.log('did mount: ', a);
        dispatch({ type: 'A', payload: 1 })
      }, []); 
    
      useEffect(() => {
        console.log('use effect: ', a);
        dispatch({ type: 'A', payload: a });
      }, [a]) 
    
      return (<View style={styles.container}>
        <Text style={styles.text}>{a}</Text>
      </View>);
    };
    

    Result

    Log:

    did mount ran: 0
    useEffect ran: 0
    

    The last result is variable 'a' = 0

    ????

    As I understand, After the first render, both effect ran sequentially in their order in the code.
    (Step 1) So the first effect run fist -> log 'did mount ran: 0'. Then it dispatch value 1 to store
    (Step 2) The second effect run after -> log 'did mount ran: 0'. Then it dispatch value 0 to store

    But what I don't understand is the second effect must track the change from variable 'a', so there will be:
    In the following render time:
    (Step 3) the second useEffect should be run when the value 'a' change from 0 to 1 (from Step 1).
    And then:
    (Step 4) it should have the third re-render when the value change again from 1 to 0 (from Step 2)

    So the log should be:

    did mount ran: 0
    useEffect ran: 0
    useEffect ran: 1
    useEffect ran: 0
    

    Could you please explain to me what I'm missing? Thank you