react-native-paper icon indicating copy to clipboard operation
react-native-paper copied to clipboard

Nested component in Portal not receiving state values of React Context

Open ismailcodeboi opened this issue 2 years ago • 14 comments

Current behaviour

When using Portal component you are not given the context of the state in the wrapping/global React Context Provider

Expected behaviour

You should be able to access the state values of the Context Provider

How to reproduce?

  • create a new expo app
  • install react-native-paper
  • create a context using createContext from react and give it an initial state/default value
  • wrap the entire app in your named context.Provider
  • create any component (text, view, snackbar etc.)
  • using the Portal component wrap the created component
  • inside the created component call the useContext() with your created context as the parameter
  • expect the state to be defined/a value, but it is undefined

https://snack.expo.dev/184285Vsi - here is a simple snack to what the issue is, with some logs

Preview

The Context and Provider image

Main App wrapped in Provider image

Portal Component image

nested component of Portal image

Log of initial values outside of Portal image

We expect these values to be the same within the portal

However as seen the in screenshot of the logs below it is undefined: image

What have you tried so far?

To fix this I have passed in the state values as props to the nested component - this seems to work, however it is defeating the purpose of using Context Providers of react

Your Environment

software version
react-native 0.71.7
react-native-paper ^5.7.0
node 16.19.0
npm 8.19.3
expo sdk ^48.0.11

ismailcodeboi avatar May 10 '23 09:05 ismailcodeboi

Thanks for raising this issue! We're experiencing the same issue - took us days to debug.

JPStrydom avatar May 10 '23 09:05 JPStrydom

Try flipping the positions of <StateProvider> and <Provider>

DimitarNestorov avatar May 10 '23 09:05 DimitarNestorov

Try flipping the positions of <StateProvider> and <Provider>

But then we won't get access to the theme in our StateProvider will we? Which might be a bit of a pain as I can imagine things like error boundaries, notifications, snackbars, etc. might be needed outside the StateProvider

JPStrydom avatar May 10 '23 09:05 JPStrydom

I still fell like this is breaking the React rule. If something is inside a React context, it should have access to that context - where as that is not the case with the RNP Portal component.

And the fix of simply moving the state context outside of the theme context seems like a hack, as now we'll not have access to the theme inside the state provider.

JPStrydom avatar May 29 '23 10:05 JPStrydom

Anybody have a reason as to why this occurs?

andrewtran10 avatar Jun 04 '23 10:06 andrewtran10

@ismailcodeboi I tinkered a bit with your example and seems that the <Portal> component will always render outside of the parent tree, while <Portal.Host> is the one that renders the <Modal> alongside others components in the parent tree and can access the StateContext.

Not really sure if it should behave this way, but the children will be rendered only inside of these providers: (see line 59)

https://github.com/callstack/react-native-paper/blob/1ce31113cd67e7aa450c72d6f86cd4a20c8157a1/src/components/Portal/Portal.tsx#L44-L68

jeferson-sb avatar Jun 05 '23 01:06 jeferson-sb

One big problem that we currently have with this issue, is that our modals cannot read the navigation state or use the navigation hooks. We're using React Navigation for our navigation, which requires routes at the root level of it context wrapper - meaning that we can't have our theme context nor our own state context inside our navigation context. This means that we won't be able to access these contexts inside our modals, which is quite frustrating as we can't have any navigation functionality on our modals now.

JPStrydom avatar Aug 14 '23 07:08 JPStrydom

I'm facing the same problem thank you for open this issue. You guys have any elegant workaround for this?

emanoelqueiroz avatar Oct 05 '23 14:10 emanoelqueiroz

I'm facing the same problem thank you for open this issue. You guys have any elegant workaround for this?

Unfortunately not an elegant solution, but we ended up wrapping our RNP ThemeProvider in our state providers. This did require a bit of rework of our application. We had to move our theme and state consumers inside the theme provider (which is inside our state providers). This then allowed all Portal components to have access to the relevant context.

This is still not an ideal solution, as our NavigationProvider (from React Navigation) cannot be outside the ThemeProvider due to the way it works. This means that modal components cannot consume the navigation context (navigation state and navigation functions).

JPStrydom avatar Nov 10 '23 08:11 JPStrydom

Yeah, I ended up doing the same and facing the same problem of not being able to use anything from React Navigation.

I'll try some workarounds (like I've being doing) and when they fix, maybe, I'll change this "temporary code", but thank you anyway buddy

emanoelqueiroz avatar Nov 16 '23 18:11 emanoelqueiroz

I still fell like this is breaking the React rule. If something is inside a React context, it should have access to that context - where as that is not the case with the RNP Portal component.

And the fix of simply moving the state context outside of the theme context seems like a hack, as now we'll not have access to the theme inside the state provider.

Totally agree. Also took hours to find out. And the same happens with React Navigation context as reported in #4022 .

danb4r avatar Dec 08 '23 08:12 danb4r

I'm also facing the same problem.

szado avatar Dec 23 '23 14:12 szado

+1, facing the same issue. Is there an update for this?

rveltonCL avatar Feb 17 '24 18:02 rveltonCL

Any idea for how to attract some attention from RN paper devs? This issue seems abandoned. Would be petty to make a MR with something they don't approve

Also note that you can use Portal.Host so that in theory you would be able to access the contexts by creating the instance after creating the contexts, however this doesn't appear to work as expected

import { useState } from 'react';
import { Provider, Portal } from 'react-native-paper';
import { StateProvider } from './StateProvider.jsx';
import { CustomPage } from './CustomPage.jsx';
import { CustomModal } from './CustomMOdal.jsx';

export default function App() {
  const [showModal, setShowModal] = useState(false);
  return (
    <Provider>
      <StateProvider>
        <Portal.Host>
          <CustomPage show={showModal} setIt={setShowModal}></CustomPage>
          <CustomModal
            show={showModal}
            onClose={() => setShowModal(false)}></CustomModal>
        </Portal.Host>
      </StateProvider>
    </Provider>
  );
}

Anyways, this is my hacky workaround:

import {ThemeContext} from '@shared/assets/themes/themeContext';
import React, {useContext} from 'react';
import {Portal as PaperPortal} from 'react-native-paper';

function PortalWithTheme(props: {children: React.ReactNode}) {
  const themeContext = useContext(ThemeContext);
  const theme = themeContext.theme;
  const setTheme = themeContext.setTheme;
  return (
    <PaperPortal {...props}>
      <ThemeContext.Provider value={{theme: theme, setTheme: setTheme}}>
        {props.children}
      </ThemeContext.Provider>
    </PaperPortal>
  );
}

export default PortalWithTheme;

Basically just redefine the context within the portal, and it doesn't conflict with the context outside the portal

JacobDel avatar Aug 08 '24 20:08 JacobDel