Skip to content

Instantly share code, notes, and snippets.

@deepakshrma
Last active May 4, 2022 15:59
Show Gist options
  • Save deepakshrma/f37f1578ab7e6468459d6d329707f0cc to your computer and use it in GitHub Desktop.
Save deepakshrma/f37f1578ab7e6468459d6d329707f0cc to your computer and use it in GitHub Desktop.
Using Context and TypeScript to build an Alert Messenger in React.js
// index.tsx
import { StrictMode } from "react";
import ReactDOMClient from "react-dom/client";
import { createGlobalStyle, ThemeProvider } from "styled-components";
import App from "./App";
import { AlertContextProvider } from "./components/Alert/AlertContextProvider";
const Global = createGlobalStyle`
p,
h1,
h2,
h3,
h4,
h5,
h6 {
padding: 0;
margin: 0%;
}
`;
const rootElement = document.getElementById("root");
const root = ReactDOMClient.createRoot(rootElement as HTMLElement);
const theme = {
colors: {
message_error: "#fdeded",
message_success: "#edf7ed",
},
};
root.render(
<StrictMode>
<ThemeProvider theme={theme}>
<AlertContextProvider>
<Global />
<App />
</AlertContextProvider>
</ThemeProvider>
</StrictMode>
);
// src/components/Alert/AlertContextProvider.tsx
import { createContext, FC, ReactNode } from "react";
interface AlertContext {}
export const AlertMessengerContext = createContext<AlertContext>({} as AlertContext);
export const AlertContextProvider: FC<{
children: ReactNode;
noOfMessages?: number;
autoHideTimeout?: number;
autoHideError?: boolean;
}> = ({ children, noOfMessages = 5, autoHideTimeout = 3000, autoHideError = false }) => {
return(
<AlertMessengerContext.Provider value={{}}>
{children}
</AlertMessengerContext.Provider>
);
};
// src/App.tsx
import { AppContainer, Button } from "./components/Alert/styled.components";
function App() {
const onSuccess = () => {};
const onError = () => {};
return (
<AppContainer>
<Button onClick={onSuccess} variant="success">
Alert Success
</Button>
<Button onClick={onError} variant="error">
Alert Error
</Button>
</AppContainer>
);
}
export default App;
// src/components/Alert/styled.components.tsx
import styled from "styled-components";
export const AppContainer = styled.div`
display: flex;
justify-content: space-evenly;
`;
export const Button = styled.button<{ variant: string }>`
height: 48px;
width: 200px;
background-color: ${(p) => p.theme.colors[`message_${p.variant}`]};
`;
//src/components/Alert/index.tsx
import { MouseEvent, ReactNode } from "react";
import styled from "styled-components";
enum MessageType {
SUCCESS = "success",
ERROR = "error",
}
interface AlertProps {
type?: MessageType;
title: string;
message?: string;
gutterBottom?: boolean;
gutterTop?: boolean;
actionIcon?: ReactNode | boolean;
onAction?: (e?: MouseEvent<HTMLButtonElement>) => void;
}
const noop = (_?: MouseEvent<HTMLButtonElement>) => {};
const AlertContainer = styled.div<Partial<AlertProps>>`
padding: 10px 20px;
background: ${(p) => p.theme.colors[`message_${p.type}`]};
margin-top: ${(p) => p.gutterTop && "10px"};
margin-bottom: ${(p) => p.gutterBottom && "10px"};
min-width: 300px;
height: 80px;
border-radius: 4px;
position: relative;
padding-top: 40px;
`;
const ActionIconContainer = styled.div`
position: absolute;
right: 10px;
top: 10px;
`;
const Alert = ({
type = MessageType.SUCCESS,
title,
message,
gutterBottom,
gutterTop,
actionIcon,
onAction,
}: AlertProps) => {
const ActionIcon =
typeof actionIcon === "boolean" && actionIcon ? <button onClick={onAction ?? noop}>X</button> : actionIcon;
return (
<AlertContainer type={type} gutterBottom={gutterBottom} gutterTop={gutterTop}>
{actionIcon && <ActionIconContainer>{ActionIcon}</ActionIconContainer>}
<h4>{title}</h4>
<p>{message}</p>
</AlertContainer>
);
};
export default Alert;
// src/components/Alert/AlertContextProvider.tsx
import { createContext, FC, ReactNode } from "react";
import styled from "styled-components";
import Alert from ".";
interface AlertContext {}
enum MessageType {
SUCCESS = "success",
ERROR = "error",
}
export const AlertMessengerContext = createContext<AlertContext>({} as AlertContext);
const messages = [
{
id: Date.now(),
type: MessageType.ERROR,
title: "Error Header",
message: "Error Message",
},
{
id: Date.now(),
type: MessageType.SUCCESS,
title: "Success Header",
message: "Success Message",
},
];
const AlertsContainer = styled.div`
background: #fff;
position: absolute;
left: 0;
bottom: 0;
padding: 10px 20px;
overflow-y: auto;
max-height: calc(100vh - 100px);
`;
export const AlertContextProvider: FC<{
children: ReactNode;
noOfMessages?: number;
autoHideTimeout?: number;
autoHideError?: boolean;
}> = ({ children, noOfMessages = 5, autoHideTimeout = 3000, autoHideError = false }) => {
return (
<AlertMessengerContext.Provider value={{}}>
{children}
<AlertsContainer>
{messages.map(({ type, title, message, id }) => (
<Alert
key={`alert__message__${id}`}
type={type}
title={title}
message={message}
gutterTop
actionIcon={type === "error"}
onAction={() => null}
/>
))}
</AlertsContainer>
</AlertMessengerContext.Provider>
);
};
interface Message {
id: string;
type: MessageType;
title: string;
message?: string;
}
enum AlertActionKind {
ADD_SUCCESS = "ADD_SUCCESS",
ADD_ERROR = "ADD_ERROR",
REMOVE_MESSAGE = "REMOVE_MESSAGE",
}
interface AlertAction {
type: AlertActionKind;
payload?: any;
}
interface AlertState {
messages: Message[];
noOfMessages: number;
}
const AlertReducer = (state: AlertState, action: AlertAction) => {
switch (action.type) {
case AlertActionKind.ADD_SUCCESS:
return {
...state,
messages: [{ type: "success", ...action.payload }, ...state.messages].slice(0, state.noOfMessages),
};
case AlertActionKind.ADD_ERROR:
return {
...state,
messages: [{ type: "error", ...action.payload }, ...state.messages].slice(0, state.noOfMessages),
};
case AlertActionKind.REMOVE_MESSAGE:
return { ...state, messages: state.messages.filter((message) => message.id !== action.payload.id) };
}
};
// src/components/Alert/AlertContextProvider.tsx
export const AlertContextProvider: FC<{
children: ReactNode;
noOfMessages?: number;
autoHideTimeout?: number;
autoHideError?: boolean;
}> = ({ children, noOfMessages = 5, autoHideTimeout = 3000, autoHideError = false }) => {
const [state, dispatch] = useReducer(AlertReducer, { messages: [], noOfMessages });
const addSuccessMessage = useCallback(
(data: any) => {
const id = Date.now();
dispatch({ type: AlertActionKind.ADD_SUCCESS, payload: { id, ...data } });
setTimeout(() => {
dispatch({ type: AlertActionKind.REMOVE_MESSAGE, payload: { id } });
}, autoHideTimeout);
},
[dispatch, autoHideTimeout]
);
const addErrorMessage = useCallback(
(data: any) => {
const id = Date.now();
dispatch({ type: AlertActionKind.ADD_ERROR, payload: { id, ...data } });
if (autoHideError) {
setTimeout(() => {
dispatch({ type: AlertActionKind.REMOVE_MESSAGE, payload: { id } });
}, autoHideTimeout);
}
},
[dispatch, autoHideError, autoHideTimeout]
);
const removeMessage = useCallback(
(id: any) => {
dispatch({ type: AlertActionKind.REMOVE_MESSAGE, payload: { id } });
},
[dispatch]
);
return (
<AlertMessengerContext.Provider value={{ state, addSuccessMessage, addErrorMessage, removeMessage }}>
{children}
<AlertsContainer>
{state.messages.map(({ type, title, message, id }) => (
<Alert
key={`alert__message__${id}`}
type={type}
title={title}
message={message}
gutterTop
actionIcon={type === "error"}
onAction={type === "error" ? () => removeMessage(id) : undefined}/>
))}
</AlertsContainer>
</AlertMessengerContext.Provider>
);
};
function App() {
const { addSuccessMessage, addErrorMessage } = useContext(AlertMessengerContext);
const onSuccess = () => {
const time = new Date().toLocaleString();
addSuccessMessage({ title: `Success: ${time}`, message: `This message generated at ${time}` });
};
const onError = () => {
const time = new Date().toLocaleString();
addErrorMessage({ title: `Error: ${time}`, message: `This message generated at ${time}` });
};
return (
<AppContainer>
<Button onClick={onSuccess} variant="success">
Alert Success
</Button>
<Button onClick={onError} variant="error">
Alert Error
</Button>
</AppContainer>
);
}
// src/components/Alert/index.tsx
import { MouseEvent } from "react";
import { AlertProps, MessageType } from "./interfaces";
import { ActionIconContainer, AlertContainer } from "./styled.components";
const noop = (_?: MouseEvent<HTMLButtonElement>) => {};
const Alert = ({
type = MessageType.SUCCESS,
title,
message,
gutterBottom,
gutterTop,
actionIcon,
onAction,
}: AlertProps) => {
const ActionIcon =
typeof actionIcon === "boolean" && actionIcon ? <button onClick={onAction ?? noop}>X</button> : actionIcon;
return (
<AlertContainer type={type} gutterBottom={gutterBottom} gutterTop={gutterTop}>
{actionIcon && <ActionIconContainer>{ActionIcon}</ActionIconContainer>}
<h4>{title}</h4>
<p>{message}</p>
</AlertContainer>
);
};
export default Alert;
// src/components/Alert/AlertContextProvider.tsx
import { createContext, FC, ReactNode, useCallback, useReducer } from "react";
import Alert from ".";
import { AlertAction, AlertActionKind, AlertContext, AlertState } from "./interfaces";
import { AlertsContainer } from "./styled.components";
export const AlertMessengerContext = createContext<AlertContext>({} as AlertContext);
const AlertReducer = (state: AlertState, action: AlertAction) => {
switch (action.type) {
case AlertActionKind.ADD_SUCCESS:
return {
...state,
messages: [{ type: "success", ...action.payload }, ...state.messages].slice(0, state.noOfMessages),
};
case AlertActionKind.ADD_ERROR:
return {
...state,
messages: [{ type: "error", ...action.payload }, ...state.messages].slice(0, state.noOfMessages),
};
case AlertActionKind.REMOVE_MESSAGE:
return { ...state, messages: state.messages.filter((message) => message.id !== action.payload.id) };
}
};
export const AlertContextProvider: FC<{
children: ReactNode;
noOfMessages?: number;
autoHideTimeout?: number;
autoHideError?: boolean;
}> = ({ children, noOfMessages = 5, autoHideTimeout = 3000, autoHideError = false }) => {
const [state, dispatch] = useReducer(AlertReducer, { messages: [], noOfMessages });
const addSuccessMessage = useCallback(
(data: any) => {
const id = Date.now();
dispatch({ type: AlertActionKind.ADD_SUCCESS, payload: { id, ...data } });
setTimeout(() => {
dispatch({ type: AlertActionKind.REMOVE_MESSAGE, payload: { id } });
}, autoHideTimeout);
},
[dispatch, autoHideTimeout]
);
const addErrorMessage = useCallback(
(data: any) => {
const id = Date.now();
dispatch({ type: AlertActionKind.ADD_ERROR, payload: { id, ...data } });
if (autoHideError) {
setTimeout(() => {
dispatch({ type: AlertActionKind.REMOVE_MESSAGE, payload: { id } });
}, autoHideTimeout);
}
},
[dispatch, autoHideError, autoHideTimeout]
);
const removeMessage = useCallback(
(id: any) => {
dispatch({ type: AlertActionKind.REMOVE_MESSAGE, payload: { id } });
},
[dispatch]
);
return (
<AlertMessengerContext.Provider value={{ state, addSuccessMessage, addErrorMessage, removeMessage }}>
{children}
<AlertsContainer>
{state.messages.map(({ type, title, message, id }) => (
<Alert
key={`alert__message__${id}`}
type={type}
title={title}
message={message}
gutterTop
actionIcon={type === "error"}
onAction={type === "error" ? () => removeMessage(id) : undefined}
/>
))}
</AlertsContainer>
</AlertMessengerContext.Provider>
);
};
// src/App.tsx
import { useContext } from "react";
import { AlertMessengerContext } from "./components/Alert/AlertContextProvider";
import { AppContainer, Button } from "./components/Alert/styled.components";
function App() {
const { addSuccessMessage, addErrorMessage } = useContext(AlertMessengerContext);
const onSuccess = () => {
const time = new Date().toLocaleString();
addSuccessMessage({ title: `Success: ${time}`, message: `This message generated at ${time}` });
};
const onError = () => {
const time = new Date().toLocaleString();
addErrorMessage({ title: `Error: ${time}`, message: `This message generated at ${time}` });
};
return (
<AppContainer>
<Button onClick={onSuccess} variant="success">
Alert Success
</Button>
<Button onClick={onError} variant="error">
Alert Error
</Button>
</AppContainer>
);
}
export default App;
// src/index.tsx
import { StrictMode } from "react";
import ReactDOMClient from "react-dom/client";
import { createGlobalStyle, ThemeProvider } from "styled-components";
import App from "./App";
import { AlertContextProvider } from "./components/Alert/AlertContextProvider";
const Global = createGlobalStyle`
p,
h1,
h2,
h3,
h4,
h5,
h6 {
padding: 0;
margin: 0%;
}
`;
const rootElement = document.getElementById("root");
const root = ReactDOMClient.createRoot(rootElement as HTMLElement);
const theme = {
colors: {
message_error: "#fdeded",
message_success: "#edf7ed",
},
};
root.render(
<StrictMode>
<ThemeProvider theme={theme}>
<AlertContextProvider autoHideError>
<Global />
<App />
</AlertContextProvider>
</ThemeProvider>
</StrictMode>
);
// src/components/Alert/interfaces.ts
import { MouseEvent, ReactNode } from "react";
export enum MessageType {
SUCCESS = "success",
ERROR = "error"
};
export interface AlertProps {
type?: MessageType;
title: string;
message?: string;
gutterBottom?: boolean;
gutterTop?: boolean;
actionIcon?: ReactNode | boolean;
onAction?: (e?: MouseEvent<HTMLButtonElement>) => void;
}
export interface Message {
id: string;
type: MessageType;
title: string;
message?: string;
}
export interface AlertState {
messages: Message[];
noOfMessages: number;
}
export enum AlertActionKind {
ADD_SUCCESS = "ADD_SUCCESS",
ADD_ERROR = "ADD_ERROR",
REMOVE_MESSAGE = "REMOVE_MESSAGE",
}
export interface AlertAction {
type: AlertActionKind;
payload?: any;
}
export interface AlertContext {
state: AlertState;
addSuccessMessage: Function;
addErrorMessage: Function;
removeMessage: Function;
}
// src/components/Alert/styled.components.tsx
import styled from "styled-components";
import { AlertProps } from "./interfaces";
export const AppContainer = styled.div`
display: flex;
justify-content: space-evenly;
`;
export const Button = styled.button<{ variant: string }>`
height: 48px;
width: 200px;
background-color: ${(p) => p.theme.colors[`message_${p.variant}`]};
`;
export const AlertsContainer = styled.div`
background: #fff;
position: absolute;
left: 0;
bottom: 0;
padding: 10px 20px;
overflow-y: auto;
max-height: calc(100vh - 100px);
`;
export const AlertContainer = styled.div<Partial<AlertProps>>`
padding: 10px 20px;
background: ${(p) => p.theme.colors[`message_${p.type}`]};
margin-top: ${(p) => p.gutterTop && "10px"};
margin-bottom: ${(p) => p.gutterBottom && "10px"};
min-width: 300px;
height: 80px;
border-radius: 4px;
position: relative;
padding-top: 40px;
`;
export const ActionIconContainer = styled.div`
position: absolute;
right: 10px;
top: 10px;
`;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment