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> ) } };
Any time any piece of the Redux state is updated, our Component will re-render, regardless of whether the store update was related to our component or not.
There are four reasons why a component would re-render itself: state changes, parent (or children) re-renders, context changes, and hooks changes. There is also a big myth: that re-renders happen when the component's props change.
React components automatically re-render whenever there is a change in their state or props. A simple update of the state, from anywhere in the code, causes all the User Interface (UI) elements to be re-rendered automatically.
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.
You've got everything hooked up correctly, but you're missing one key concept for Redux:
state
.From the Redux guide:
Things you should never do inside a reducer:
- Mutate its arguments;
- Perform side effects like API calls and routing transitions;
- Call non-pure functions, e.g. Date.now() or Math.random().
In 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:
function deleteSingleNotification(notifications, notificationId){ return notifications.filter (notification => { return notification.id !== notificationId } }
Here is a JSBin with your working notification system! 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:
var array = [ 'a', 'b', 'c' ] var oldArray = array array.splice (1, 1) // cut out 'b' oldArray === array // => true! Both arrays were changed by using .splice, // so React-Redux *doesn't* update anything
Instead, React-Redux needs us to do this:
var array = [ 'a', 'b', 'c' ] var oldArray = array array = array.filter (item, index => index !== 1) // new array without 'b' oldArray === array // false. That part of your state has changed, so your // componenet is re-rendered
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.
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