Some days ago when I was working on a project, I needed to encapsulate a Toast
component. I remembered that the library I used before can be used globally as long as it is introduced in the entry file, which is still very convenient, so I took this opportunity to implement it as well. This is also a practical use of forwardRef
, useImperativeHanle
and useContext
.
The first, usingforwardRef
anduseImperativeHanle
One is to use it like the react-toastify
library, placing ToastContainer
at the entry point and then using toast("Wow so easy!")
anywhere in the code with a prompt
import React from 'react';
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
function App(){
const notify = () => toast("Wow so easy!");
return (
<div>
<button onClick={notify}>Notify!</button>
<ToastContainer />
</div>
);
}
- The second, using
useContext
Place ToastProvider
at the entry point and then use const { show } = useToast()
anywhere in the code to get a hint. I forget what library.
In the article, we’ll use message
from antd
to simulate the Toast
component that we wrote ourselves.
Let’s go over the basic use of forwardRef
, useImperativeHanle
and useContext
.
forwardRef
Basic use of useImperativeHandle
forwardRef
and useImperativeHandle
, which are often used together to expose specific methods or properties of a child component in the parent component.
forwardRef
, which allows you to forward ref
from the parent component to a DOM node or other React component in the child component. This way, the parent component can access the child component’s reference and manipulate it directly.
useImperativeHandle
is a custom Hook that allows you to customize the ref
value exposed to the parent component via forwardRef
. You can specify which are exposed instead of exposing the entire DOM node or component instance directly.
Here is a simple example
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
const ChildComponent = forwardRef((props, ref) => {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
}));
return <input ref={inputRef} {...props} />;
});
const ParentComponent = () => {
const childRef = useRef(null);
const handleClick = () => {
childRef.current.focus();
};
return (
<div>
<ChildComponent ref={childRef} />
<button onClick={handleClick}>Focus Child Input</button>
</div>
);
};
export default ParentComponent;
Wrapping the global Toast with forwardRef
and useImperativeHanle
assembly
import React, { createRef, forwardRef, useImperativeHandle } from 'react';
import { Button, message } from 'antd';
const Toast = forwardRef((props, ref) => {
const [messageApi, contextHolder] = message.useMessage();
useImperativeHandle(ref, () => ({
show: (msg: string) => {
messageApi.info(msg);
}
}));
return <>
{contextHolder}
</>
})
const ToastRef = createRef<{ show: (msg: string) => {} }>();
export const ToastContain = () => {
return <Toast ref={ToastRef} />
}
export const showToast = (msg: string) => {
if (ToastRef.current) {
ToastRef.current.show(msg)
}
};
Introducing in the portal
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import router from '@/router/index'
import reportWebVitals from './reportWebVitals';
import { RouterProvider } from 'react-router-dom';
import ErrorBoundary from './ErrorBoundary';
import { ToastContain } from './components/Toast';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<ToastContain />
<RouterProvider router={router} fallbackElement={<div>ing...</div>} />
</React.StrictMode>
);
reportWebVitals();
Then you can use the showToast
method globally
import React from 'react';
import { showToast } from '../../../components/Toast';
export default function Index() {
return <>
<div
onClick={() => {
showToast('sadasds')
}}
>
pop-up window
</div>
</>
}
useContext
basic use
useContext
Used to access Context
defined at a level in the component tree. Context
Provides a way to share values between components without having to explicitly pass props
through each level of the component tree.
- Creating a Context
First, you need to create a Context
object. This can be done by calling React.createContext
. You can also provide a parameter for a default value that will be used if Provider
of Context
is not found in the component tree.
import React from 'react';
const MyContext = React.createContext('defaultValue');
- Providing Context
You need to provide this Context
somewhere in the component tree. This is usually done at the top level of the component, by using the MyContext.Provider
component and passing a value
prop
import React from 'react';
import MyComponent from './MyComponent';
import { MyContext } from './MyContext';
function App() {
return (
<MyContext.Provider value="Hello from Context">
<MyComponent />
</MyContext.Provider>
);
}
export default App;
- utilization
useContext
In components that need access to Context
, you can use the useContext
Hook to get the current value of Context
import React, { useContext } from 'react';
import { MyContext } from './MyContext';
function MyComponent() {
const contextValue = useContext(MyContext);
return <p>Context value: {contextValue}</p>;
}
export default MyComponent;
Use useContext
to encapsulate the global Toast
assembly
import React, { createContext, useCallback, useContext, useState } from 'react';
import { Button, message } from 'antd';
const ToastContext = createContext<any>(null);
export const ToastProvider = ({ children }: any) => {
const [messageApi, contextHolder] = message.useMessage();
const show = useCallback((msg: string) => {
messageApi.info(msg);
}, [messageApi]);
return (
<ToastContext.Provider value={{ show }}>
{children}
{contextHolder}
</ToastContext.Provider>
);
};
export const useToast = () => {
const context = useContext(ToastContext);
return context;
};
Use at the entrance
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import router from '@/router/index'
import reportWebVitals from './reportWebVitals';
import { RouterProvider } from 'react-router-dom';
import ErrorBoundary from './ErrorBoundary';
import { ToastProvider } from './components/ToastOne';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<ToastProvider>
<RouterProvider router={router} fallbackElement={<div>ing...</div>} />
</ToastProvider>
</React.StrictMode>
);
It can then be used globally via useToast
import React from 'react';
import { useToast } from '../../../components/ToastOne';
export default function Index() {
const { show } = useToast()
return <>
<div
onClick={() => {
show('guiyu')
}}
>
Click
</div>
</>
}