Prevent modal from closing after re-render in react

Viewed 3340

Inside a Component, I have a Modal from which the user can do an update to some data through an API and then change the state of that main Component. Because there is a change in state, everything will re-render.

I would like to keep the modal open, so that I can show a success message.

The code would be something like this.

 const Main = () => {
    const [state, setState()] = useState();
    return (
    <Component state={state}>
       <Modal onButtonClick={() => {
           updateThroughApi().then(() => setState())} />
    </Component>
    )
}

When user clicks on modal's button, the state changes, and Component is re-rendered. Modal is rendered too, as it's inside.

I have thought of two possible solutions:

  1. Move the modal outside of the component. This is a problem, as my actual code is not as simple as the example I posted. In my code, the modal opens on the click of a button B, which is deep inside Component. So, if I move the modal out from Component, I would have to pass the status and the action to change status (e.g. [open, setOpen]) through several components until button B (prop drilling).

  2. Another solution: On the action onButtonClick I just do the API update, and use a new state updated = true; then, onModalClose, only if updated is true, I run setState so Component is rendered just after the modal is closed. But this solution seems a hack to me. There must be a better way.

Is there any better solution?

2 Answers

Something is obviously going very wrong here, the Modal should not close. As a workaround you could do something like this:

const Main = () => {
    const [state, setState()] = useState();
    const modal = useMemo(() => (
       <Modal onButtonClick={() => {
           updateThroughApi().then(() => setState())} />
    ), [])

    return (
        <Component state={state}>{modal}</Component>
    )
}

Your Modal is re-rendering because your function passed as onButtonClick is redefined at every render.

So you have 2 options here:

1/ Keep your Modal inside your Component and use useMemo

import { useMemo } from 'react'

const Main = () => {
    const [state, setState] = useState();
    const modal = useMemo(() => (
        <Modal onButtonClick={() => (
                updateThroughApi().then(() => setState())
            )}
        />
    ), [])

    return (
        <Component state={state}>
            {modal}
        </Component>
    )
}

Or 2/ Move your Modal outside your component and use combination of memo and useCallback

import { memo, useCallback } from 'react'

const Main = () => {
    const [state, setState] = useState();
    const onButtonClick = useCallback(() => updateThroughApi().then(() => setState()), []);

    return (
        <Component state={state}>
            <Modal onButtonClick={onButtonClick} />
        </Component>
    )
}

const Modal = memo(({onButtonClick}) => {

})

So in this case, at every render, memo will compare if all Modal props are === from previous render, which is now the case, because memoization of onButtonClick with useCallback, and so your Modal component will not re-render

https://reactjs.org/docs/hooks-reference.html#usememo

Related