In this article, I will focus on how to only implement Notifaction without third-party libraries

Demand analysis

Referring to the Notification from Ant-Design, we expect the components we implement to have the following capabilities:

  1. There are four styles: Info (blue), Success (green), Warning (orange) and Error (red)
  2. Notification is positioned in the upper right corner of the screen
  3. There are cutscenes for adding and removing. When one notication is removed, the others should slide vertically
  4. You can create notifications that close after 10 seconds
  5. Can be used in JSX<Notification color="success" />Way to call
  6. It can also be called as a function for examplesuccess()

Pre – work

Create-react-app is used to create the project, and CSS Modules are used to write styles. Here’s the code

create-react-app notify --template typescript
Copy the code

Using CSS modules in TS requires a separate configuration, starting with typescript-plugin-CSS-modules

yarn add -D typescript-plugin-css-modules
Copy the code

Then add the following configuration to tsconfig.json

{
  "compilerOptioins": {
    "plugins": [{"name": "typescript-plugin-css-modules"}}}]Copy the code

Finally, add global.d.ts to the SRC /types directory

declare module "*.module.scss" {
  const classes: {[key: string] :string}
  export default classes;
}
Copy the code

Here is the corresponding directory structure


- notify
  - Notification
    - index.module.scss
    - index.tsx
    - times.svg
  - createContainer
    - index.module.scss
    - index.tsx
  - index.ts
Copy the code

Notification Component

Here is the code for the core Notifcation

// notify/Notification/index.js
import React from 'react'
import cn from 'classnames'

import { ReactComponent as Times } from './times.svg'
import styles from './index.module.scss'

export enum Color {
  info = 'info',
  success = 'success',
  warning = 'warning',
  error = 'error',}export interfaceNotificatonProps { color? : Color; }const Notification: React.FC<NotificatonProps> = ({color = Color.info, children}) {
  return (
    <div className={cn([styles.notification, styles[color]])} >
      {children}
      <button className={styles.closeButton}>
        <Times height={16} />
      </button>
    </div>)}export default Notification
Copy the code

Currently this Notification receives two props:

  • Color: Determines the background color of the current Notification. There are four possible values: info, success, Warning, and error
  • Children: React Element that can be rendered in Notification

Here’s how it looks:

.notification {
  max-width: 430px;
  max-height: 200px;
  overflow: hidden;
  padding: 12px 48px 12px 12px;
  z-index: 99;
  font-weight: bold;
  position: relative;
  color: #fff;

  .closeButton {
    position: absolute;
    top: 50%;
    right: 12px;
    height: 16px;
    transform: translateY(-50%);
    background: transparent;
    padding: 0;
    border: none;
    cursor: pointer;
    color: #fff;
    outline: none;
  }

  &:not(:last-child) {
    margin-bottom: 8px;
  }

  &.info {
    background-color: #2196f3;
  }

  &.success {
    background-color: #4caf50;
  }

  &.warning {
    background-color: #ff9800;
  }

  &.error {
    background-color: #f44336; }}Copy the code

Render Notification in DOM

When using Notification in the business, we expect that the notfication will not be affected by the style of the parent element, so we use react portals to exit the DOM tree, but not the React tree. Do you really know react Portals

CreateContainer is the container used to create the Notification and then add it to the body:

// notify/createContainer/index.js
import styles from './index.module.scss'

export default function createContainer() {
  export default function createContainer() :Element {
  const portaId = "notifyContainer";

  let element = document.querySelector(` #${portaId}`)

  if (element) {
    return element;
  }

  element = document.createElement('div');
  element.setAttribute('id', portaId);
  element.className = styles.container;
  document.body.appendChild(element);
  return element;
}
Copy the code

Here is the style of the Container

.container {
  position: fixed;
  top: 16px;
  right: 16px;
}
Copy the code

We then modify the Notification component to render it in the container we created

const container = createContainer();

const Notification: React.FC<NotificatonProps> = ({color = Color.info, children}) = > {
  return createPortal(
    <div className={cn([styles.notification, styles[color]])} >
      {children}
      <button className={styles.closeButton}>
        <Times height={16} />
      </button>
    </div>,
    container
  )
}

Copy the code

Demo

Before writing the demo, expose the Notification and type definitions as follows:

export Notification, { Color } from './Notification'
Copy the code

Next, write a demo to look at the notificaiton of the various modes

import React, { useState } from 'react';
import { Notification, Color } from './notify';
import './App.css';

interface NoteInterface {
  id: number; color? : Color; }function App() {
  const [notifications, setNotifications] = useState<NoteInterface[]>([]);
  const createNotification = (color: Color) = > {
    setNotifications([
      ...notifications,
      {
        color,
        id: notifications.length,
      },
    ]);
  };

  return (
    <div className="App">
      <h1>Notification Demo</h1>
      <button onClick={()= > createNotification(Color.info)}>Info</button>
      <button onClick={()= > createNotification(Color.success)}>Success</button>
      <button onClick={()= > createNotification(Color.warning)}>Warning</button>
      <button onClick={()= > createNotification(Color.error)}>Error</button>
      {notifications.map(({ id, color }) => {
        <Notification key={id} color={color} />;
      })}
    </div>
  );
}

export default App;
Copy the code

Notification for four different topics is shown in the demo

Close the Notification

Next add an event to the close button for Notification to actively disable notification:

interfaceNotificatonProps { color? : Color,onDelete: Function,}const Notification: React.FC<NotificatonProps> = ({ color = Color.info, children, onDelete }) = >
  createPortal(
    <div className={cn([styles.notification, styles[color]])} >
      {children}
      <button onClick={()= > onDelete()} className={styles.closeButton}>
        <Times height={16} />
      </button>
    </div>,
    container
  );

export default Notification;
Copy the code

Then add onDelete to app.tsx to turn off notification:

function App() {
  const [notifications, setNotifications] = useState<NoteInterface[]>([]);
  const createNotification = (color: Color) = > {
    setNotifications([
      ...notifications,
      {
        color,
        id: notifications.length,
      },
    ]);
  };

  const deleteNotification = (id: number) = >
    setNotifications(notifications.filter(notification= >notification.id ! == id));return (
    <div className="App">
      <h1>Notification Demo</h1>
      <button onClick={()= > createNotification(Color.info)}>Info</button>
      <button onClick={()= > createNotification(Color.success)}>Success</button>
      <button onClick={()= > createNotification(Color.warning)}>Warning</button>
      <button onClick={()= > createNotification(Color.error)}>Error</button>
      {notifications.map(({ id, color }) => (
        <Notification onDelete={()= > deleteNotification(id)} key={id} color={color}>
          This is Notification
        </Notification>
      ))}
    </div>
  );
}
Copy the code

Add a fade in and out animation

In the above notification, the dynamic effect is very rigid whether it is added or removed. Let’s add a fade in and out animation to make the add and remove process smoother for the user experience.

In the Add animation, move the component position from translateX(100%) to translateX(0%).

Here is the animation code to use Keyframes:

// notify/Notification/index.module.scss
@keyframes slideIn {
  from {
    transform: translateX(100%)}to {
    transform: translateX(0)}}.notification{&.slideIn {
    animation-name: slideIn;
    animation-duration: 0.3 s;
    animation-timing-function: ease-in-out; }}Copy the code

Removing the animation is a little tricky, because if you delete the DOM immediately after closing, it won’t work in Transition. So when you hit delete, add 300ms delay, element from translateX(0%) to translateX(150%)

Below is the effect CSS for removing the animation

// notify/Notification/index.module.scss
.notification {
  transition: transform .3s ease-out;

  &.slideOut {
    transform: translateX(150%);
    flex: 0; }}Copy the code

To implement the closing phase, we need to add a state value isClosing in the component, which is false by default. When we click the close button, set isClosing to true and call onDelete after the animation is over

Note that we can only use slideIn animations when there is no shutdown phase, and slideOut animations when there is shutdown

const Notification: React.FC<NotificatonProps> = ({ color = Color.info, autoClose = false, children, onDelete }) = > {
  const [isClosing, setIsClosing] = useState(false);
  useEffect(() = > {
    if (isClosing) {
      const timerId = setTimeout(() = > setIsClosing(true), timeToDelete);
      return() :void= > {
        clearTimeout(timerId);
      };
    }
  }, [isClosing, onDelete]);
  return createPortal(
    <div
      className={cn([
        styles.notification.styles[color], {[styles.slideIn]: !isClosing[styles.slideOut]: isClosing,})} >
      {children}
      <button type="button" onClick={() : void= > setIsClosing(true)} className={styles.closeButton}>
        <Times height={16} />
      </button>
    </div>,
    container
  );
};
Copy the code

Remove the animation

When a notification is removed, the next notification needs to be moved to the location of the deleted notification

To make the process smoother, add a container to the component during the close phase to make the contraction smoother

const Notification: React.FC<NotificatonProps> = ({ color = Color.info, autoClose = false, children, onDelete }) = > {
  const [isClosing, setIsClosing] = useState(false);
  useEffect(() = > {
    if (isClosing) {
      const timerId = setTimeout(() = > setIsClosing(true), timeToDelete);
      return () = > {
        clearTimeout(timerId);
      };
    }
  }, [isClosing, onDelete]);
  return createPortal(
    <div
      className={cn([
        styles.container, {[styles.shrink]: isClosing,})} >
      <div
        className={cn([
          styles.notification.styles[color], {[styles.slideIn]: !isClosing[styles.slideOut]: isClosing,})} >
        {children}
        <button type="button" onClick={() : void= > setIsClosing(true)} className={styles.closeButton}>
          <Times height={16} />
        </button>
      </div>
    </div>,
    container
  );
};
Copy the code

The container defaults to Max height of 200px and automatically shrinks to 0px during the remove phase. Then add some distance between the different containers:

.container {
  overflow: hidden;
  max-height: 200px;
  transition: max-height .3s ease-out;

  &:not(:last-child) {
    margin-bottom: 8px;
  }

  &.shrink {
    max-height: 0; }}Copy the code

Add automatic shutdown

Add props for autoClose, use useEffect to listen for changes to the autoClose, and change isClosing to true for 10 seconds when the value changes

const timeToClose = 10 * 1000;
 useEffect(() = > {
    if (autoClose) {
      const timerId = setTimeout(() = > setIsClosing(true), timeToClose);
      return () = > {
        clearTimeout(timerId);
      };
    }
  }, [autoClose]);
Copy the code

Then test autoClose in demo

function App() {
  const [notifications, setNotifications] = React.useState([]);

  const createNotification = (color) = >
    setNotifications([...notifications, { color, id: notifications.length }]);

  const deleteNotification = (id) = >
    setNotifications(
      notifications.filter((notification) = >notification.id ! == id) );return (
    <div className="App">
      <h1>Notification Demo</h1>
      <button onClick={()= > createNotification(Color.info)}>Info</button>
      <button onClick={()= > createNotification(Color.success)}>Success</button>
      <button onClick={()= > createNotification(Color.warning)}>Warning</button>
      <button onClick={()= > createNotification(Color.error)}>Error</button>
      {notifications.map(({ id, color }) => (
        <Notification
          key={id}
          onDelete={()= > deleteNotification(id)}
          color={color}
          autoClose={true}
        >
          This is a notification!
        </Notification>
      ))}
    </div>
  );
}
Copy the code

Call Notification as a function

Let’s add a function to call notificaiton, such as success() or error()

To achieve this effect, you still need to render the component in the DOM, but you need to re-wrap the Notification.

Let’s create notification Manager to meet this requirement

import React, { useEffect } from 'react';

import Notification, { NotificatonProps } from './notification';

interface Props {
  setNotify(fn: (params: NotificatonProps) = > void) :void;
}

export default function NotificationsManager(props: Props) {
  const { setNotify } = props;
  const [notifications, setNotifications] = React.useState([]);

  const createNotification = ({ color, autoClose, children }): void= > {
    setNotifications(prevNotifications= > [
      ...prevNotifications,
      {
        children,
        color,
        autoClose,
        id: prevNotifications.length,
      },
    ]);
  };

  useEffect(() = > {
    setNotify(({ color, autoClose, children }) = > createNotification({ color, autoClose, children }));
  }, [setNotify]);

  const deleteNotification = (id: number) :void= > {
    const filteredNotifications = notifications.filter((_, index) = >id ! == index, []); setNotifications(filteredNotifications); };return (
    <template>{notifications.map(({ id, ... props }, index) => (<Notification key={id} onDelete={() : void= >deleteNotification(index)} {... props} /> ))}</template>
  );
}

Copy the code

In the above code, we used the Notifications array to manage notification and iterate through several sets of Notifications generation components. Add and delete are actually operations on this array.

This function takes a function setNotify, and the input parameter to this function is a function that adds a Notification component into something like createNotification. Just expose this when calling the notification method imperative, and you can implement the functionality.

Here’s the code

// notify/index.tsx

import React from 'react';
import ReactDOM from 'react-dom';

import NotificationsManager from './NotificationsManager';
import Notification, { Color } from './notification';
import createContainer from './createContainer';

const containerElement = createContainer();

let notify;

ReactDOM.render(
  <NotificationsManager
    setNotify={(notifyFn): void= >{ notify = notifyFn; }} / >,
  containerElement
);

export { Notification, Color };

export function info(children, autoClose) : () = >void {
  return notify({
    color: Color.info,
    children,
    autoClose,
  });
}
// ...
Copy the code

Notification can be called as follows:

info('message'.true)
Copy the code

Welcome to pay attention to “front-end learn well”, front-end learning do not get lost or add wechat SSDWBObo, exchange and learn together