Jonas Grønbek
Jonas Grønbek

Reputation: 2019

useEffect hook called on initial render without dependency changing

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)

Upvotes: 2

Views: 2357

Answers (2)

Debotos Das
Debotos Das

Reputation: 569

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
  }
}, [])

Upvotes: 0

SMHutch
SMHutch

Reputation: 126

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' })
}

Upvotes: 3

Related Questions