Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using nested NiceModalProvider for new window #141

Open
Tanney-102 opened this issue Dec 22, 2023 · 3 comments
Open

Using nested NiceModalProvider for new window #141

Tanney-102 opened this issue Dec 22, 2023 · 3 comments
Labels
enhancement New feature or request

Comments

@Tanney-102
Copy link

Hello, thanks for this good library. I've always used it well.
I'm opening this issue because I have a suggestion.

I found that the dispatch is declared as global variable. In a situation that you have a single provider, this isn't a big deal. But this is problem if you are using more than one provider.

When NiceModal.provider is called, the global dispatch is updated with givenDispatch or new dispatch function that returned from useReducer hook.

const InnerContextProvider: React.FC = ({ children }) => {
  const arr = useReducer(reducer, initialState);
  const modals = arr[0];
  dispatch = arr[1]; // update with new dispatch
  return (
    <NiceModalContext.Provider value={modals}>
      {children}
      <NiceModalPlaceholder />
    </NiceModalContext.Provider>
  );
};

export const Provider: React.FC<Record<string, unknown>> = ({
  children,
  dispatch: givenDispatch,
  modals: givenModals,
}: {
  children: ReactNode;
  dispatch?: React.Dispatch<NiceModalAction>;
  modals?: NiceModalStore;
}) => {
  if (!givenDispatch || !givenModals) {
    return <InnerContextProvider>{children}</InnerContextProvider>;
  }
  dispatch = givenDispatch; // update with givenDispatch
  return (
    <NiceModalContext.Provider value={givenModals}>
      {children}
      <NiceModalPlaceholder />
    </NiceModalContext.Provider>
  );
};

If you use multiple providers, the global dispatch will be updated with dispatch function intended by the last called provider. And every modals will only be rendered under this last provider because all methods in library (like NiceModal.show) use global dispatch.

function SomeApp() {
  return (
    <NiceModal.Provider> // update global dispatch
      <SomeArea />
      <NiceModal.Provider> // update global dispatch again
        // the modal dispatched in 'SomeArea' will be rendered here 
      </NiceModal.Provider />
    <NiceModal.Provider />
  )
}

I know that this is not common case.
In our team, we open new window and render components with react portal. In this case we should use multiple providers to render modals in new window.

function NewWindow({ children }) {
  const [container] = useState(() => {
    const newWindow = window.open()
    const container = newWindow.document.createElement('div')
    newWindow.document.body.append(container)
    return container
  })

  return ReactDOM.createPortal(children, container)
}

function SomeApp() {
 return (
    <NiceModal.Provider>
      ...
      <NewWindow>
        <NiceModal.Provider>
          ...
        </NiceModal.Provider>
      </NewWindow>
    </NiceModal.Provider>
   )
}

So I suggest following.

  • provide dispatch function with context
    const InnerContextProvider: React.FC = ({ children }) => {
      const arr = useReducer(reducer, initialState);
      const modals = arr[0];
      dispatch = arr[1];
    
      return (
        <NiceModalContext.Provider value={modals}>
          <NiceModalDispatchContext.Provider value={dispatch}
            {children}
          </NiceModalDispatchContext.Provider>
          <NiceModalPlaceholder />
        </NiceModalContext.Provider>
      );
    };
  • use the dispatch from context in showCallback that is returned by useModal hook
    export function useModal(modal?: any, args?: any): any {
      ...
      const dispatch = useContext(NiceModalDispatchContext)
      ...
      const showCallback = useCallback((...) => {
        ...
        dispatch(showModal(...))
      }, [...]
    }

Thanks for reading.
Please consider and respond.

@supnate
Copy link
Collaborator

supnate commented Dec 25, 2023

Hmm, so why you need to use nested provider? To render modals under a new window's DOM?

@Tanney-102
Copy link
Author

To render modals under a new window's DOM?

yes!

@supnate supnate added the enhancement New feature or request label Jan 11, 2024
@mikkelblanne
Copy link

mikkelblanne commented May 13, 2024

Nested usage is only a subset of the problem - the same problem occurs with providers in different places in the React tree.

We have this issue in a shadow DOM context. That would usually be a web component, but we're using it for micro-frontends, where parts of the ui are in isolated containers with their own shadow DOMs. I was quite surprised to see modals rendered inside another shadow DOM - I was expecting the NiceModal.Provider context to contain everything, also registered modals etc, so that each provider would work independently from each other, just by using the closest provider ancestor.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants