I'm creating a React/Redux front-end for a multi-channel chat app. I'm having problems getting some React components to re-render after state change while using redux
, react-redux
, and redux-thunk
.
I believe that my reducers are non-mutating, and that I'm subscribed via react-redux
's connect
. When I run the app and view the browser console, I see the initial render of the component (i.e. with initial, empty state), then the state change (triggered by an action dispatch in index.js
).... I would then expect the component to re-render with new props, but it doesn't happen.
I've put up a repo here: https://github.com/mattmoss/react-redux-no-update
node_modules
is not in the repo, so to run, first download dependencies (running yarn
is sufficient), then npm start
.
Some excerpts (see full source in repo):
reducers/channelList.js
import * as c from '../actions/constants';
export default function channelList(state = [], action) {
switch (action.type) {
case c.FETCH_CHANNELS_SUCCESS:
return action.channels;
default:
return state;
}
}
actions/channelActions.js
export function fetchChannels() {
return (dispatch) => {
return ChannelApi.allChannels()
.then(channels => dispatch(fetchChannelsSuccess(channels)))
.catch(error => { throw(error); });
};
}
export function fetchChannelsSuccess(channels) {
return {
type: c.FETCH_CHANNELS_SUCCESS,
channels
};
}
components/ChannelListView.js
class ChannelListView extends React.Component {
render() {
const { channels, current, onSelect } = this.props;
console.log("channels:", channels, "current:", current);
return (
<ListGroup>
{channels.map(channel =>
<ListGroupItem
key={channel.id}
active={channel.id === this.props.current}
onClick={onSelect(channel.id)}
>
<strong>#{channel.name}</strong>
</ListGroupItem>
)}
</ListGroup>
);
}
}
export default ChannelListView;
containers/ChannelList.js
import ChannelListView from '../components/ChannelListView';
const mapStateToProps = (state, ownProps) => {
return {
channels: state.channelList,
current: state.currentChannel
};
};
const mapDispatchToProps = (dispatch) => {
return {
onSelect: (id) => () => {}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(ChannelListView);
App.js
class App extends Component {
render() {
return (
<Grid>
<Row>
<Col>
<h1>Channels</h1>
<ChannelList />
</Col>
</Row>
</Grid>
);
}
}
index.js
const store = configureStore();
store.dispatch(fetchChannels());
ReactDOM.render(
<Provider store={configureStore()}>
<App />
</Provider>,
document.getElementById('root')
);
store/configureStore.js
import { createStore, applyMiddleware } from 'redux';
import rootReducer from '../reducers/rootReducer';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
export default function configureStore() {
return createStore(
rootReducer,
applyMiddleware(thunk, logger)
);
}
State updates in React are asynchronous; when an update is requested, there is no guarantee that the updates will be made immediately. The updater functions enqueue changes to the component state, but React may delay the changes, updating several components in a single pass.
Your component is only going to re-render if its state or props are changed.
The fact that the Redux state changes predictably opens up a lot of debugging possibilities. For example, using time travel makes it possible to travel back and forth between different states instead of having to reload the whole app in order to get back to the same place.
You need to create a selector and use it in your component to re-render your component after the state changes. Updating the state with proper action (to update username) will cause a re-render in the Topbar component to get the new username from the store.
I'm not 100% as I am still relatively new to React myself. But look at your index.js
script.
// You configure the store, then dispatch the fetchChannels action
const store = configureStore();
store.dispatch(fetchChannels());
ReactDOM.render(
// But here, you're recreating the store again, which I think will re-initialise an empty store
// Change this to use the `store` variable from above.
<Provider store={configureStore()}>
<App />
</Provider>,
document.getElementById('root')
);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With