Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Are useEffect clean-ups called in the wrong order ? #17080

Closed
strblr opened this issue Oct 13, 2019 · 8 comments
Closed

Are useEffect clean-ups called in the wrong order ? #17080

strblr opened this issue Oct 13, 2019 · 8 comments
Labels
Resolution: Stale Automatically closed due to inactivity Type: Needs Investigation

Comments

@strblr
Copy link

strblr commented Oct 13, 2019

What is the current behavior?

I'm trying to write a small Title component to render nested document titles. However the kinda unexpected order in which effect clean-up functions are called makes it impossible to do this :

import { Children, useEffect } from 'react'

export default ({ children }) => {
  const subtitle = Children.toArray(children).join('')
  useEffect(() => {
    const root = document.title
    document.title = `${subtitle} - ${root}`
    console.log('Wrote', document.title)
    return () => {
      document.title = root
      console.log('Restored', document.title)
    }
  }, [subtitle])
  return null
}

Here is a loosy sketch of my component structure (handled with @reach/router) :

App                     // contains <Title>App</Title>
> Dashboard             // contains <Title>Dashboard</Title>
> Profile               // contains <Title>Profile</Title>
> Project               // contains <Title>Project</Title>
  > Calendar            // contains <Title>Calendar</Title>

Each one of these components calls my Title component. Everything works fine when going from profile to dashboard or from dashboard to project. But when I jump from calendar to dashboard, the document title gets messed up because the clean-up in Project is called before the clean-up in Calendar even though Calendar is a child of Project, which is kinda unexpected and makes tree side-effect logic using (just) useEffect simply impossible.

The order in which the effect functions are called is a direct reflection of the order in which the components are nested. Shouldn't the clean-up functions be called following the exact reverse order ? Isn't that one of the goals of a clean-up ?

When I navigate to the calendar and then back to the dashboard, I get the following logs :

Wrote App
Wrote Project - App
Wrote Calendar - Project - App
Restored App                          // This should happen after...
Restored Project - App                // ...this.
Wrote Dashboard - Project - App       // This is messed up.

codesandbox here

What is the expected behavior?

Here is what I expected and what I would get if clean-ups in children were to be called before clean-ups in parents :

Wrote App
Wrote Project - App
Wrote Calendar - Project - App
Restored Project - App
Restored App
Wrote Dashboard - App

I'm using React 16.10.2.

This ugly workaround works by "forcing" the order of what the clean-ups do (by keeping manually track of the effect functions' order and then just going the other way) :

const stack = []

export default ({ children }) => {
  const subtitle = Children.toArray(children).join('')
  useEffect(() => {
    stack.push(document.title)
    document.title = `${subtitle} - ${last(stack)}`
    return () => {
      document.title = stack.pop()
    }
  }, [subtitle])
  return null
}
@strblr strblr changed the title useEffect clean-ups called in the wrong order ? Are useEffect clean-ups called in the wrong order ? Oct 14, 2019
@bvaughn
Copy link
Contributor

bvaughn commented Oct 14, 2019

Can you provide a Code Sandbox with actual, runnable code that shows the unexpected behavior you're describing?

Both effects and their cleanup functions are expected to run from the inside out- which is not the order your copy-pasted log shows, so it seems like some context is missing.

For example, I would expect to see:

// Mount App:Project:Calendar

Wrote Calendar
Wrote Project
Wrote App

// Update to App:Dashboard

Restored Calendar
Restored Project
Wrote Dashboard
Restored App
Wrote App

@strblr
Copy link
Author

strblr commented Oct 15, 2019

@bvaughn https://codesandbox.io/s/vigilant-feynman-0jxwb?fontsize=14

I also added the link in the original post.

@aleclarson

This comment has been minimized.

@the-main-thing
Copy link

the-main-thing commented Nov 10, 2019

I also have similar issue with order of useEffect calls and I thought that non guarateed order of useEffect calls is intentional.

@gaearon
Copy link
Collaborator

gaearon commented Nov 10, 2019

Is there a difference from the componentWillUnmount order in classes?

@aleclarson
Copy link
Contributor

Ok, I took a second look at my expectations, and I think everything is working as expected.

App
	Dashboard
		Title
	Project
		Title
		Calendar
			Title

With such a layout, it makes sense that, when Project unmounts, its Title cleanup is called before the Calendar > Title cleanup, since it renders above the Calendar component.

The OP should make a useTitle hook so the useEffect call can happen in the Project component instead of that Title component.

I'd say this is okay to close.

@stale
Copy link

stale bot commented Feb 8, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contribution.

@stale stale bot added the Resolution: Stale Automatically closed due to inactivity label Feb 8, 2020
@stale
Copy link

stale bot commented Feb 15, 2020

Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Resolution: Stale Automatically closed due to inactivity Type: Needs Investigation
Projects
None yet
Development

No branches or pull requests

5 participants