2

Okay, I am experiencing some behaviour I don't really understand. I have this useState hook

const [permanent, setPermanent] = useState(false) 

and this useEffect hook

 useEffect(() => { if (permanent) { dispatch({ value: 'Permanent booth', key: 'period' }) } else { dispatch({ value: '0', key: 'period' }) } }, [permanent]) 

It triggers a rerender on initial render, and I do not call setPermanent upon rendering my component, I have checked this both by commenting every single setPermanent call out in my application. And I have also tried replacing it with a function that logs to the console.

 //const [permanent, setPermanent] = useState(false) const permanent = false const setPermanent = () => { console.log('I am called') //does not get called on initial render } 

I know it triggers a rerender because when I comment one of the second dispatch call in it out, it does not trigger the rerender.

 useEffect(() => { if (permanent) { dispatch({ value: 'Permanent booth', key: 'period' }) } else { //dispatch({ value: '0', key: 'period' }) } }, [permanent]) 

Is there a reason for this, because I cannot seem to find documentation explaining this behaviour?

EDIT --------------

 const shopOptions = (() => { const options = [ { label: 'Choose a shop', value: '0' }, ] Object.keys(stores).forEach(store => { options[options.length] = { label: store, value: options.length } }) return options })() const genderOptions = [ { label: 'Choose a gender', value: '0' }, { label: 'Female', value: '1' }, { label: 'Male', value: '2' } ] const periodOptions = [ { label: 'Choose a period', value: '0' }, { label: '1 week', value: '1' }, { label: '2 weeks', value: '2' }, { label: '3 weeks', value: '3' }, { label: '4 weeks', value: '4' } ] const initialState = { shop: shopOptions[0], gender: genderOptions[0], period: periodOptions[0], } function reducer(prevState, { value, key }) { const updatedElement = { ...prevState[key] } updatedElement.value = value return { ...prevState, [key]: updatedElement } } //form const [state, dispatch] = useReducer(reducer, initialState) 
1
  • 1
    Is the dispatch doing something that would cause the component to unmount and remount? (e.g. parent going into a loading state). You could return a cleanup function that just logs something to the console and set a breakpoint in it to see what's going on at that moment. (This is not as straightforward as it sounds due to the layers of abstractions and decoupling between your component and React's rendering process.) Commented Sep 8, 2020 at 22:45

2 Answers 2

3

useEffect hooks run both after the first render and after every update of variables passed to the dependency array (in your case [permanent]).

Because you have a boolean value that triggers the effect, it's hard to know whether it's the first render or a re-render within the effect. In your case I would consider not using a useEffect here, and instead dispatching what you need while updating the state. For example:

const [permanent, setPermanent] = useState(false) const makePermanent = () => { setPermanent(true) dispatch({ value: 'Permanent booth', key: 'period' }) } const makeTemporary = () => { setPermanent(false) dispatch({ value: '0', key: 'period' }) } 
Sign up to request clarification or add additional context in comments.

3 Comments

I had no clue ALL useEffect hooks ran on initial render, is there not a way to prevent that? I just realized my whole application has useEffects running all over the place, this explains a lot actually. Is there no true substitute for componentDidUpdate?
@JonasGrønbek This article provides a way to mimic componentDidUpdate, but I would strongly recommend trying not to do that too often. This (very comprehensive) article me against trying to replicate the class life-cycle methods. Good luck!
@JonasGrønbek the latter is a whole book @@
0

This is my solution. Just a boolean property and ordering hook will do the job.

const _isMounted = React.useRef(false) const [filter, setFilter] = React.useState('') React.useEffect(() => { if (_isMounted.current) { getData(filter) // Now it will not get called very first time } }, [filter]) /* Order is important. [] or so called componentDidMount() will be the last */ React.useEffect(() => { _isMounted.current = true return () => { _isMounted.current = false } }, []) 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.