Material UI | How to easily setup popup notifications

749 Words

Using Material UI and React Context to set up global notification system.

Need for global notification system#

In your application, you want have good user experience and let the user know if the actions/clicks have resulted in success or failure. We should also show warning and information messages to guide user through the flows.

It is of course possible to display a local popup component wherever needed, but it quickly becomes a bloat and irritating to manage. It would be really cool to just set up the whole system once and trigger it from wherever from the app to get consistent experience.

A global system runs like plug-and-play and is isolated from rest of the local logic.

Summary#

We will need the following -

  • An alert component
  • A place to trigger one or more instances and remove ( unmount) them after a set time period
  • A way to keep track of notifications list and add new notifications.

Create notifications component#

Let’s start with a basic Alert component. It will receive a title and text as props, and also a type of alert as well. We will also create a handleClose function.

We will now need a wrapper component which maps through the list of notifications and feeds each Alert correctly.

const useStyles = makeStyles((theme) => ({
  root: {
    position: 'fixed',
    right: theme.spacing(2),
    bottom: theme.spacing(2),
    zIndex: 2000, // Make sure it pops up on top of everything
  },
  alert: {
    marginBottom: theme.spacing(2),
  },
}))

const AlertProvider = ({ duration = 1000, alert, actions }) => {
  const classes = useStyles()
  const handleClose = () => {
    actions.removeAlert(alert)
  }
  useEffect(() => {
    // run handleClose once duration is finished.
    const timer = setTimeout(handleClose, duration)
    return function () {
      clearTimeout(timer)
    }
  }, [])
  return (
    <MuiAlert
      className={classes.alert}
      onClose={handleClose}
      id="alert"
      elevation={6}
      variant="filled"
      severity={alert.type}
    >
      <AlertTitle>{alert.title}</AlertTitle>
      {alert.text}
    </MuiAlert>
  )
}

Decide on a global state management system -> Redux or Context#

In this article, I will be going with Context and use a global provider to handle state management. We could have used Redux just as well to set it up and keep the rest as it is.

Create Context provider#

Let’s set up our global context provider for alerts management

import React, { createContext, useReducer } from 'react'

const AlertContext = createContext([{}, () => {}])
AlertContext.displayName = 'AlertContext'

const initialState = {
  alerts: [],
}

let reducer = (state, action) => {
  let newAlerts, element
  switch (action.type) {
    case 'ADD_ALERT':
      newAlerts = [...state.alerts, action.payload]
      return { ...state, alerts: newAlerts }
    case 'REMOVE_ALERT':
      element = state.alerts.filter((e) => e.id !== action.payload.id)
      return { alerts: element }
    default:
      console.log('No valid action: ' + action.type)
      return state
  }
}

function AlertContextProvider(props) {
  let [state, dispatch] = useReducer(reducer, initialState)

  const actions = {
    addAlert: (payload) => {
      dispatch({ type: 'ADD_ALERT', payload })
    },
    removeAlert: (payload) => {
      dispatch({ type: 'REMOVE_ALERT', payload })
    },
  }

  return (
    <AlertContext.Provider value={{ state: state, actions: actions }}>
      {props.children}
    </AlertContext.Provider>
  )
}

export { AlertContext, AlertContextProvider }

Primarily, the provider handles adding an alert, and removing it.

Wrap app.js in provider#

Once we have the basic stuff scaffolded, time to wire them up with rest of the app.

First, make it available for the whole app by adding it on top of the tree.

import React from 'react'
import ReactDOM from 'react-dom'
import { AlertContextProvider } from './AlertProvider'

import App from './App'

const rootElement = document.getElementById('root')
ReactDOM.render(
  <React.StrictMode>
    <AlertContextProvider>
      <App />
    </AlertContextProvider>
  </React.StrictMode>,
  rootElement
)

Set up dispatch on trigger#

The very last thing we need to do is to actually trigger the alert somehow.

import React, { useContext } from 'react'
import './styles.css'
import { Button, Card } from '@material-ui/core'
import { AlertContext } from './AlertProvider'
import Notification from './Notification'

export default function App() {
  const { actions } = useContext(AlertContext)
  const alertTypes = ['success', 'warning', 'info', 'error']
  // Select a random type for example app
  const selectedType = alertTypes[Math.floor(Math.random() * alertTypes.length)]
  const triggerNotification = () => {
    actions.addAlert({
      text: 'Notification text',
      title: ` Clicked on ${selectedType}`,
      type: selectedType,
      id: Date.now(), // lazy way of adding unique id per alert
    })
  }
  return (
    <div className="App">
      <Card>
        <Button onClick={triggerNotification}>
          <h1>Click me</h1>
        </Button>
      </Card>
      <Notification />
    </div>
  )
}

Finally, here is a working example to show how it actually works.




If you enjoyed this blog post, please