dispatching in componentWillMount
See original GitHub issueDo we expect dispatching in componentWillMount
to take effect before the component is rendered?
For example, this test fails:
it('should handle dispatches before componentDidMount', () => {
const store = createStore(stringBuilder)
@connect(state => ({ string: state }) )
class Container extends Component {
componentWillMount() {
store.dispatch({ type: 'APPEND', body: 'a' })
}
render() {
expect(this.props.string).toBe('a'); // => Error: Expected '' to be 'a'
return <Passthrough {...this.props}/>
}
}
const tree = TestUtils.renderIntoDocument(
<ProviderMock store={store}>
<Container />
</ProviderMock>
)
})
Issue Analytics
- State:
- Created 8 years ago
- Reactions:12
- Comments:57 (23 by maintainers)
Top Results From Across the Web
Multiple dispatch in componentWillMount React - Stack Overflow
Dispatch is synchronous, but this will only guarantee that fetchCategories is fired (not fetched) before fetchPlaces .
Read more >Update React Component Lifecycle - Medium
You can move code from componentWillMount to constructor and componentDidMount. ... and any action dispatch will move to componentDidUpdate.
Read more >React.Component
This page contains a detailed API reference for the React component class definition. It assumes you're familiar with fundamental React concepts, ...
Read more >Reactjs – Prevent react component from rendering twice when using ...
I have a React component that dispatches a redux state change in its componentWillMount function. The reason is that when the component is...
Read more >React redux dispatch action before render container-Reactjs
componentWillMount () and componentDidMount() for you. And my opinion - you should avoid using the componentWillMount and prefer the componentDidMount - this ...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
@jordanmkoncz
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
<BlogPost>
component that displays the content of a blog post, based on an ID in the URL (e.g.example.com/blog/5
andexample.com/blog/27
). If you have a slice in your redux store responsible for holding the “current” blog post content, merely havingisFetched
andisFetching
booleans will be inadequate just as you said.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
blogPostId
field to your store slice:Then, if your component connects to this slice and sees that the
blogPostId
(5) is different than theblogPostId
provided in the URL (27, via react-router params), it knows to display the spinner. Once the fetch for post 27 is received, you update the slice, and the component re-renders without the spinner since the ids match.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
<BlogPost>
component does not get re-mounted (i.e.componentDidMount
is not called), so if you’re fetching data for a component only incomponentDidMount
, you probably need to consider also fetching incomponentWillReceiveProps
. This is just a ramification of how react-router and react work.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.
@Matsemann Yes, I think you make a great point!
You are correct that the React API for
componentWillMount
specifies that state changes will take place before the first render. It’s easy to expect a synchronous dispatch to a Redux store from withincomponentWillMount
to behave the same way.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
react-redux
bindings are implemented.As @markerikson said,
react-redux
pushes everything from the store down to connected components viaprops
. This means at the time of mounting, the props have already been pushed down, and there is no way for the component to intercept those props with an immediate state change like you can do withsetState
.Back to the
isFetching
stuff: I hope you can agree that there are three distinct states — the question is which of those statesrender
needs to be capable of handling? In vanilla React, you can ensure thatrender
never “sees” one of those states, by running yoursetState({ isFetching: true})
incomponentWillMount
(I believe this is the invariant that @Matsemann is talking about).The problem in
react-redux
is there is no way from within the component itself to change props; by the time you’re insidecomponentWillMount
, it’s too late.As far as I know, there are only three ways to remedy this issue:
react-redux
to subscribe to the Redux store from within your component (rather than from within a wrapper around your component). This likely has many ramifications that could lead to different problems, although perhaps it’s worth exploring.{ isFetching: true}
is the first state seen by your component). Ultimately this means putting your fetch dispatch somewhere outside of your component (personally, this is what I do)render
can handle all 3 states (which is what I proposed earlier to @jordanmkoncz ).Ultimately (and perhaps unfortunately), it’s not as simple as blindly swapping out
setState
fordispatch
.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
react-redux
is using such a relationship in its implementation ofconnect
.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
react-redux
or Redux needs to clearly delineate suggestions for how you might need to modify your application to handle these subtleties?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
componentWillMount
)Personally I take presentation only to an extreme – I use a modified version of
connect
that accepts acomponentWillMount
argument that runs in the context of the connected wrapper instead of the underlying wrapped component. This means the component knows nothing about fetching data. It also means that mydispatch
occurs before my component is mounted, which means I actually get the behavior you desire (i.e. myrender
function doesn’t have to deal with the “not fetching yet” state). However, this is not a simple library tweak we could code intoreact-redux
– it’s a fundamental shift in how I (or you) think about components.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
state
toprops
is subtle, but significant, and you cannot blindly changesetState
todispatch
.Perhaps
react-redux
docs could do a better job at helping people navigate these subtleties?Perhaps
react-redux
could be rewritten to usestate
instead ofprops
? (various difficulties in doing that, I believe).I appreciate the discussion and would welcome your thoughts.