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