I need all screens to be wrapped inside the same instance of Provider component.
In version 1 of react-native-navigation there was 3rd parameter of registerComponent
that was allowing that.
Is it possible in v2?
Example:
In case like this:
https://reactjs.org/docs/context.html#updating-context-from-a-nested-component
Context value is based on root component state. This requires, however, to have only single instance of this root component. When I'm registering component inside react-native-navigation
I need to pass this root component everytime (so it's not the same instance), so it have different internal state, so it's not possible to sync context between multiple screens.
You probably want to take a look at this thread https://github.com/wix/react-native-navigation/issues/3549
Here's my solution. i created a helper function:
```
import React from 'react';
import { Provider } from 'react-redux';
const wrapWithProvider = component, store) => (
() => React.createElement(
Provider,
{ store },
React.createElement(component, null)
)
);
and then when you register your component
Navigation.registerComponent('HOME', () => wrapWithProvider(Home, store));
```
@abdullah-sr app crashes at first link with your method. Are you sure of it?
I solved it in this way:
const wrapWithToastProvider = Comp => props => (
<ToastProvider>
<Comp {...props} />
</ToastProvider>
);
Navigation.registerComponent('myschool.login', () => wrapWithToastProvider(Login), store, ReduxProvider);
@pie6k @MaxInMoon Redux support was introduced with this PR https://github.com/wix/react-native-navigation/pull/3675
Now, you can use registerComponentWithRedux
just as you would with v1.
This issue can probably be closed closed now.
I really would prefer a way set a root "component" with all the providers instead of having to wrap them on every screen, because that would cause multiple instances of each provider, which defeats the purpose of the new context api... :/
@brunolemos 's suggestion would be really handy for things like the native-base
StyleProvider
: https://docs.nativebase.io/Customize.html#theaming-nb-headref
I would normally wrap the whole app:
<StyleProvider style={getTheme(variables)}>
<Provider store={store}>
{app}
</Provider>
</StyleProvider>
And @sturmenta 's workaround is good but masks any static options()
in the component which I find handy for setting the topBar
on a screen-by-screen basis.
Like @LordParsley said, when using native base we want to set the theme for the whole app, isn't bad doing it for every component?
Has anybody found a solution to this?
This singleton solution on StackOverflow is the closest thing I could find, but feels very hacky.
Feels like a major flaw in react-native-navigation v2
if we can't use the context api as intended 😞
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
If you believe the issue is still relevant, please test on the latest Detox and report back. Thank you for your contributions.
Hey guys, we've improved supports for this use case. When registering components you can wrap them with providers, additionally you'll need to pass a third argument which is a generator function which returns the actual component.
Navigation.registerComponent('navigation.playground.ContextScreen', () => (props) => (
<TitleContext.Provider value={'Title from Provider'}>
<ContextScreen {...props} />
</TitleContext.Provider>
), () => ContextScreen);
Navigation.registerComponent('navigation.playground.ReduxScreen', () => (props) => (
<Provider store={reduxStore}>
<ReduxScreen {...props} />
</Provider>
), () => ReduxScreen);
With this change, Navigation.registerComponentWithRedux
is now deprecated and will be removed in the future.
I'm closing the issue but suggestions for improvements and feedback are more then welcome.
@guyca thanks for the tip but I still have the same problem of two instances using Navigation.registerComponent('navigation.playground.ContextScreen', () => (props) => (
<TitleContext.Provider value={'Title from Provider'}>
<ContextScreen {...props} />
</TitleContext.Provider>
), () => ContextScreen);
the way you provided it with two different screen.
I know this has been closed, but i think you guys should be more elaborate on this... currently learning about this and i found out that Navigation.registerComponentWithRedux
is still working, how exactly am i supposed to apply whats below to multiscreens... what components will be in
Navigation.registerComponent('navigation.playground.ReduxScreen', () => (props) => (
<Provider store={reduxStore}>
<ReduxScreen {...props} />
</Provider>
), () => ReduxScreen);
@boiy You definitely summarized in one phrase all my frustrations. I'm just learning react and I try for about 2 days now to create a new application where I connect redux and AsyncStorage to my simple app that has a buttonTab navigator and allso I plan to navigate directly between different screens. I guess my simple app is not simple. Can someone point me to a complete up to date example?
Switching from context api to mobx solves all my problems. To register my components and inject the mobx stores i wrote a little helper:
const registerComponentWithStores = (componentID: string, Component: any) => {
Navigation.registerComponent(componentID, () => (props: Object) => (
<Component
{ ...props }
mobxStore1={mobxStore1}
mobxStore2={mobxStore2}
/>
), () => Component)
}
The stores are now available in the props of the registered component: props.mobxStore1
, props.mobxStore2
. All changes i perform via actions to mutate values in the stores are automatically observed by all other registered screens.
Still don't know how to setup a mobx
provider and other provider together (eg. Intl Provider.
ThemeProvider)
How to register a root component with these providers and avoid register them individual.
@guyca I'm not sure if I misunderstood something, but as far as I can see in your code, still - every registered component will have different provider 'instance'.
Let's say we have context with some random number.
// each screen will have different 'random' values. I'd like all of them to be wrapped inside the same instance of the provider with the same value across all screens.
Navigation.registerComponent('navigation.playground.ContextScreen', () => (props) => (
<RandomContext.Provider value={Math.random()}>
<ContextScreen {...props} />
</RandomContext.Provider>
), () => ContextScreen);
There are of course ways to synchronize those context values, but the ultimate goal is to have everything wrapped inside the same root providers. And this seems to still be unresolvable as far as I know
The result would be as if you'd
// no matter what, every screen will have the same context value at any given moment
<Provider value={Math.random()}>
<ScreenA />
<ScreenB />
</Provider>
Right now my root
is:
return (
<ThemeProvider theme="light">
<DataProvider>
<TutorialProvider>
<DragAndDropProvider>
<ModalProvider>
<AuthProvider>
<ScreenComponent />
</AuthProvider>
</ModalProvider>
</DragAndDropProvider>
</TutorialProvider>
</DataProvider>
</ThemeProvider>
);
I don't really want to have all of them duplicated on every screen as:
I've investigated it a bit more, and it's not possible, as far as I've tried to create a single provider for every screen, which is quite an issue for me.
Imagine a simple case like this:
export function Provider({children}) {
// let's say we have provider that counts how many times app went to background
const [timesAppWentToBackground, setTimesAppWentToBackground] = useState(0);
// every time it happens, we modify inner state of provider
useAppGoesToBackground(() => {
setTimesAppWentToBackground(count => count + 1);
});
// in real life, we'd create some context for it etc.
return <>{children}</>
}
It works well if we have only one screen. However, for every new screen, the brand new provider is created with a fresh state of 0
in this case.
This is the simple case and I could keep such count somewhere to be reused later like
let cachedCount = 0;
export function Provider({children}) {
// let's say we have provider that counts how many times app went to background
const [timesAppWentToBackground, setTimesAppWentToBackground] = useState(cachedCount);
useEffect(() => {
cachedCount = timesAppWentToBackground
}, [timesAppWentToBackground]);
// every time it happens, we modify inner state of provider
useAppGoesToBackground(() => {
setTimesAppWentToBackground(count => count + 1);
});
// in real life, we'd create some context for it etc.
return <>{children}</>
}
And this would work. Every new screen would get an 'actual' count. However, in many cases, it's not possible to pass a 'previous state' in such a simple way, especially if it includes some callbacks, etc.
Also, in some cases, the provider is executing some network fetching, etc. In such a case, every screen cause another request, even tho it's not needed.
I consider it serious limitation of react-native navigation.
@pie6k did you find any solution to this? I found that using redux works in that it keeps the state consistent across each and every screen, however, my plan was not to use redux, but if i have to, i might use it with easy peasy for example
@yemi I didn't find any solution for this.
Redux is specific as even if you have multiple providers, you still have a single store, so providers are in sync.
I was experimenting with a single, invisible overlay that is opened all the time and it holds some state that is shared with other screens basing on subscriptions. I had some successes in that, but it was limiting and complicated. It also didn't allow me to use vanilla react things like contexts etc. It was also not possible to pass data between screens in sync way in many cases so I didn't end up using it as it felt way too overengineered
Most helpful comment
I really would prefer a way set a root "component" with all the providers instead of having to wrap them on every screen, because that would cause multiple instances of each provider, which defeats the purpose of the new context api... :/