-
Notifications
You must be signed in to change notification settings - Fork 7.7k
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
Issues with componentWillUpdate deprecations (state machines implementation) #781
Comments
Can you provide the minimal code demonstrating what you’re doing please? |
Just in case it's unclear, mutating state- either |
To me it sounds like you want to have a single entry point for state changes. There, use the setState functional form to get access to previous state. performAction(action) {
this.setState(prevState => {
let nextState = { action };
if (prevState.action !== action) {
nextState.someField = null;
}
return nextState;
});
} It’s hard to say more without seeing your code. But if you need to adjust state based on state then you should do it when you set that state, not later. |
@gaearon I'm not currently at home and will only be back late in the night most probably, but I'll try to show some code example tomorrow. Your example above makes sense. I guess I should be able to replace @bvaughn I'm aware of that, but most of the render call rely on some state variables in order to render. Modifying the states in-place during render is way more simple than extracting some variables to something like "const someValue = isNewAction ? 'something' : this.state.somethingElse". It's much simpler to write something like: if (hasActionChangedToList) {
this.state.clients = [];
this.state.somethingElse = ...
} |
Right, right. I see how it could be more convenient to write that way, but I wanted to point out that there were subtle problems with mutation like that in the past (even without regard to async) which is one of the reasons why the will lifecycles have been deprecated and tagged as "unsafe". 😄 |
You can also use something like https://github.com/mweststrate/immer if you want to write code in mutable style inside a pure updater function. |
That said you can already write code in a “mutating style” for top-level state keys in the updater function, like I show in #781 (comment). You’d need to be careful to not mutate the previous state with deeper updates though. |
Thanks for pointing out to immer. Seems like an interesting library even though I don't intend to use it right now. I actually liked the suggested approach in comment 781 and will try that once I'm back to home. Thanks. With that approach I wouldn't have to modify the previous state if I only care about mutating top-level state keys. For modifying inner states then I can always consider immer if I feel that modifying in-place could lead to unexpected behavior in my code. If I can easily save those 2k then I prefer to modify in-place, but if I suspect that could introduce subtle bugs, then I'd replace those in-line state changes with something like immer. Thanks for all your help and considerations :) |
@rosenfeld Was there any reason you used |
cWRP wouldn’t fire on state-only changes. |
Was there any reason why setState in |
Probably that it was unnecessary to support setState in it? If you need to change state in response to changing state, it means that either one of those states is unnecessary (and can be eliminated), or that those states should have been computed together. |
Just to be clear, I never actually implemented it using componentWillUpdate. I've thought about using that but since I never remember the actual names of the hook methods I always look at the documentation and this time I noticed the changes in the API, so I used the method I described in this issue, by storing the previous action and comparing against the current one in render(). But I thought that maybe it would be clearer if I used componentWillUpdate instead, without realizing setState is not supposed to be used in that hook either. As you can see, I'm still kinda new to React application development and I'm really giving it a try in the development of a new app I started to build since a few months ago. But after thinking better about this I think Dan is right in pointing that the state reset should really happen once the action state is changed, which seems a bit obvious after this issue's discussion. It wasn't obvious to me while developing the application because I don't actually run That should be enough, but if there's a catch I can't easily fix without those lifecycle hooks I'll get back to you. |
Hi @gaearon, I was finally able to take a closer look in the actual implementation and unfortunately the flow is not as simple as we have previously discussed. There's a top-level Router component which is responsible for rendering the current app, which is stored in Router's top-level "currentApp" state. Suppose the current app is ClientsManager and that the current action is "list". When the user calls When the current app (ClientsManager) gets the path property updated it would then perform a state change when it detects the action has changed, after extracting the action from location.search. At this point the state should be reset accordingly to the new action (or, maybe, in some rarer cases, depending on the previous action as well I guess). With Maybe you have some suggestion on what could be done to improve the situation in this scenario I described. The idea behind the router is that once the user clicks on the edit button for a given client it would change the URL so that if the user then refreshes the browser they would still be editing that client rather than rendering the user's list action. However, when the application is already loaded we'd like those actions to feel instanteous. For example, once the user goes to the edit action and then hit the browser's Back button the router would take care of invoking the previous URL action ('list') but since the clients state was already set previously there's no need to fetch the list of clients again. So, it feels like a traditional state-less app with regards to navigation but can take advantage of a traditiononal SPA's speed improvements. |
Sounds right.
If props changed in such a way as to require re-creating the If it's possible that the props could change but the |
Currently, I'm not using something like React.PureComponent, but I understand I might if performance becomes an issue at some point. The PureComponent's shouldComponentUpdate implementation is a shallow prop AND state comparison, accordingly to the documentation, which makes sense to me. However, if I add a new I realize I'm currently only suspecting about a theorical issue, since I neither use PureComponent at this point as I don't have any performance issues either right now and it's very likely the time spent to shallow compare that new state is neglitible, but I thought it might be interesting to share this use case anyway in case it might help understanding how componentWillReceiveProps is currently used. |
Are you saying that
Gotcha. Rendering is typically pretty fast. The React docs suggest to use |
No, I was saying that any call to
Yes, that makes perfect sense and I don't actually think performance would ever be an issue with my current app with regards to client-side run-time performance. As I said it's more of some "it doesn't feel right" issue rather than an actual performance one :) |
Gotcha. 😄 So with regard to this issue, are there any remaining concerns or questions (beyond the minor sCU perf thing we're talking about)? |
After thinking more carefully about this, there are other changes that would be required in order to convert my implementation to use a static method instead. Remember I call However, it's hard to predict whether this will always be the case. I have that feeling that one might actually need the |
Gotcha. Removing access to the class instance ( If you have concerns about that aspect of the API- (specific ones are going to be better than fuzzy ones)- the RFC repo is probably the right place to discuss them. 😄 |
Right, I'll try to make the changes to accomodate to the new API. If I notice something preventing me to achieve my goals with the new API (a concrete use case) I'll discuss that case in that repo. Thanks. |
Thanks for the discussion 😄 |
Hi, I'd like to discuss an use case for which I think the new lifecycle methods won't help demonstrating how it seems to me like a bad move to deprecate componentWillUpdate (or prefix it with UNSAFE_).
I have some sort of client-side router that uses the history API to change the query search to implement some sort of state-machine for a given component. Think of some UsersManagement component that could render actions such as list, edit and display actions. It has an "action" state and render() will dispatch to the proper method depending on what is the current action.
When there's an action change, some initialization is required and I'd like to reset some other relevant states. Currently, the only way I can think of implementing that is to store the previous "action" state in some instance property and compare the current action with the previous one in render(). When they are different I'd perform the changes to the other state attributes in-place since it's already too late to call setState() from render().
Even componentWillUpdate() is not ideal for that since I can't call setState from it either, but at least I wouldn't have to store the previous "action" state myself. With componentWillUpdate() I'd be able to compare the previous action state with the next one and modify the relevant state changes in-place before render is called, which seems like a bit cleaner approach.
I feel that something is missing in React and that it should be possible to react to state changes and perform further state changes before rendering without much hack. State machines should be possible even without something like Redux. Am I missing something obvious while trying to implement a state machine?
The text was updated successfully, but these errors were encountered: