question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

dispatching in componentWillMount

See original GitHub issue

Do 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:closed
  • Created 8 years ago
  • Reactions:12
  • Comments:57 (23 by maintainers)

github_iconTop GitHub Comments

43reactions
nawcommented, Sep 6, 2016

@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 and example.com/blog/27). If you have a slice in your redux store responsible for holding the “current” blog post content, merely having isFetched and isFetching 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:

  1. data not fetched
  2. data fetched, but data is for the wrong blog post id
  3. fetching data
  4. fetched data, and data is for the correct blog post id

One way to implement this is adding a blogPostId field to your store slice:

{
  data: "Content for a blog post",
  blogPostId: 5
  isFetching: false,
  isFetched: true
}

Then, if your component connects to this slice and sees that the blogPostId (5) is different than the blogPostId 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 in componentDidMount, you probably need to consider also fetching in componentWillReceiveProps. 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.

26reactions
nawcommented, Sep 7, 2016

@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 within componentWillMount 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 via props. 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 with setState.

Back to the isFetching stuff: I hope you can agree that there are three distinct states — the question is which of those states render needs to be capable of handling? In vanilla React, you can ensure that render never “sees” one of those states, by running your setState({ isFetching: true}) in componentWillMount (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 inside componentWillMount, it’s too late.

As far as I know, there are only three ways to remedy this issue:

  1. Modify 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.
  2. Modify your application so that your component never sees certain states (i.e. find a different way to ensure that { 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)
  3. Modify your application so that 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 for dispatch.

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 of connect.

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 a componentWillMount 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 my dispatch occurs before my component is mounted, which means I actually get the behavior you desire (i.e. my render function doesn’t have to deal with the “not fetching yet” state). However, this is not a simple library tweak we could code into react-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 to props is subtle, but significant, and you cannot blindly change setState to dispatch.

Perhaps react-redux docs could do a better job at helping people navigate these subtleties?

Perhaps react-redux could be rewritten to use state instead of props? (various difficulties in doing that, I believe).

I appreciate the discussion and would welcome your thoughts.

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found