Reputation: 255
Using the terminal to test my dispatched actions, Redux-logger shows that my state is being correctly updated. However, my component is not re-rendering as a result of the state change. I've looked at the SO answers regarding component not re-rendering, a majority of the responses claim that the state is being mutated; as a result, Redux won't re-render. However, I'm using Lodash's merge to deep-dup an object, I'm pretty sure I'm not returning a modified object. (Please see attached snippet below)
Would love to hear some advice from you guys, pulling my hair out on this one!
const usersReducer = (state = {}, action) => {
Object.freeze(state); // avoid mutating state
console.log(state);
// returns an empty object
let newState = merge({}, state);
console.log(newState);
// returns my state with my dispatched action object inside already???
// newState for some reason already has new dispatched action
switch (action.type) {
case RECEIVE_USER:
let newUser = {[action.user.id] = action.user};
return merge(newUser, newUser);
case RECEIVE_USERS:
newState = {};
action.users.forEach(user => {
newState[user.id] = user;
});
return merge({}, newState);
default:
return state;
}
};
React Container Component
import { connect } from 'react-redux';
import { receiveUsers, receiveUser, refreshAll, requestUsers, requestUser } from '../../actions/user_actions';
import allUsers from '../../reducers/selectors';
import UserList from './user_list';
const mapStateToProps = (state) => ({
users: allUsers(state), // allUsers (selector that takes the state specfically the user Object and returns an array of user Objects)
state
});
const mapDispatchToProps = (dispatch) => ({
requestUser: () => dispatch(requestUser()),
requestUsers: () => dispatch(requestUsers()),
receiveUsers: (users) => dispatch(receiveUsers(users)),
receiveUser: (user) => dispatch(receiveUser(user)),
refreshAll: (users) => dispatch(refreshAll(users))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserList);
React Presentational component
import React from 'react';
class UserList extends React.Component {
render() {
const { users, state } = this.props;
const userItems = users.map((user, idx) => {
return(<li key={idx}>{user.username}</li>);
});
return (
<div>
<ul>
{ userItems }
</ul>
</div>
);
}
}
export default UserList;
React Store
import { createStore, applyMiddleware } from 'redux';
import createLogger from 'redux-logger';
import RootReducer from '../reducers/root_reducer';
const logger = createLogger();
const configureStore = (preloadedState = {}) => {
return createStore(
RootReducer,
preloadedState,
applyMiddleware(logger));
};
// const configureStore = createStore(rootReducer, applyMiddleware(logger));
// oddly enough, when I have the store as a constant and not a function that returns the store constant, dispatching actions through the terminal will correctly update the state and rerender the component
export default configureStore;
React Selector
const allUsers = ({ users }) => {
return Object.keys(users).map(id => (
users[id]
));
};
export default allUsers;
Upvotes: 23
Views: 42521
Reputation: 91
i spent lot of time to find out that when using more than 1 reducer (with combineReducers), then your mapStateToProps should point the correct reducer names, for example
const mapStateToProps = state => ({
someVar: state.yourReducerName.someVar,
loading: state.yourReducerName.loading,
error: state.yourReducerName.error
});
Upvotes: 0
Reputation: 4112
Create new copy of array from the prop state to re-render the component
render() {
const {allPost} = this.props;
//Use the spread operator to create a new copy of the array
const posts = [...allPost];
const plansList = () => {
return posts.length < 1 ? null : posts && <PlansList allPost={posts}
/>;
};
return (
<>
<Container className="mt-lg-5 pt-lg-5">
{plansList()}
</Container>
</>
);
}
Upvotes: 2
Reputation: 160
Common problem in this case is using not reactive operations for changing state. For example use concat() for array, but not push() and so on.
Upvotes: 5
Reputation: 7555
I had a similar problem, just in case someone stumbles upon this, I needed to clone the array in order to re-render the view:
export const addFieldRow = () => (
(dispatch: any, getState: any) => {
const state = getState();
const myArrayOfObjects = myArrayOfObjectsProp(state);
const newObject = {
key: "",
value: "",
};
myArrayOfObjects.push(newObject);
dispatch(addFieldRowAction({ myArrayOfObjects: [...myArrayOfObjects] })); <== here
}
);
Upvotes: 24
Reputation: 243
I use this solution to do it. I put the users on my state and update it on any change, with componentWillReceiveProps. Hope it helps :-)
class UserList extends React.Component {
constructor(props) {
super(props);
console.log(this.props);
this.state = {
users: props.users
};
}
componentWillReceiveProps(nextProps) {
if (this.props.users !== nextProps.users) {
this.setState({
users: nextProps.users,
});
}
}
render() {
const { users } = this.state;
const userItems = users.map((user, idx) => {
return(<li key={idx}>{user.username}</li>);
});
return (
<div>
<ul>
{ userItems }
</ul>
</div>
);
}
}
export default UserList;
Upvotes: 4
Reputation: 302
What do your React components look like? Are you using internal state in them or props to push the data down. Usually I see the issue is with people setting the internal state of props with Redux state. You should be pushing props down to the components and they will re-render on update.
Also, check out https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en
to see if the props are really changing.
Upvotes: 2