Article summary
Getting UI elements for free is a good feeling. When it comes to popup notifications, other frameworks provide these components for free. For instance, Rails has flashes and Android has snackbars. React is a moving target, so pulling in external libraries can help simplify situations, but it may prevent extensibility later.
One solution for popup notifications is to do it yourself. For React-Redux, this process can involve attaching messages to the Redux state that can be presented temporarily by the UI.
The goal of the in-house solution could include:
- A stateless handling of the UI
- A component-agnostic approach
- A dependency that can be accessed by any calling component
Component
A simple toast or popup component might be a banner that resides in the DOM. Redux can help by refreshing whatever properties are passed to the store, allowing actions to create and dismiss toasts at any time.
const Toast = ({ toast, hideToast }: ToastProps) => (
<div className={ `toast ${toast.className}` }>
<div className="message">{ toast.text }</div>
<div onClick={ hideToast } className="close">x</div>
</div>
);
For a larger application, the toast could live at the root of a parent element. Luckily, the toast component can be presented anywhere in the DOM. In this case, the “Home” element will hold and display toast messages.
Container
The Redux container is necessary to separate state and the React toast components. For this example, the container maps “show” and “dismiss” to the React properties. When mapped from Redux, the toast could also be sent because of an API call to show a success or error messages.
const mapDispatchToProps = (dispatch: any): DispatchProps => ({
hideToast: (toast) => dispatch(hideToast(toast))
});
Now for a word of caution. The “toasts” container must be separate from any other layout container. This is for the sake of data flow from the Redux Store. If the toasts were a part of a layout container, the props from the parent component could be refreshed whenever a toast is popped.
Action and Reducer
Actions can be simple or complex, but the primary actions for generating toasts are to show and hide them. Showing a toast will require creating a “show” action, whereas hiding a toast should create a “hide” event. A hide event can be initiated by the user or dismissed automatically by the hide action itself.
const showToast = (text: string, className: string) => ({
type: SHOW_TOAST,
toast: {
timestamp: Date.now(),
text,
className
}
});
The reducer has the effect of adding or removing a toast accordingly. By using the spread operator, we can cut down on the need to modify our existing array of toasts. In turn, the state will reflect the currently displayed toasts.
export const uiReducers = (state: UiState = DEFAULT_STATE, action: ActionType): UiState => {
switch (action.type) {
// more here....
case SHOW_TOAST:
return {
...state,
toasts: [...state.toasts, action.toast]
}
}
}
Result
Baking your own toast results in a customizable solution to an age-old UI element. The dependency for the component exists only on importing it to display. The cost for calling components is to map a “show” or “hide” dispatch.
Full source code is available on GitHub. Leave a comment if you’ve built similar components or have thoughts on improving this one!
Good summary of the basic approach. There’s actually over a dozen existing “React+Redux notification” libs out there [0], but when I needed one for my own app, I saw how simple the approach is and just wrote one myself. The same concept can be used for modal dialogs as well [1].
Skimming through the code, it looks very good overall. I’ve only got two minor nitpicks. First, I heavily encourage people to use the object shorthand syntax for `mapDispatch` instead of writing an actual `mapDispatch` function. So, `ToastsContainer` could just have `export default connect(mapState, {hideToast})(Toasts)`. Also, the use of `_.reject` in the reducer seems like a bit of overkill given that you could do `state.toasts.filter()` and invert the condition.
Good article, and I’ll be adding it to my links list in the next update!
[0] https://github.com/markerikson/redux-ecosystem-links/blob/master/widgets.md#notifications
[1] https://github.com/markerikson/react-redux-links/blob/master/redux-techniques.md#ui-and-widget-implementations
Thanks for the input! The shorthand for the container is new to me, so it’s great advice moving forward.
I think one shortcoming for the notifications libraries I came across initially was the use of window which reached outside a comfortable scope, especially when dealing with React that tries to control scope.