Reputation: 504
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
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
Reputation: 1267
Couple of things I can think of:
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} />;
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)
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