Create that Component #3

This is a series where I design and code various common UI components with React and <place CSS solution here>.

Teemu Taskula
Jun 7, 2017 · 6 min read

In this episode we are going to create a toaster component very similar to the one present in macOS. Let’s take a look at the basic version of the component down below:

And here’s some more toasts for good measure:

Pretty neat huh? We have some sweet animation goodness happening in conjunction with the toast being both auto-cleared without any user interaction and manually removed by clicking the x-icon.

So, before we begin I’m going to shortly mention the dependencies we are going to use:

We are using Redux simply because I’m most comfortable with it plus it makes adding toasts very simple via actions. Since we are dealing with components entering (mounting) and leaving (unmounting) and React does not have any built-in way to animate these situations we are going to use react-transition-group to do the job. Lastly, I’m using styled-components for component styling since I used it in the two previous episodes (#1 and #2) and I haven’t found any valid reason to change it to something else because it’s so god damn awesome!

By the way, they’ve released v2.0 and new docs recently — you should definitely check them out 💅

Okay, enough smooth talk, let’s talk business.

Toaster container

Since we are relatively decent lads we are going to follow the good practice of separating presentational and container components. That means that our Toast container is responsible for connecting to the Redux store, handling toast auto-clearing and mounting/unmounting animations using CSSTransitionGroup from react-transition-group. All Redux related things are imported from a ducks file that we will talk more about later. The container has a simple componentWillReceiveProps lifecycle method that starts a timeout that automatically clears a toast after given/default timeout every time a new toast is added. Note that I’m using basic CSS here to define the animation styles but I also include the SASS way of doing the same in the comments.

Here’s the code for the container component:

Now that you’ve read through the container component code let’s talk about how the CSSTransitionGroup works. Every time a new toast component is added or removed inside CSSTransitionGroup it applies the CSS classes defined in transitionName prop object in a certain order with the given timeouts. So when a toast component is mounted it first adds either the appear (initial mount) or the enter className to the element and then in the next tick it adds the enterActive/appearActive className to actually start the wanted animation. Then when the component should be unmounted CSSTransitionGroup adds the leave + leaveActive classNames and finally unmounts the component after given timeout when it can assume that the animation is finished. Note that the timeouts given as props and the ones in CSS should match to keep this process in sync.

Here are the CSS styles I used to make the fade-in and slide+fade-out animations:

State management

Before we take a look at the ducks file I mentioned earlier I should probably explain what the heck is this ducks thing!? If you have used Redux before you have probably structured your actions, action types, reducers, selectors etc in separate folders by type. This approach is ok when your project is a basic ToDo app but as soon as you start to tackle some bigger fish things will get quite hairy really fast. The problem is every time you want to add a new thing or change something related to Redux in your app you need to modify at least three different files to get the job done — which is time consuming and gets repetitive fast. A good alternative to this mess is the ducks pattern introduced by Erik Rasmussen. It combines all Redux related things into one ducks file by simultaneously enforcing a few rules such as: the default export has to be a function called reducer() and all action creators must be exported. You should check out the rest of the rules in the Github repo linked above.

So, here comes our ducks 🦆

We are using immutability-helper to make immutable state changes less painful, and redux-actions to reduce the boilerplate when creating actions. Note that these packages are in no means necessary, but I personally like to use them to make my life a bit easier.

There are a few things to note here. In the reducer function we handle a new toast by attaching an id to it so that it can be easily filtered when the same toast gets removed. We also export two simple actions addToast and removeToast, and some shortcut actions to all the different toast types: success, warning and error.

Here’s how they look:

Success / Warning / Error toasts

Presentational component

The final thing to look at is the presentational component for a single toast. There’s nothing too fancy happening here other than all the styled-components goodness. We define some color variables and a few helper functions that we use inside our styled components to dynamically apply styles based on passed props.

Usage

So, how do you use this Toaster component in your app?

One way would be to put all the files inside, let’s say, a toast folder and rename the toast.index.js to just index.js so that it can be imported more easily by referencing just the folder it’s in and rename toast.ducks.js to just ducks.js to be more concise. The presentational component(s) would go under components folder which is what the container component assumes.

To clarify, take a look at the final folder structure below.

/toast
├── ducks.js
├── index.js
└── /components
├── Toast.js
└── Toast.css

Then in your own component you can import the Toaster from the root of the toast folder and all actions you need from the ducks file.

import Toaster from './<PATH_HERE>/toast';// Then add toaster somewhere in the root of your apprender() {
return (
<div>
...your other components...
<Toaster />
</div>
);
}
// Use actions somewhere appropriateimport {
addToast,
addSuccess,
addWarning,
addError,
} from './<PATH_HERE>/toast/ducks';

You also need to add the reducer from the ducks file to your combineReducers() when you create your store, like so:

import toastReducer from './<PATH_HERE>/toast/ducks';combineReducers({
...your reducers...
toast: toastReducer,
})

Voilà, your toast is ready to be served sir.

Conclusion

Making a Toaster component that uses Redux is somewhat straightforward, but there are many improvements we could make to our implementation:

import Toaster, { addToast, ... } from './<PATH_HERE>/toast';

However, I didn’t want to make this episode too long by implementing all these features but instead I’m leaving it to you, the reader, to go and implement them yourself if you find this episode useful and think that you would like to try out my ReduxToaster 🍞

That’s all, see you in the next episode!

We are hiring!

Do you want to build exciting and interesting apps with modern web technologies? Check out our open positions and join us at Taito United!

Can’t find positions directly suitable for you? Feeling adventurous? Send your resume and an open application to jobs@taitounited.fi — we are more than happy to hear you out and see what you got cooking 🍳

TaitoUnited

Software Development, Ohjelmistokehitystä