Setting a useEffect hook's dependency within`useEffect` without triggering useEffect

Viewed 1807

Edit: It just occurred to me that there's likely no need to reset the variable within the useEffect hook. In fact, stateTheCausesUseEffectToBeInvoked's actual value is likely inconsequential. It is, for all intents and purposes, simply a way of triggering useEffect.

Let's say I have a functional React component whose state I initialize using the useEffect hook. I make a call to a service. I retrieve some data. I commit that data to state. Cool. Now, let's say I, at a later time, interact with the same service, except that this time, rather than simply retrieving a list of results, I CREATE or DELETE a single result item, thus modifying the entire result set. I now wish to retrieve an updated copy of the list of data I retrieved earlier. At this point, I'd like to again trigger the useEffect hook I used to initialize my component's state, because I want to re-render the list, this time accounting for the newly-created result item.

​

const myComponent = () => {
  const [items, setItems] = ([])
  useEffect(() => {
    const getSomeData = async () => {
      try {
        const response = await callToSomeService()
        setItems(response.data)
        setStateThatCausesUseEffectToBeInvoked(false)
      } catch (error) {
        // Handle error
        console.log(error)
      }
    }
  }, [stateThatCausesUseEffectToBeInvoked])

  const createNewItem = async () => {
    try {
      const response = await callToSomeService()
      setStateThatCausesUseEffectToBeInvoked(true)
    } catch (error) {
        // Handle error
        console.log(error)
    }
  }
}

​

I hope the above makes sense.

​

The thing is that I want to reset stateThatCausesUseEffectToBeInvoked to false WITHOUT forcing a re-render. (Currently, I end up calling the service twice--once for win stateThatCausesUseEffectToBeInvoked is set to true then again when it is reset to false within the context of the useEffect hook. This variable exists solely for the purpose of triggering useEffect and sparing me the need to elsewhere make the selfsame service request that I make within useEffect.

​

Does anyone know how this might be accomplished?

2 Answers

There are a few things you could do to achieve a behavior similar to what you described:

Change stateThatCausesUseEffectToBeInvoked to a number

If you change stateThatCausesUseEffectToBeInvoked to a number, you don't need to reset it after use and can just keep incrementing it to trigger the effect.

useEffect(() => {
  // ...
}, [stateThatCausesUseEffectToBeInvoked]);

setStateThatCausesUseEffectToBeInvoked(n => n+1); // Trigger useEffect

Add a condition to the useEffect

Instead of actually changing any logic outside, you could just adjust your useEffect-body to only run if stateThatCausesUseEffectToBeInvoked is true. This will still trigger the useEffect but jump right out and not cause any unnecessary requests or rerenders.

useEffect(() => {
  if (stateThatCausesUseEffectToBeInvoked === true) {
    // ...
  }
}, [stateThatCausesUseEffectToBeInvoked]);

Assuming that 1) by const [items, setItems] = ([]) you mean const [items, setItems] = useState([]), and 2) that you simply want to reflect the latest data after a call to the API:

When the state of the component is updated, it re-renders on it's own. No need for stateThatCausesUseEffectToBeInvoked:

const myComponent = () => {

const [ items, setItems ] = useState( [] )

const getSomeData = async () => {
    try {
        const response = await callToSomeService1()
        // When response (data) is received, state is updated (setItems)
        // When state is updated, the component re-renders on its own
        setItems( response.data )
    } catch ( error ) {
        console.log( error )
    }
}

useEffect( () => {
    // Call the GET function once ititially, to populate the state (items)
    getSomeData()
    // use [] to run this only on component mount (initially)
}, [] )

const createNewItem = async () => {
    try {
        const response = await callToSomeService2()
        // Call the POST function to create the item
        // When response is received (e.g. is OK), call the GET function
        // to ask for all items again.
        getSomeData()
    } catch ( error ) {
        console.log( error )
    }
}                                           }

However, instead of getting all items after every action, you could change your array locally, so if the create (POST) response.data is the newly created item, you can add it to items (create a new array that includes it).

Related