280

I have some side effects to apply in my react component and want to know how to organize them:

  • as a single useEffect
  • or several useEffects

Which is better in terms of performance and architecture?

AncientSwordRage
  • 7,086
  • 19
  • 90
  • 173
Vadim
  • 3,474
  • 3
  • 14
  • 21
  • It probably matters depending on your use case, but I'm going to edit your question to make it clear this is covering general advice. – AncientSwordRage Nov 09 '21 at 13:34

3 Answers3

404

The pattern that you need to follow depends on your use case.

First: You might have a situation where you need to add event listener during the initial mount and clean them up at unmount and another case where a particular listener needs to be cleaned up and re-added on a prop change.

In such a case, using two different useEffect is better to keep the relevant logic together as well as having performance benefits

useEffect(() => {
   // adding event listeners on mount here
   return () => {
       // cleaning up the listeners here
   }
}, []);

useEffect(() => {
   // adding listeners everytime props.x changes
   return () => {
       // removing the listener when props.x changes
   }
}, [props.x])

Second: You need to trigger an API call or some other side-effect when any of the state or props change from a defined set. In such a case a single useEffect with the relevant dependencies to monitor would be better

useEffect(() => {
    // side effect here on change of any of props.x or stateY
}, [props.x, stateY])

Third: You need separate side-effect for different sets of changes. In such a case, separate out relevant side-effects into different useEffects

useEffect(() => {
   // some side-effect on change of props.x
}, [props.x])

useEffect(() => {
   // another side-effect on change of stateX or stateY 
}, [stateX, stateY])
AncientSwordRage
  • 7,086
  • 19
  • 90
  • 173
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
  • 1
    what about a middle ground between **Second** and **Third** example above?: you have logic that runs when a subset of state/props change, but each has separate logic that needs to run in addition to some common code that needs to run? You wouldn't use `[]` (because it's still only a _subset_ of state/props you're awaiting changes for) but you'd also like to reuse code. Do you use separate `useEffects` and put the shared code in a function they each separately call? – ecoe Aug 25 '19 at 18:22
  • 8
    As the answer below suggests, React team suggests separating hooks by concern, so you would split it into multiple `useEffect` calls. – hakazvaka Sep 20 '19 at 10:17
  • 2
    Are they always triggered in the order of their definition? i.e. effect1 is always called first, then effect2? – computrius Jun 04 '20 at 20:58
  • 4
    @computrius [Yes](https://reactjs.org/docs/hooks-effect.html#tip-use-multiple-effects-to-separate-concerns), `React will apply every effect used by the component, in the order they were specified.` – August Janse Nov 17 '20 at 06:49
  • 1
    What if I have multiple "componentDidMount" effects (empty array []) but they do very different things? should I put them in a single useEffect or multiple? – Dror Bar Mar 16 '21 at 09:08
91

You should use multiple effects to separate concerns as suggested by reactjs.org.

AncientSwordRage
  • 7,086
  • 19
  • 90
  • 173
Guy Engel
  • 2,436
  • 1
  • 17
  • 13
  • 3
    I know, but it would improve your answer if you explained why separation of concern, and whether that's an architectural or performance concept. – AncientSwordRage Nov 09 '21 at 13:41
23

It's perfectly fine to have have multiple useEffect.

Here's how one of my setups looks like:

/*
 * Backend tags list have changed add the changes if needed
 */
useEffect(() => {
    setTagsList(setTagsAdded);
}, [setTagsAdded]);

/*
 * Backend files have changed add the changes if needed
 */
useEffect(() => {
    for (let i = 0; i < changedFilesMeta.length; i += 1) {
        // Is the list item value changed
        if (changedFilesMeta[i].id === currentEditableFile.id) {
            unstable_batchedUpdates(() => {
                setTags(changedFilesMeta[i].tags ? changedFilesMeta[i].tags : []);
            });
        }
    }
}, [changedFilesMeta]);

/*
 * Reset when user select new files using the filepicker
 */
useEffect(() => {
    if (setNewFiles.length > 0) {
        unstable_batchedUpdates(() => {
            setCurrentFile(null);
            setDescription('');
            setTitle('');
            setTags([]);
        });
    }
}, [setNewFiles]);

/*
 * User selecet to edit a file, change to that file
 */
useEffect(() => {
    // When user select a file to edit it
    if (currentEditableFile && currentEditableFile !== theCurrentFile) {
        setCurrentFile(currentEditableFile);
        unstable_batchedUpdates(() => {
            setDescription(currentEditableFile.description);
            setTitle(currentEditableFile.title);
            setTags(currentEditableFile.tags);
        });
    }
}, [currentEditableFile]);
Erik
  • 5,039
  • 10
  • 63
  • 119
  • could you add some more of your code, to make it clear what bit of state `setTags` is updating? Is `setTagsAdded` a function or object? Also how have you made sure 'performance and architecture' are better this way round vs. one big useEffect? – AncientSwordRage Nov 09 '21 at 13:33
  • 1
    I don't think including this code, especially out of context, is helpful or relevant to the question. – Bricky Nov 09 '21 at 18:00
  • 1
    @Bricky regardless, stripping out all the code AND adding in a quote changes the answer too much to justify it – AncientSwordRage Nov 09 '21 at 18:46
  • The quote is the relevant section *from the link*. Simply including a link is a low-effort answer. – Bricky Nov 10 '21 at 21:37