-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
dispatching in componentWillMount #210
Comments
Would you like to add this test and see if you can fix it? |
@jedborovik did you find a solution to this? |
As I said in the previous comment it might be related to another bug report. Please help investigate and fix it. |
Sorry, I'd taken a look at #196 and from a glance didn't think it was related. This is more an issue of deliberately changing the state after redux has already set the props. In any case, I tried applying #196 locally and the issue still persists. The react docs mention that if you change a component's state during I haven't had time to look into the source for |
@gaearon alright after doing some digging, I found the problem. |
Alright I guess I spoke too soon. I'm really not sure how we can work around this problem. I made the mistake of confusing In other words, we can't stop ourselves from calling render the first time because the dispatch hasn't happened yet. I'm at a loss here. Here's a test case, I didn't want to open a PR for it because I don't have a solution: it('should only call render once (with the updated data) when a state change occurs during componentWillMount', () => {
const store = createStore(stringBuilder)
@connect(state => ({str: state}))
class Container extends Component {
componentWillMount() {
store.dispatch({ type: 'APPEND', body: 'a' })
}
render() {
expect(this.props.str).toBe('a')
return (<div></div>)
}
}
TestUtils.renderIntoDocument(
<ProviderMock store={store}>
<Container />
</ProviderMock>
)
}) |
Please do open a PR with a failing case, it makes it easier for others to try to fix it. |
Roger that. Just opened PR #222. |
I looked at it and I think React works as intended here. |
@lsapan: can't do |
@evandrix you can return an empty div, I just didn't write it out. |
We have a component that loads some data in However, when the component is unmounted and later mounted (for instance going to another page in the react-app and back), this doesn't work as expected. The data is again fetched by an action inside In our case, this has the unwanted side-effect of generating a rather expensive chart for the old data, that is immediately thrown away anyway. How can this be avoided in a nice way when we inside the render function only sees the old store data, not the data being set in willMount? |
Use a higher order component to dispatch the action and to not render the child component until the required props are present. |
Don't think that will work? As stated, this is an issue the second time the component is mounted, as the old data is still present. So the props are there for a brief moment, just with the old values, so checking that they are present wont solve it. |
You check for the presence of the required props in a higher order component. And that component that wraps the component that cares about certain props is prevented from rendering by doing a return in the higher order component.
This sort of talks about what I'm trying to say http://natpryce.com/articles/000814.html |
But that higher order component will still see the wrong data, and thus not really know if to render the wrapped component or not. |
Correct you just need to render something else in the HOC until the correct On 31 March 2016 at 23:08, Mats Krüger Svensson notifications@github.com
|
@gaearon would something like Alt.js https://github.com/altjs/utils/blob/master/src/connectToStores.js#L80-L88 |
|
That is a good point. After more thought, the cases I was thinking of could be solved with firing actions in React Router event handlers. Thanks for explaining! |
@chrishoage Did you manage to dispatch actions in the @gaearon I'm having the issue where I need the changes in my first render because it being on the server side and if it would be picked up later it causes my server side rendered HTML would differ from the HTML on the client. So the changes being "picked up" on the next render is not good enough, or am I overseeing a solution to this? |
Usually the solution is to hydrate data first and then render. So rather than render in a lifecycle hook, you would get the matched components from the router, call a static method on them which dispatches an action, and wait for the promise to resolve. Does this make sense? https://github.com/markdalgleish/redial is one way of doing it. |
Thanks for your response @gaearon! I do hydrate my store before rendering. If I understand correctly that is not the problem. To be a little more specific: |
I think the problem is you’re effectively duplicating the data between the store and the router. Why not just use the router params for this? Is there any particular reason you prefer it coming from the store? |
Since there are other ways to change the language than just with the URL I would have to then have to duplicate the state between the store and the router if I understand correctly. Edit: I think you're right though, I should not try to do this with redux, I'll try to only use the params only. Thanks a lot for your help @gaearon. |
You would want to change URL anyway, at which point it’s easier to treat router as the source of truth for the URL. |
@gaearon I have sync action which fill up data in store while dispatching action using higher order component import React, { PropTypes, Component } from 'react';
import { connect } from 'react-redux';
export default function decoratedComponent(options = {}) {
return (WrappedComponent) => {
class HOC extends Component {
constructor(props) {
super(props);
//Binding other methods
}
componentWillMount() {
this.props.actions.initialize(this.props); // This initialize state from the props
}
render() {
console.log(this.props.form) // This is undefined or {} which initial state.
return(
<WrappedComponent
{...this.props}
/>);
}
}
function mapStateToProps(state) {
return {
form: state.forms
};
}
return connect(mapStateToProps)(HOC);
};
} |
Also, depending on your exact needs, the simplest change to get the correct spinner behavior is to make the initialState for Or, for more clarity, you could rename |
@naw At one point I was actually trying to solve this using one of those suggestions; for every However I ran into an issue with this solution as well - it works when rendering a component for the first time, but if I switch to a different component (e.g. via route change) and then back again, the |
Yes, the issue you point out is certainly a very real issue, and one that I've experienced myself. There are various challenges that arise when you build a single page application using a store that persists across different URL's. In a traditional server-rendered application, every time you land on a new URL, all of your data is thrown away and fetched from scratch synchronously, and then passed to your template. You don't have to worry about a template getting the wrong data from an old URL In a single page application with a store (i.e. redux/react with react-router), all of your data is just sitting there, and nothing automatically marks it as "stale" when you visit a new URL. There might be abstractions you can build on top of redux/react that will help with this, but vanilla redux/react doesn't solve this problem for you. Suppose you have a The solution to this problem is similar to the solution I mentioned previously ---- you need to identify all of the distinct states you might find yourself in, and make sure your store slice has adequate information to help you distinguish these states, or at least distinguish the ones that matter (i.e. should I show a spinner or not). You actually have 4 distinct states:
One way to implement this is adding a {
data: "Content for a blog post",
blogPostId: 5
isFetching: false,
isFetched: true
} Then, if your component connects to this slice and sees that the Another way to organize your state is to have a slice that holds all fetched blog posts in a hash by id. Your component knows the desired blog post id (e.g. from react-router params), and reaches into that slice to find the correct blog post --- if the key for that id is missing, you display the spinner and wait for the correct blog post to be received. There really is no getting around this, unless you use a higher-level abstraction on top of react/redux. Personally I'm still brainstorming on building such an abstraction for my own projects. Until then, I believe the aforementioned techniques are adequate, albeit a little painful. The reality is that redux is a low-level tool, not a high-level tool, so you have to do more from scratch unless you're using other tools on top of it. Finally, just a a warning in case you haven't run into this yet -- if you're using react-router and link directly from blog post 5 to blog post 27, the You have some great questions, and I'm just responding because I've run into the same issues and spent a lot of time thinking about it. If anyone knows of a simpler way to solve this with vanilla redux, I'd be interesting in hearing it. |
@naw : this deserves to be turned into a blog post of its own, really. We could use more publicly available info on how to think in terms of app state. |
@jordanmkoncz Yeah we faced the same problem, as written far up here somewhere. One can make it work for the first render, but that only postpones the problem to when the component is hidden and shown again. It becomes a lot of boilerplate and pit falls to make sure a simple spinner can be shown. @naw, great post
I disagree with this. The code declares an invariant that should hold, but then Redux goes ahead and breaks it.
and
It's specific to Redux. If one had used setState from React instead of dispatching an action in componentWillMount, the correct props would be available on first render. This is a promise made in React's API, so no wonder people get confused when this doesn't prove to be true when switching state handling to Redux. |
@Matsemann : Not sure what "invariant" you're referring to. I also don't see this as anything specific to Redux. It looks like this applies to any use of the "container component" pattern, where a presentational component is asking a parent component to fetch data. That means that the data is coming in via props instead of being applied internally via |
@Matsemann Yes, I think you make a great point! You are correct that the React API for I agree this is confusing, and I think it's worth discussing how (or if) its feasible to improve it. To be clear, this is not a Redux issue, it's a ramification of how the As @markerikson said, Back to the The problem in As far as I know, there are only three ways to remedy this issue:
Ultimately (and perhaps unfortunately), it's not as simple as blindly swapping out I agree with @markerikson that this is intrinsic to a parent-child props relationship. However, a potential problem is that it's not conceptually obvious that In other words, we are not encouraged to think of connected components as presentational "children" receiving props from a connected wrapper --- instead, we tend to think of the component being connected and the resulting decorated component as one-and-the-same. At least, that's my perception. It's pretty common to see connected components that have data fetching inside of them, which can lead to problems. Perhaps One conceptual way to handle these subtleties is to treat your components as presentation only as @markerikson suggested. In other words, force yourself to think of your components as simply receiving state with no ability to change state (as opposed to what you might traditionally do in Personally I take presentation only to an extreme -- I use a modified version of TL;DR In vanilla React, components are meant to be a mix of state management and presentation. In vanilla Redux, if you try to mix state management and presentation, you can run into non-obvious problems. Ultimately, the switch from Perhaps Perhaps I appreciate the discussion and would welcome your thoughts. |
Hmm. That's exactly how I view things - wrapper container and presentational component. Admittedly, I'm a really bad example case - I'm obviously intimately familiar with how
I love the simplified version of |
Thanks for your perspective and also the link, @markerikson For context, let me explain my personal philosophy (just an opinion): I believe a truly presentational component should:
A presentational component receives props. Those props are generally either a piece of data to render, or a function to call when something interesting (like a click) happens. These pieces of data generally are slices of Redux state, or at least derived from Redux state. The functions are often bound-action-creators. (I know you know this, @markerikson, just trying to be very clear within the discussion) I don't think a presentational component should ever dispatch something like I suppose you could consider "I am mounting" as something interesting, in which case the parent could expose a function through props like If we did think about our components like this, which I believe was your point farther up, then the subtleties discussed in this issue could be avoided. I think we're on the same page in this regard. ❤️ So, one question is whether people generally "get" that they need to construct their components differently when they use Redux than when they use vanilla React? Or, is it trivial to convert an application from vanilla React to Redux? Can (or should) we make it more trivial to do so? There are two extremely different conceptual ways to approach this:
My opinion is that the current A better version of (1) would put things like A better version of (2) would subscribe to the Redux store directly inside the component (i.e. no wrapper), so that the component's state can be manipulated directly (e.g. To be clear, I'm not trying to rag on |
Coincidentally, FB is maybe considering depreciating componentWillMount |
@jimbolla : dude, you beat me to it by 13 seconds. :( |
Great posts @naw, you've definitely helped clarify the subtle implications of how After taking this into consideration, I've come up with a solution to this problem. I'll extend your I'm using normalizr for my project, which manages an
My
When I successfully fetch a given blog post, it will be added to Given this state shape and reducer functionality, I now have the following variables which are used to determine my loading state at any given time:
Based on these variables, I want to reduce my loading state at any given time to be one of the following:
I can do so with the following logic:
Given these 3 possible loading states, I can have the following logic in my component.
Of course, in my actual implementation I'm using constants rather than direct string comparisons, and I've created some abstractions for getting the current loading state, but I've deliberately tried to be explicit here. So far, this solution appears to work well and keeps state/reducer boilerplate to a minimum (e.g. no need to maintain a separate |
At first glance this looks like a solid approach. However, there are some nuances to consider.
In the scenario where you visit Also, it's feasible for AJAX calls to return in the wrong order. Suppose you visit
Probably the easiest way to guard against this is to prevent more than one fetch at a time. However, some features need to fire off more than one request at at time (like an autocomplete query, for example), so you may have to use more complicated techniques. |
Any chance you've found a decent way to work around this? I have a very similar problem and been banging my head on the wall for a few days with no success. I'm new to React and Redux which is making this even more frustrating. |
@alien109 could you clarify what specific issue you've been having trouble with? |
Sorry for not being more clear. I'm assuming I'm running into a similar situation as you'd described in a comment above. I'm dispatching an action in ComponentWillMount to perform multiple ajax calls to receive data. Once all of the ajax calls have completed, I'm dispatching an action that updates a slice in my store that sets a fetching status for the entire page. This way I can check that rather than for each individual entity that's being passed in from the store. For the first time a visit the route, the data all gets returned, the store updates, props update, and the component is rendered correctly. If I then navigate away from that route, and then return, the process of fetching data above is started again, except the component is then rendered before the props are updated. Even though I can see that the store is being updated correctly, the component calls render before it's props are updated with the new store state. Any sort of fetching status flags I'm using are pointless since they are out of date. The only solution I've come up with is moving all data rendering bits into separate helper functions in the class and then wrapping them in try/catch statements. I then can return an empty tag in the catch block so that errors get swallowed and don't halt the execution of the component rendering again once the props actually do get updated. This feels super janky though. I'm assuming this was the primary issue you were having, but maybe that's my misunderstanding. As I stated, I'm just getting started with React and Redux, so it's possible I'm just being confused. BTW, thanks for a quick response. |
Conceptually, the act of navigating to the page should cause a state change (mark existing data as stale, set a fetching flag, etc.). The problem is that the component must render before anything has a chance to update the redux state. This means there is no way for One workaround is for the component itself to provide some of the key information (in particular, the fact that it was just navigated to). As an example, the component can track when it was mounted (i.e. as a A sample implementation might be this: class MyComponent extends Component {
componentWillMount() {
this.setState({ mountedAt: Date.now() })
// ajax calls go here, with dispatch afterwards
}
render() {
if (this.props.data && this.props.data.fetchedAt > this.state.mountedAt) {
// return content
}
// return spinner
}
}
mapStateToProps = (state) => ({
data: state.someSlice
})
export default connect(mapStateToProps)(MyComponent); Depending on your application, you'd likely have to do something similar in There are variations of this approach (for example, you don't have to use a timestamp, you could use a unique id instead). I believe it's feasible to implement something like this completely outside of the component itself by using the factory version of The fundamental cause of this issue is that a connected component has two sources of input (ownProps, and redux-derived props) which can be out-of-sync. This manifests itself when you using a router like react-router (which passes information to you via ownProps). Conceptually you might be able to solve this by ignoring ownProps, and rigging the router to pass route changes to you through redux, but that's a large can of worms probably best avoiding right now. |
The solution I came up with was multiple nested HOCs (using recompose). Three layers: one that maps the action creators, one that uses the action and one that pulls the data from the store. In this way, the action is called in layer 2's componentWillMount before layer 3's props are generated. MyHandler = compose(
connect(null, mapDispatchToProps),
lifecycle({
componentWillMount(){
this.props.fetch(this.props.id);
}
}),
connect(mapStateToProps)
)(CMAHandler); It works but I feel silly doing it |
@naw Your solution worked really well for me. The spinner is never even visible.. it just needed a few extra milliseconds to retrieve the data from the |
I was having the same issue as many of the folks who have commented - I was making chained asynchronous calls and I wanted to wait for them all to complete before going through my render logic. I had a loading store that was set to true before the ajax calls were made and false once they were all complete. This was initiated in componentWillMount, but render was being called once, at least, before the loading store was set. I tried several of the solutions but they just didn't suit my needs. I came up with a solution that, so far, has worked for me. I actually didn't change my loading store:
In the relevant components I added a ready state:
which was set to false in componentWillMount:
Then, in componentWillReceiveProps:
and in render: So far this has worked everywhere for me. |
Also can be solved by setting loading state in |
you can set the
, and call this
worked for me ,
|
Do we expect dispatching in
componentWillMount
to take effect before the component is rendered?For example, this test fails:
The text was updated successfully, but these errors were encountered: