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

Pass prevContext param to componentDidUpdate #8631

Merged
merged 4 commits into from
Apr 21, 2017

Conversation

bvaughn
Copy link
Contributor

@bvaughn bvaughn commented Dec 22, 2016

See this comment for my latest thoughts about this issue.

This PR resolves the issue of the missing prevContext param to componentDidUpdate calls.

Note that this approach is support option 1 below.

Options Considered

It seems like we have a high-level decision to make regarding how and if we support the prevContext parameter going forward. Options include:

  1. Drop support for it: If we do this we should update the ReactContextValidator-test test to assert that the prevContext parameter is not passed and update to Stack to mirror this behavior.
  2. Support it (option A): Store it as an expando property on instance (eg __reactInternalPrevContext). Adding to the instance avoids bloating Fibers for all of the types that don't need this property and keeps our Fiber-consuming functions monomorphic.
  3. Support it (option B): Use stateNode to store a wrapper object around instance and previous context for the case of ClassComponents. This prevents all non-ClassComponent fibers from growing while also keeping fiber-consuming methods monomorphic. Since this only impacts ClassComponent it should hopefully introduce minimal forking.

Regarding continued support: At this time it does not seem like this parameter is being used by React components within Facebook but a quick Github search seems to suggest the parameter is being used externally (eg 1, 2, 3, etc.).

@sebmarkbage
Copy link
Collaborator

Sorry. We explicitly did not choose this strategy for the reason that it doesn't make sense to add to everything when only a few places change and it can be recreated while traversing anyway.

@bvaughn
Copy link
Contributor Author

bvaughn commented Dec 22, 2016

I've updated this PR description with some more context based on our face-to-face chat about this.

@gaearon
Copy link
Collaborator

gaearon commented Dec 22, 2016

a quick Github search seems to suggest the parameter is being used externally (eg 1, 2, 3, etc.).

Most of these appear to be copies of React, type definitions, and a few one-off projects with no community activity that use context directly despite us explicitly saying in the docs that it’s not supported. I wouldn’t worry too much about them because updates are already broken for context.

Edit: this is getting some traffic so I want to point out that if you relied on this feature, the “fix” to get it back is three lines of code. The benefit of dropping it is that it was broken in the first place (so if you relied on it, your program was likely buggy) and that it introduced an unnecessary memory cost for all components when only a tiny minority used it.

@bvaughn bvaughn changed the title Stored memoized context on fiber Pass prevContext to componentDidUpdate Dec 22, 2016
@bvaughn bvaughn changed the title Pass prevContext to componentDidUpdate Pass prevContext param to componentDidUpdate Dec 22, 2016
@gaearon
Copy link
Collaborator

gaearon commented Dec 24, 2016

"But there still exist some problems" — is this ready to get merged or do you want to add failing cases?

@bvaughn
Copy link
Contributor Author

bvaughn commented Dec 24, 2016

Hey Dan. 😄 I temporarily set this down because of the holidays. However I also kind of wanted to wait until others on the team weighed in regarding the 3 options above. Do you have any thoughts or preferences?

Sebastian and I chatted about this for a good hour or two on Thursday and it was not clear at the end of that talk which option was most appropriate. I thought this PR was worth considering because it fixes a backwards compatibility problem. I didn't realize the was such a big unopened question around it.

As for the issues hinted at- there are some problems with context|props being stale if an update is bailed out on midway (after render but before commit) and then later updated again by a higher priority change. I will write a failing test or two for that on Tuesday even if we don't fix it right away. (Started writing one already but haven't finished yet.) 😁

Happy holidays!

@gaearon
Copy link
Collaborator

gaearon commented Dec 24, 2016

Yea, no rush, just curious.
I’m inclined towards either this fix or not fixing it at all.

@bvaughn
Copy link
Contributor Author

bvaughn commented Dec 24, 2016

No worries. I should have left a clearer status on the PR before heading out.

I was starting to feel bike-sheddy about this on Thursday and I was hoping a few days away from it might lend some clarity.

@bvaughn
Copy link
Contributor Author

bvaughn commented Jan 3, 2017

Note to self: PR #8655 has implications on this that probably pushes us toward either option B or not passing prevContext to componentDidUpdate at all.

I haven't wrapped my head fully around this yet but I think that if it's possible for instance.props and instance.state inputs to be invalidated by swapping child and progressedChild then the same applies to any property stored on the instance.

@bvaughn
Copy link
Contributor Author

bvaughn commented Jan 4, 2017

I've been thinking more about the lifecycle methods and specific concerns about context in fiber.

First some assumptions I have:

  • componentWillReceiveProps only exists to enable components to update state in reaction to a change in inputs (props and, unofficially, context).
  • componentWillUpdate only exists to enable components to compare prev/next inputs to decide if anything internally needs to be set/reset in response to a specific input change (eg in react-virtualized I reset cached size and position data if certain inputs change).
  • componentDidUpdate exists to enable components to do side-effect things like DOM manipulations (that rely on a render having already been done) or network requests (in response to a change in inputs).

Assuming the above assumptions are correct, I have some concerns about fiber and context.

I think it's not controversial to say that side-effect things (eg DOM manipulation, network requests) should only be done in componentDidUpdate. Other lifecycle methods (eg componentWillUpdate) can't be relied on because the work they do may be bailed out on.

What about other instance attributes? Using react-virtualized as an example again- I store some derived data on the instance. This data is outside of React's management/awareness and so it won't be reset when a Fiber is bailed out on. I think that PR #8655 mitigates this by explicitly resetting instance.props and instance.state before calling lifecycle hooks (or at least it will by the time it is merged). As long as I'm using the correct lifecycle methods, my instance attributes will be eventually consistent with inputs.

But what about instance attributes that are derived from context?

  • If we remove the prevContext parameter from componentDidUpdate then how can a component safely react to a change in context?
  • Even with that parameter present- if context isn't managed in the same way as props and state (PR [Fiber] Move memoization to begin phase #8655) then it could be inconsistent between the instance and the fiber.

I believe @sebmarkbage has some ideas/plans for context going forward that are pretty different from the current, unstable implementation. Hopefully these ideas do away with the way we're managing next/prev context and this whole comment becomes invalid- but I wanted to write it down while it was in my mind in case. @sebmarkbage, maybe we can chat more about this topic next week when you're back?

Update: Chatted in meatspace with @acdlite about this. He pointed out another existing stack limitation with fiber WRT shouldComponentUpdate. (Seems like context is already flaky but fiber will make it more so.) The only safe way to use context is to pass singleton/static props (eg the way react-redux Provider passes down store) that components subscribe to for changes.

I think I'm now leaning toward option 1 above (removing the context param entirely from the lifecycle hooks). I'm not sure about the timing for this though. Would we want to do this for the next release or hold off? If we want to maintain a semblance of backward compatibility in this area, we could move forward with this PR (given that context remains an unofficial/unsupported feature) and deprecate for both stack and fiber in a subsequent release.

@bvaughn
Copy link
Contributor Author

bvaughn commented Jan 6, 2017

I want to expand on my concern about instance attributes a little more while I'm thinking about it.

I think it's unsafe to do anything with side effects (even setting or updating "private" instance attributes) in componentWillReceiveProps; this method should only be used for updating state in response to a change in props. Anything else can be problematic.

(There may be operations that should only be done once- when an input changes from X to Y. Because we might call componentWillReceiveProps on the same instance twice with the same X-to-Y transition- due to how we reset inputs a la PR #8655- this type of operation is unsafe.)

This is also a potential problem if we store anything references to non-primitive types in state. Because our instance may run componentWillReceiveProps more than once for the same previous inputs- it's possible we take an action too many times (eg increment a counter more than once).

@sebmarkbage I'd like to chat with you about this next week if you're available. 😄

@bvaughn
Copy link
Contributor Author

bvaughn commented Jan 13, 2017

Ping @sebmarkbage. Now that you're back, can we chat about this?

Brian Vaughn added 2 commits February 28, 2017 16:33
This makes use of an expando property (__reactInternalPrevContext) on the stateNode (instance). A property on the instance was used instead of a Fiber attribute because:
1) Conditional fields on fibers would break monomorphism.
2) Adding to all fibers would bloat types that don't use context.
This affects Stack and Fiber. I've updated impacted tests as well.
This may not be the direction we choose to go either. More discussion is still needed about the future of context...
bvaughn pushed a commit that referenced this pull request Sep 27, 2017
* Update changelog for unreleased 16.0 changes (#10730)

* First shot at updating changelog for 16.0

**what is the change?:**
Added an 'unreleased' section to the changelog with info from #10294

**why make this change?:**
To get things set for the 16.0 release.

**test plan:**
Visual inspection

**issue:**
#8854

* Fix typos and formatting errors in changelog

* Add requestAnimationFrame and remove "New helpful warnings"

**what is the change?:**
In response to helpful code review -
- Add mention of dependency on `requestAnimationFrame` and need to
  polyfill that as well as `Map` and `Set`
- Remove "New helpful warnings" section; it was incomplete, and there
  are so many new and updated warnings that it might not be reasonable
  to cover them in the changelog.

**why make this change?:**
Accuracy

**test plan:**
Visual inspection

**issue:**
issue #8854

* Improve wording

* Improve wording and fix missing links

* Add backticks to file names & code; wording tweak

* Break "Major Changes" into "New Feature" and "Breaking Changes"

* Add server side render changes to 16.0 changelog

* Change gist link from mine to gaearons

* Add note about returning fragments

* fix misc nits

* Misc. formatting/wording fixes to changelog

**what is the change?:**
Thanks to the kind code review comments of @gaearon and @nhunzaker we
have
- removed the non-deterministic bold styling on some bullet points
- improved wording of the bullet points for portals, attribute whitelist
  changes, and server rendering changes
- Add note about error boundaries including a breaking change to error
  handling behavior.
- punctuation and capitalization fixes

**why make this change?:**
Clarity and correctness

**test plan:**
Visual inspection

**issue:**
#8854

* fix broken link

* Fixes #9667: Updated createTextInstance to create the text node on correct document (#10723)

* Record sizes

*  Add a changelog for elements having the same key (#10811)

*  Add a changelog for elements having the same key

* Reword

* Markdown fixs on "DOM Attributes in React 16" post (#10816)

* Include tag name into the table snapshot (#10818)

* Update DOM warning wording and link (#10819)

* Update DOM warning wording and link

* Consistently use "Invalid" for known misspellings

* Update license headers BSD+Patents -> MIT

Did find and replace in TextMate.

```
find: (?:( \*)( ))?Copyright (?:\(c\) )?(\d{4})\b.+Facebook[\s\S]+(?:this source tree|the same directory)\.$
replace: $1$2Copyright (c) $3-present, Facebook, Inc.\n$1\n$1$2This source code is licensed under the MIT license found in the\n$1$2LICENSE file in the root directory of this source tree.
```

* Change license and remove references to PATENTS

Only remaining references:

```
docs/_posts/2014-10-28-react-v0.12.md
51:You can read the full text of the [LICENSE](https://github.com/facebook/react/blob/master/LICENSE) and [`PATENTS`](https://github.com/facebook/react/blob/master/PATENTS) files on GitHub.

docs/_posts/2015-04-17-react-native-v0.4.md
20:* **Patent Grant**: Many of you had concerns and questions around the PATENTS file. We pushed [a new version of the grant](https://code.facebook.com/posts/1639473982937255/updating-our-open-source-patent-grant/).

src/__mocks__/vendor/third_party/WebComponents.js
8: * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
```

* Version bumps to use MIT license

* Add ReactTestRenderer documentations (#10692)

* Add ReactTestRenderer documentations

* Add TestRenderer documentations

* TestRenderer is not experiment

* Add a link for jsdom

* Use ES Modules syntax

* Twaek

* Add a Link component

* Use Jest assertions

* Move a documentation for createNodeMock to Idea section

* Renamed

* Tweak

* Rename

* More explicit

* Add a usecase for createNodeMock

* Add changelog for 15.6.2

* Add 15.6.2 blog post to master

* Add Nate to authors on master

* Bump object-assign patch range to match main package.json

* Flow should ignore node_modules/create-react-class

* Update error codes

* Update CHANGELOG for React 16

* v16.0.0

* Doc updates for React 16 + blog post (#10824)

* Doc updates for React 16 + blog post

* Add link to Sophie's post

* Fix React links on the website (#10837)

* Fix React links on the website

* Fix code editor

* Fix code editor, attempt 2

* Doc change for prevContext removal in CDU (#10836)

* Doc change for prevContext removal in CDU

Ref: #8631

* Minor rewording

* Fix note formatting

* React.createPortal is not a function (#10843)

* Update Portals Documentation (#10840)

* Update Portals Documentation

Correct some grammar to be more explicit and clear. Update example CodePen to better match code found in documentation. Update code example styles to match other code examples (ie. 'State and Lifecycle', 'Handling Events').

* Clean up comment to be accurate to example

There was a small comment overlooked when reviewing the documentation. This fixes it to be accurate to the example as well as grammatically correct.

* Update portals.md

* More fixes

* Update name of property initializer proposal (#10812)

The proposal for property initializers is called [Public Class Fields](https://tc39.github.io/proposal-class-public-fields/) now (part of the combined [Class Fields](https://github.com/tc39/proposal-class-fields) proposal).

* Fix portal link (#10845)

* Update docs for React 16 (#10846)

* Minor doc edit

* Rename urls
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants