11

So I have the following situation when using a useEffect that calls a functions that depends on state.

Example:

// INSIDE APP COMPONENT

const [someState, setSomeState] = React.useState(0);
const [someTrigger, setSomeTrigger] = React.useState(false);

function someFunction() {
  return someState + 1;  
}

React.useEffect(() => {

  const newState = someFunction();  // 'someFunction' IS BEING CALLED HERE
  setSomeState(newState);

},[someTrigger])

QUESTIONS:

In this case, should I declare someFunction inside the useEffect() or is it safe to keep it outside (but inside component's body)?

I could add it to the dependency array, but it will hurt the redability of my code, since I want to focus on the trigger.

Since the useEffect() will run after a fresh render, is it safe to assume that it will have fresh copies of the functions that I'm calling inside of it?

Is there a basic rule of when you should declare functions inside the useEffect hook or when is it mandatory that you add it to the dependency array?

EDIT: Note that it's necessary that the useEffect has fresh copies of those functions, since those functions need to access some fresh up-to-date state variable.

NOTE:

This code triggers the following eslint warning on CodeSandbox. Although it works just fine.

React Hook React.useEffect has a missing dependency: 'someFunction'. Either include it or remove the dependency array. (react-hooks/exhaustive-deps)eslint

REAL CASE SCENARIO:

This is a simplified example. In my real case, this is a product search page with filters components. So when I click on a filter to activate it (let's say, price <= 50), I'm triggering a useEffect() that is "listening" for the activePriceFilters state variable. That effect then calls a function (someFunction in the example) that will calculate the filteredList and will set the new productList state with the new filteredList.

SNIPPET

function App() {
  
  const [someState, setSomeState] = React.useState(0);
  const [someTrigger, setSomeTrigger] = React.useState(false);
  
  function someFunction() {
    return someState + 1;  
  }
  
  React.useEffect(() => {
  
    const newState = someFunction();
    setSomeState(newState);
  
  },[someTrigger])
  
  return(
    <React.Fragment>
      <div>I am App</div>
      <div>My state: {someState}</div>
      <button onClick={()=>setSomeTrigger((prevState) => !prevState)}>Click</button>
    </React.Fragment>
  );
}

ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
cbdeveloper
  • 27,898
  • 37
  • 155
  • 336
  • 1
    You can read [this FAQ answer](https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies). Also, [this is an awesome article](https://overreacted.io/a-complete-guide-to-useeffect/) about `useEffect` – devserkan Jul 02 '19 at 10:33

2 Answers2

10

A decision to define a function within or outside of useEffect depends on where all the functions is called.

If your function is called only from within the useEffect then it makes sense to define it within the useEffect.

However is the same function is being called from within useEffect as well as other event handlers or other effects, you need to define it outside of useEffect

PS. in your case you are just updating the state for which you needn't define a separate function

function App() {
  
  const [someState, setSomeState] = React.useState(0);
  const [someTrigger, setSomeTrigger] = React.useState(false);
  

  React.useEffect(() => {
    setSomeState(oldState => oldState + 1);
  
  },[someTrigger])
  
  return(
    <React.Fragment>
      <div>I am App</div>
      <div>My state: {someState}</div>
      <button onClick={()=>setSomeTrigger((prevState) => !prevState)}>Click</button>
    </React.Fragment>
  );
}

ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>

As far as you scenario is concerned, you would write it like

useEffect(() => {
    const calcVal = (oldState) => {
         // calculate previous state based on oldState
    }

    setSomeState(calcVal);
}, [activePriceFilters])
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
  • The example is an over simplified version of my real case scenario. – cbdeveloper Jul 02 '19 at 12:00
  • Can you help with my real case scenario? It's a product search page with filters components. So when I click on a filter to activate it (let's say, price <= 50), I'm triggering a useEffect() that is "listening" for the activePriceFilters state variable. That effect then calls a function (someFunction in the example) that will calculate the filteredList and will set the new productList state with the new filteredList. Is there anything wrong with this approach? How would you handle this? Thanks! – cbdeveloper Jul 02 '19 at 12:00
  • 1
    As I said, you need to define a separate function outside of useEffect if the function will be used outside of useEffect, else define it within useEffect and pass it on to state update – Shubham Khatri Jul 02 '19 at 12:02
  • Thanks! But from my overall code pattern, do you think it's a good approach? Using a `useEffect` to "listen" for change in the state filters variable and call a function that sets a new state for the `filteredList` ? Sorry to bother you. Your answer was really helpful. – cbdeveloper Jul 02 '19 at 12:05
  • Doesn't declaring functions inside the useEffect mean they are re-created every time the useEffect is triggered? Or is the Javascript runtime smart enough to cache them or something? – mpelzsherman Dec 20 '22 at 00:04
1

Setting another state should not be an effect at all.

 const [someState, setSomeState] = React.useState(0);

 function incrementState() {
    setSomeState(someState => someState + 1);
 }
Kirill Taletski
  • 388
  • 2
  • 6
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • Can you help with my real case scenario? It's a product search page with filters components. So when I click on a filter to activate it (let's say, `price <= 50`), I'm triggering a `useEffect()` that is "listening" for the `activePriceFilters` state variable. That effect then calls a function (`someFunction` in the example) that will calculate the `filteredList` and will set the new `productList` state with the new `filteredList`. Is there anything wrong with this approach? How would you handle this? Thanks! – cbdeveloper Jul 02 '19 at 10:31
  • Does it calculate them synchronously? Or does it retrieve them from a server? – Jonas Wilms Jul 02 '19 at 10:34
  • It is synchronous. I should also add that I have more than 1 component filter, and more then one state variable with different active filters. If any of them change, I should calculate a new `filteredList` state that is passed down as props to my `ResultList` component. – cbdeveloper Jul 02 '19 at 10:35
  • 1
    Then I'd just do `const [filters, setFilters] = useState([]); const result = data.filter(filters /*...*/);` – Jonas Wilms Jul 02 '19 at 10:36
  • And what if it were asynchronous? Would you do it inside a `useEffect` then? Like: `useEffect(()=>{ // Activate Loading State // Await for async call // Deactivate Loading state // },[activeFilter] );` Something like that? – cbdeveloper Jul 02 '19 at 11:39
  • 1
    Yes, in that case. – Jonas Wilms Jul 02 '19 at 13:12