Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dispatch Action right after state variable is set

Tags:

reactjs

redux

I have an initial redux state like this:

{
  loggedInUserId: null,
  comments: []
}

Here's how my React App looks like:

class App extends Component {
  componentWillMount() {
    this.props.getLoggedInUserId();
  }

  render() {
    return (
      <Switch>
        <Route exact path="/" component={HomePage} />
        <Route path="/comments" component={Comments} />
      </Switch>
    );
  }
}

In my App, I dispatch an action getLoggedInUserId() which asynchronously fills the loggedInUserId in the state.

The HomePage is a dumb component showing some text. I start the app (route is now '/'), see the HomePage component, then I navigate to the Comments page, which has:

componentWillMount() {
  this.props.fetchComments(this.props.loggedInUserId); // Dispatch action to do API call to fetch user's comments
}

render() {
  // Show this.props.comments nicely formatted
}

Everything works, I see the list of comments in the Comments component.

But if I refresh the page on the route /comments, then by the time the Comments runs componentWillMount, the loggedInUserId has not been loaded yet, so it will call fetchComments(null).

Right now, to fix this, I'm doing in my Comments component:

componentWillMount() {
  if (!this.props.loggedInUserId) return;
  this.props.fetchComments(this.props.loggedInUserId);
}

componentWillReceiveProps(nextProps) {
  if (!this.props.loggedInUserId && nextProps.loggedInUserId) {
    nextProps.fetchComments(nextProps.loggedInUserId);
  }
}

which works well. But I'm doing this in 10+ components, and it seems like a lot of work which can be factorized, but I didn't find an elegant way to do it.

So I'm asking you how do you generally deal with this kind of situation? Any idea is welcome:

  • HOC
  • side-effects
  • other libraries
like image 572
jeanpaul62 Avatar asked Dec 23 '17 12:12

jeanpaul62


1 Answers

I'm using wrapper around Route, which checks if users are logged in and if not, redirect them to login page. Wrapped routes are rendered only after userId of authenticated user is fetched.

import * as React from 'react'
import { Route, Redirect } from 'react-router-dom'
import URLSearchParams from 'url-search-params'

class AuthRoute extends React.Component {
  componentDidMount() {
    if (!this.props.isLoading) {
      this.props.getLoggedInUserId()
    }
  }

  render() {
    if (this.props.isLoading) {
      // first request is fired to fetch authenticated user
      return null // or spinner
    } else if (this.props.isAuthenticated) {
      // user is authenticated
      return <Route {...this.props} />
    } else {
      // invalid user or authentication expired
      // redirect to login page and remember original location
      const search = new URLSearchParams({
        next: this.props.location.pathname,
      })
      const next =
        this.props.location.pathname !== '/' ? `?${search.toString()}` : ''
      return <Redirect to={`/login${next}`} />
    }
  }
}

You need to update your reducer which handle getLoggedInUserId action to store also isLoading state.

like image 114
Tomáš Ehrlich Avatar answered Nov 03 '22 17:11

Tomáš Ehrlich