It is not easy to use dialog boxes in React, mainly because:
- The dialog box needs to be declared in the parent component to control whether it is displayed in the child component.
- Passing parameters to the dialog box can only be passed in by props, which means that all state management must be in higher-order components. In fact, the parameters of this dialog box are only maintained in the child components. At this time, we need to use custom events to pass the parameters back
The essence of these problems is: how to manage dialog boxes in a unified way, so that the business logic related to the dialog boxes is more modular and decoupled from other business logic.
The following method is just a summary of experience, not the only or the best implementation:
Idea: Use global state to manage all dialog boxes
The dialog box is essentially a window independent of other interfaces, used to complete a separate function.
Therefore, defining a dialog box, positioning is equivalent to defining a page with a unique URL path. It’s just that the former is implemented by the pop-up layer, while the latter is the page switching.
The pop-up process of the dialog UI is very similar to the switching of the page URL. So we can define a globally unique ID for each dialog box, and then use this ID to display or hide a dialog box, and pass parameters to it.
Try to design an API to do global management of dialog boxes
Assuming that the dialog box we implemented is NiceModal, then our goal is to use it as follows:
const UserInfoModal = ( 'user-info-modal', RealUserInfoModal ) // Create a hook like useNiceModal to get the operation object of the dialog box with a certain idconst modal = useNiceModal('user-info-modal') // Display a dialog box and be able to pass parameters to it(args) ()
It can be seen that if there is such an API, no matter which level of components, as long as you know the ID of a certain Modal, you can use these dialog boxes in a unified way, and you no longer need to consider which level of components to define them.
Implementation: Create NiceModal components and related APIs
Create an action creator and reducer that handles all dialog boxes
function showModal(modalId, args) { return { type: "nice-modal/show", payload: { modalId, args } } } function hideModal(modalId, force) { return { type: "nice-modal/hide", payload: { modalId, force } } }
const modalReducer = (state = { hiding: {} }, action) { switch () { case "nice-modal/show": const {modalId, args} = return { ...state, // If there is a status corresponding to modalId (i.e. args), this dialog box will be displayed // As long as there are parameters, the dialog box should be displayed. If args is not passed, use the default value true in reducer [modalId]: args || true, // Define a hiding state that is used to handle dialog box closing animations hiding: { ..., [modalId]: false, } } case "nice-modal/hide": const { modalId, force: boolean } = // The dialog box is actually removed only when force is used, otherwise it is hidden hiding return ? { ...state, [modalId]: false, hiding: { [modalId]: false } } : { ...state, hiding: { [modalId]: true } } default: return state } }
The main idea of this code is to store the status and parameters of each dialog box through the Redux store. Two actions are designed here, showing and hiding the dialog box respectively.
It is particularly noteworthy that a state like hiding is added here to handle the dialog box closing process animation.
According to the order of use, first implement createNiceModal.
Use container mode to return null directly when the dialog is not visible, so that nothing is rendered.
Make sure that even if 100 dialogs are defined on the page, performance will not be affected.
createNiceModal = (modalId, Comp) => { return (props) => { const { visible, args } = useNiceModal(modalId) if (!visible) return null return <Comp {...args} {...props} /> } } // useconst MyModal = createNiceModal('my-modal', () => { return ( <NiceModal title="Nice modal"> Hello NiceModal </NiceModal> ) })
Implement useNiceModal, encapsulate some logic according to id.
Make Redux's action more convenient to use, encapsulate the operation of the store internally, thereby realizing logical reuse of dialog box state management.
const modalCallbacks = {} const useNiceModal = (modalId) => { const dispatch = useDispatch() // Encapsulate Redux action to display dialog boxes const show = useCallback( (args) => { dispatch(showModal(modalId, args)) }, [dispatch, modalId] ) // Encapsulate Redux action to hide dialog boxes (force: boolean) const hide = useCallback( (force) => { dispatch(hideModal(modalId, force)) }, [dispatch, modalId] ) const args = useSelector((s) => s[modalId]) const hiding = useSelector((s) => [modalId]) // As long as there are parameters, the dialog box should be displayed. If args is not passed, use the default value true in reducer return { args, hiding, visible: !!args, show, hide } }
In this way, we implement a global dialogue management framework like NiceModal.
Use this way:
import { Button } from 'antd' import NiceModal, { createNiceModal, useNiceModal } from "./NiceModal" const MyModal = createNiceModal("my-modal", () => { return ( <NiceModal title="Nice Modal"> Hello World </NiceModal> ) }) function MyModalExample() { const modal = useNiceModal("my-modal") return ( <> <Button type="primary" onClick={() => ()}> Show my modal </Button> <MyModal /> </> ) }
Process the return value of the dialog box
If the two UI modes of dialog boxes and pages are basically the same, they are both independent windows to complete independent logic. However, in terms of user interaction, there are certain differences:
- The dialog box may need to return the value to the caller
- Page switching generally does not care about what the result of page execution is
Based on the NiceModal implementation logic above, now consider how to get the caller to get the return value.
We can regard the user's operations in the dialog box as an asynchronous operation logic. After the user completes the operation of the content in the dialog box, he thinks that the asynchronous operation logic has been completed. Therefore, we can use Promise to accomplish such logic.
Then, the API we want to implement is as follows:
const modal = useNiceModal('my-modal') // Implement a promise API to handle return values(args).then(res => {})
In fact, it is not difficult to implement such a mechanism, which is to provide such a method in the implementation of the useNiceModal Hook, which can resolve the promise returned.
The core idea of the code is to connect the two functions show and resolve through Promise. Therefore, the call positions of the two functions are different, so we use a local temporary variable to store the resolve callback function.
// Use an object to cache promise resolve callback functionconst modalCallbacks = {}; export const useNiceModal = (modalId) => { const dispatch = useDispatch(); const show = useCallback( (args) => { return new Promise((resolve) => { // When the dialog box is displayed, return promise and temporarily save the resolve method modalCallbacks[modalId] = resolve; dispatch(showModal(modalId, args)); }); }, [dispatch, modalId], ); const resolve = useCallback( (args) => { if (modalCallbacks[modalId]) { // If the resolve callback function exists, then call it modalCallbacks[modalId](args); // Make sure to resolve only once delete modalCallbacks[modalId]; } }, [modalId], ); // Other logic... // Use resolve as part of the return value return { show, hide, resolve, visible, hiding }; };
Summarize
This is the article about hooks implementing the display dialog function in React project. For more related contents of React hooks, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!