Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

useEffect not working when dependency value changed

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

like image 647
Nam Le Avatar asked Feb 03 '20 08:02

Nam Le


People also ask

What happens if you don't pass dependency in useEffect?

Empty dependency array So what happens when the dependency array is empty? It simply means that the hook will only trigger once when the component is first rendered. So for example, for useEffect it means the callback will run once at the beginning of the lifecycle of the component and never again.

How do you run useEffect when state changes?

You can add the state variables you want to track to the hook's dependencies array and the logic in your useEffect hook will run every time the state variables change. Copied! import {useEffect, useState} from 'react'; const App = () => { const [count, setCount] = useState(0); useEffect(() => { console.

How do I fix React hook useEffect has missing dependencies?

The warning "React Hook useEffect has a missing dependency" occurs when the useEffect hook makes use of a variable or function that we haven't included in its dependencies array. To solve the error, disable the rule for a line or move the variable inside the useEffect hook.

Does useEffect run when props change?

Side Effect Runs After Props Value Change Just like the state, we can also use props as a dependency to run the side effect. In this case, the side effect will run every time there is a change to the props passed as a dependency. useEffect(() => { // Side Effect }, [props]);


2 Answers

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 hasn't changed.

like image 99
Amit Chauhan Avatar answered Oct 20 '22 19:10

Amit Chauhan


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.

like image 29
xadm Avatar answered Oct 20 '22 19:10

xadm