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

#5473 warn and ignore setState and forceUpdate calls if rendered on server #5879

Closed
wants to merge 4 commits into from

Conversation

mdolbin
Copy link

@mdolbin mdolbin commented Jan 19, 2016

As discussed in the #5473:

In dev mode, we could mark a component as having been rendered using SSR. If setState or forceUpdate is ever called on such a component, print an intelligent warning.

@jimfb
Copy link
Contributor

jimfb commented Jan 19, 2016

I think we can't ignore setState() (I know that was @spicyj's idea, but I think he was mistaken about this point). We should restore the 'allows setState in componentWillMount without using DOM' test, IMO. cc @spicyj for confirmation

Also, we should think through the cases where setState() is and isn't legal. I think setState within componentWillMount is legal but setState within a setTimeout is not legal. Maybe the easiest thing to do is have instance._isServerSideRendered point to an object which is created at the beginning of SSR ({warn: false}) and mutated at the end of render (set warn to true) to enable warnings on those components.

- isServerSideRendered is either 'false' or an object with a 'isAfterComponentWillMount' flag;
- setState() method calls executed after componentWillMount() (e.g. setTimeout() callback) are ignored if component was rendered on server. All setState() calls during componentWillMount() are allowed;
- all forceUpdate() method calls are ignored if component was rendered on server;
- restored 'allows setState in componentWillMount without using DOM' test.
@facebook-github-bot
Copy link

@mdolbin updated the pull request.

@mdolbin
Copy link
Author

mdolbin commented Jan 20, 2016

@jimfb As far as I know the only use of state in case of server-side rendering is a one-time initial rendering itself, isn't it?
I think we shouldn't allow to change state during render which happens (immediately?) after componentWillMount so this bound seems to be in place.
The only thing I can think about is overriding some 'internal magic state properties' which is not the case, I hope.

@jimfb
Copy link
Contributor

jimfb commented Jan 20, 2016

Yeah, I think I agree with you. cc @spicyj

'if component was rendered on server.';
jest.runAllTimers();
expect(setTimeout.mock.calls.length).toBe(1);
expect(console.error.calls.length).toBe(2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably add a boolean so we only emit the warning once (otherwise the user's logs might fill up with this message).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jimfb I saw this pattern a lot of times, but it seems like it would be much easier to rework an abstract component as a whole if we know about all warnings at once (which can be delayed in time), instead of discovering them one by one. IMHO

Anyways, let me know if we still want to stick to the first warning only policy. I'll just add one more property to the _serverSideRendered object in that case

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We generally stick to warn-once where possible/practical, because otherwise a warning that fires often can drown out a warning that only fires infrequently (which results in people not seeing the less-frequent warning).

Let's add a global boolean instead of using _serverSideRendered, since the latter will be thrown away each render.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense

@jimfb
Copy link
Contributor

jimfb commented Jan 20, 2016

Overall, I think this is good. A couple of minor notes, then I think we can squash the commits together and merge.

@sophiebits
Copy link
Collaborator

Hmm. I thought we were going to start using ReactNoopUpdateQueue for server rendering instead of ReactUpdateQueue and in that case it would be easy to swap out an implementation that warns for server rendering. Really that would be a much cleaner solution than this.

Also, if we do go with this solution, please use ReactInstanceMap instead of _reactInternalInstance directly, like all of the surrounding code already does.

@mdolbin
Copy link
Author

mdolbin commented Jan 20, 2016

use ReactInstanceMap instead of _reactInternalInstance

Cool, thanks

- fire a single warning for all components;
- this._reactInternalInstance => ReactInstanceMap.get(this);
- _isServerSideRendered => _serverSideRendered;
- updated tests.
@facebook-github-bot
Copy link

@mdolbin updated the pull request.

1 similar comment
@facebook-github-bot
Copy link

@mdolbin updated the pull request.

@mdolbin
Copy link
Author

mdolbin commented Jan 31, 2016

@jimfb @spicyj let me know if there is something else I can do to improve my PR or if I should look for another solution. Thanks :)

@jimfb
Copy link
Contributor

jimfb commented Feb 1, 2016

Looks good to me. I'll leave it open for another day in case there are any other feedbacks, and then we can merge.

@jimfb
Copy link
Contributor

jimfb commented Feb 1, 2016

Oh, but please do squash the commits before we merge. You can do this using git rebase -i and then git push -f

@sophiebits
Copy link
Collaborator

There should not be logic about server rendering in src/isomorphic. It is a concept specific to the DOM renderer.

@jimfb
Copy link
Contributor

jimfb commented Feb 24, 2016

@mdolbin Do you want to update as per @spicyj's comment?

@mdolbin
Copy link
Author

mdolbin commented Feb 24, 2016

@jimfb sure, sorry, a lot of work these days.
But still, I wonder how can I interrupt async calls to setState and forceUdpdate from the scope of dom renderers without any changes in ReactConponent itself

@mdolbin
Copy link
Author

mdolbin commented Mar 7, 2016

@jimfb @spicyj Hey, guys, can I have some kind of a hint, please? It seems like I really stuck here but there is no way I want to give up.

@jimfb
Copy link
Contributor

jimfb commented May 3, 2016

@spicyj, any ideas on where you'd like the code to live?

@sophiebits
Copy link
Collaborator

Like I suggested originally, we should change to use ReactNoopUpdateQueue on the server, which would probably mean having queue come from the transaction (since that differs between client and server rendering). Also open to other ideas but that's what I suggested 3 months ago and it's still the best option that I see.

@gaearon
Copy link
Collaborator

gaearon commented Jun 26, 2016

I’m closing as this has been inactive for quite a few months but please leave another comment in #5473 and open a new PR if you’d like to work on this again! Thanks for your time.

@evenstensberg
Copy link

@gaearon I'm going to start to find a fix, but don't expect much. Do you mind providing as much info as you got on this issue? Been looking at how client side renders updates, were you thinking of involving ReactReconileTransaction to a more server friendly approach, or how do we want to accomplish noops? ( if I got the grasp right)

@gaearon
Copy link
Collaborator

gaearon commented Jun 26, 2016

I think the problem is that ReactCompositeComponent currently uses ReactUpdateQueue directly in several places. It passes it to public instances as the third updater argument so setState and forceUpdate calls fail during server rendering.

To fix the problem, instead of ReactUpdateQueue we would read the appropriate queue from the transaction argument by calling transaction.getUpdateQueue (new method). This would allow us to specify different values for it depending on the transaction type.

In ReactReconcileTransaction, getUpdateQueue() would return ReactUpdateQueue. In ReactServerRenderingTransaction, getUpdateQueue() would return ReactNoopUpdateQueue.

I hope this helps!

@rricard
Copy link
Contributor

rricard commented Jun 27, 2016

@ev1stensberg: sorry, seems that we started working on that at the same time. I have something on https://github.com/rricard/react/tree/fix-5473. I still have no tests so the PR is not complete yet (and I still need to fix two other failing tests), maybe we could work on that together, or if you can finish this PR faster than me, please feel free to use what I did!

@evenstensberg
Copy link

@rricard No worries, we are after the same thing here 😄 I'm busy 0400-1800 every day this summer, so I doubt I'll be done before you.

@rricard
Copy link
Contributor

rricard commented Jun 27, 2016

@ev1stensberg: cool! The PR is here if you are interested: #7127

@evenstensberg
Copy link

@rricard Looks like you manage well on your own!

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.

7 participants