I am trying to design a notification component where notifications will appear on certain occasions (like connections problems, successful modifications, etc.).
I need the notifications to disappear after a couple of seconds, so I am triggering a state change to delete the notification from Redux state from setTimeout
inside the notification's componentDidMount
.
I can see that the state does change, but React-Redux is not re-rendering the parent component so the notification still appears on the DOM.
Here is my Redux reducer:
const initialState = {
notifications: []
}
export default function (state = initialState, action) {
switch(action.type) {
case CLEAR_SINGLE_NOTIFICATION:
return Object.assign ({}, state, {
notifications: deleteSingleNotification(state.notifications, action.payload)
})
case CLEAR_ALL_NOTIFICATIONS:
return Object.assign ({}, state, {
notifications: []
})
default:
return state
}
}
function deleteSingleNotification (notifications, notificationId) {
notifications.some (function (notification, index) {
return (notifications [index] ['id'] === notificationId) ?
!!(notifications.splice(index, 1)) :
false;
})
return notifications;
}
and my React components (Main
and Notification
):
/* MAIN.JS */
class Main extends Component {
renderDeletedVideoNotifications() {
console.log('rendering notifications');
const clearNotification = this.props.clearNotification;
return this.props.notifications.map((notification)=> {
return <Notification
key={notification.id}
message={notification.message}
style={notification.style}
clearNotification={clearNotification}
notificationId={notification.id}
/>
});
}
render() {
console.log('rerendering');
return (
<div className="_main">
<Navbar location={this.props.location} logStatus={this.props.logStatus}
logOut={this.logout.bind(this)}/>
<div className="_separator"></div>
{this.props.children}
<BottomStack>
{this.renderDeletedVideoNotifications()}
</BottomStack>
</div>
);
}
}
function mapStateToProps(state) {
return {logStatus: state.logStatus, notifications: state.notifications.notifications};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({checkLogStatus, logOut, clearNotification, clearAllNotifications}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(Main);
/* NOTIFICATION.JS */
export default class Notification extends Component{
constructor(props){
super(props);
this.state = {show: true}
}
componentWillReceiveProps(nextProps){
if(nextProps.message){
this.setState({show: true});
}
}
clearNotification(notificationId){
this.props.clearNotifications(notificationId);
}
componentDidMount(){
console.log('notification mount');
setTimeout(()=>{
console.log('timed out');
this.props.clearNotification(this.props.notificationId);
}, 1000);
}
closeNotification(){
this.props.clearNotification(this.props.notificationId);
this.setState({show: false});
}
render(){
const notificationStyles = () =>{
if (this.props.style === "error"){
return {backgroundColor: 'rgba(152, 5, 19, 0.8)'}
}
return {backgroundColor: 'rgba(8, 130, 101, 0.8)'}
};
if(!this.state.show){
return null;
}
return (
<div className="notification" style={notificationStyles()}>
<div className="notificationCloseButton" onClick={this.closeNotification.bind(this)}>
<i className="material-icons">close</i>
</div>
{this.props.message}
</div>
)
}
};
You've got everything hooked up correctly, but you're missing one key concept for Redux:
With Redux, you never mutate any part of
state
.From the Redux guide:
In
Here is a JSBin with your working notification system!deleteSingleNotification
, you're using .splice to cut the old notification out of your array. Instead, you need to return a brand new array with the unwanted notification missing from it. The easiest way to do this is with the .filter function:So here is why this works: React-Redux's job is to update your components whenever a specific part of your Redux store is changed. It uses a
===
test on every part of the state tree to know if anything changed.When you go and change the state with something like .splice, it checks and thinks nothing is different.
Here's an example to demonstrate the problem:
Instead, React-Redux needs us to do this:
Redux uses this approach for performance reasons. It takes a really long time to loop through a big state tree looking to see if everything is the same. When you keep your tree immutable, only a
===
test is needed and the process gets much easier.