maehue
maehue

Reputation: 504

React Redux how to dispatch using ownProps route parameter

I can't seem to figure out how to just load a component/object using the route parameters / ownProps my configuration is basically the same as is standard with the react redux apps and my actions/reducers work fine but for this component the data is not pre-loaded. Which I assume means there is a race happening where it is attempting to use the 'user' object before it is defined looks like. I assume this because it occasionally does work - it is going through the reducer(s) and api calls - and the result looks good. Is there some way I should first create a default 'user' object?

The route looks like

[tld]/user/1234

The component

import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as userActions from '../../actions/userActions';
import User from './User';

class UserPage extends Component{
   constructor(props, context){
      super(props, context);
   }

   componentDidMount(){
      this.fetchData();
   }

   fetchData(){
      const {userId} = this.props;
      const {fetchUser} = this.props.actions;
      fetchUser(userId);
   }

   render(){
      // This component when rendered has undefined property exceptions 
      //      though sometimes it works :)
      return(<User user={this.props.currentUser} />);
   }
}

function mapStateToProps(state, ownProps){
   return {
      currentUser: state.user,
      userId: ownProps.params.id
   }
}

function mapDispatchToProps(dispatch){
   return {
      actions: bindActionCreators(userActions, dispatch)
   };
}

export default connect(mapStateToProps, mapDispatchToProps)(UserPage);

UPDATE So I came up with an ugly way to resolve this in the render

return (
    {Object.keys(this.props.currentUser).length > 0 ? 
       <User user={this.props.currentUser} /> ? null }
)

This can't be the correct way to do this. - Also I notice if I just change the route id there is a brief flash of the previously loaded user while waiting for the delay of the api call to return the new user...Sorry react/redux are fairly new to me

Upvotes: 3

Views: 2957

Answers (2)

Kokovin Vladislav
Kokovin Vladislav

Reputation: 10391

there are ways to avoid undefined props:

1 Define initial state in user reducer

const initialState = { user:'', ... };
const user = (state = initialState, action) => {...};
export default user;

2 Handle undefined in mapStateToProps with state.user||""

const mapStateToProps = (state, ownProps) =>({
      currentUser: state.user||"",
      userId: ownProps.params.id
});

3 Sometimes necessary to not allow mounting component before data loaded.

const LoadingBlock = props => {
    return (<div>{props.isLoading ? 'loading...' : props.children}</div>);
};
LoadingBlock.defaultProps = {
    isLoading: true
};

///then wrap component that will be loading

<LoadingBlock isLoading={this.props.isLoading}>
   <UserComponent user={this.props.user}>
</LoadingBlock>

4 if this.props.user undefined, then not show userComponent

render(){
    let {user} = this.props
    return(
        <div>{user && <UserComponent user={user}>}</div>
    )
}

5 if this.props.user is not undefined, then show component, else show some "content"

render(){
    let {user} = this.props
    return(
        <div>{user ? <UserComponent user={user}> : "loading"}</div>
    )
}

6 define default props

class Control extends React.Component {...}
Control.defaultProps = {value: "somevalue"};

Upvotes: 3

0xRm
0xRm

Reputation: 1267

Couple of things I can think of:

  1. Checking if this.props.currentUser is defined is actually a good idea, but I would use this syntax:

    const { currentUser } = this.props;
    return currentUser && <User user={currentUser} />;
    
  2. componentDidMount is only called when the component is added to the DOM. So if the UserPage component is already in the DOM, it wont trigger and your fetch code will never be called. In that case you could use the componentWillReceiveProps event to evaluate the new props. (see: https://facebook.github.io/react/docs/component-specs.html for more info)

  3. What does fetchUser do exactly? I assume its an async call to fetch the user data. Are you using some middleware like redux-thunk? How does that code look? If you're using Thunk, you can dispatch multiple actions to indicate the status of the fetch. So, something like this:

    export function fetchUser(userId) {
      return dispatch => {
        dispatch(fetchUserStart());
        someService.fetchUser(userId)
          .then(user => dispatch(fetchUserDone(user)))
          .catch(error => {
            dispatch(fetchUserError(error));
          });
      };
    }
    

    In the above code fetchUserStart, fetchUserDone and fetchUserError are all actions (code not shown here) which are affecting the user state. fetchUserStart could set the user state to undefined, whereas fetchUserDone sets it to the user you got from the async fetch. As your UserPage component is connected to this state, it will react by showing / not showing the User component.

Upvotes: 1

Related Questions