Suppose a parent renders <ChildComponent callback={(value)=>setState(value)} />
. Suppose the parent re-renders. The pointer values of the callback
prop will be different (ie. not triple-equals-equal) but the callback
prop's value is conceptually the same for all intents and purposes. This is a "new value" despite being the "same value". You run into the same problem when the parent creates an object <ChildComponent data={{foo: 'bar', bar: 'noise'}} />
(pointers differ, despite it being the "same" object from a value-type perspective).
You also run into the reverse problem due to mutability. Suppose I say:
var value = {foo: 'bar', bar: 'noise'};
ReactDOM.render(<ChildComponent data={value} />, ...);
value.bar = 'drinks';
ReactDOM.render(<ChildComponent data={value} />, ...);
As you can see, the props have clearly "changed" (ie. componentWillReceiveProps
should get called, so the ChildComponent can respond accordingly), but the values are triple-equals-equal.
In general, there is no way to solve this, except to always call componentWillReceiveProps
any time the values might have changed.
If javascript always used value types (like they do for integers; two renders of integers or strings are always "equal" if they are equal from a value-type definition) for all data types, than this problem would not exist. Also, mutation creates an issue, because the meaning of a value can change (when it is mutated) but is still triple-equals equal to the original prop. But because javascript uses reference equality and supports mutation, you can't rely on triple-equals to tell you if two things are conceptually the same.
@jimfb thank you for helping move this forward!
Makes sense. I've been contributing to the issue to promote an environment to help surface the problems / use cases, but also to see if there is a different way of thinking of the solution space in case people are anchoring too much on the current API. As an exercise, I've been thinking that formalizing and agreeing principles could create new insights on the problems / use cases.
From my own code, I documented the type of control flow pattern that the current API creates and I'm hoping to streamline it to a flow with less boilerplate. Repeated here:
With this sort of use case in mind, I have tried to summarize some potential principles that may help resolve the issue.
I explained the reasoning for separating props and state here:
This principle should be looked at independently from the
propsChanged
example which you have successfully argued against.As a clarification, I tried using componentDidUpdate, but found that the flow in practice naturally separated since one portion of logic dealt with external inputs (props) and the other changes in internal state (state). Think of it like cascading state changes...changes to the props may force the state to reset or be translated into basis state (inputs-phase) which in turn may cause derived state to update (derived-state phase).
I definitely agree that looking at use cases around having a single method for state and props could be a valuable exercise. Also, a single method definitely has the potential to be better since it is more flexible. Finally, the argument (taken from previous issue comments) around componentDidUpdate having the potential to be called multiple time in response to it's own changes would still happen if the state is set in the state-centric method (that said, looking at my current codebase, I have only exclusively used the props to change state since multiple state attributes can be updated simultaneously at the source of the change, eg. maybe the API discussion should mainly focus on props event if this is a potential principle).
I added this principle given that it is important to keep in mind in exploring solution space options. I also had taken it for granted (eg. implicitly included) but I made it explicit because @akinnee-gl explicitly raised it when clarifying the reasons against focussing on considering the
updated
stage to simplify the lifecycle hooks.Do you believe that clarifying principles in addition to raising problems / use cases has good potential to move this forward?
If so, is there an API design that better achieves these principles? or do the principles need to be updated? or is there a line of argument that explains that we should only be looking at sustaining vs also considering disruptive (to draw on an innovation framework) API changes?