wix / react-native-navigation

A complete native navigation solution for React Native
https://wix.github.io/react-native-navigation/
MIT License
13.03k stars 2.67k forks source link

[V2] How to combine react-native-navigation v2 with redux-persist ? #3590

Closed tiennguyen9988 closed 5 years ago

tiennguyen9988 commented 6 years ago

How to config app react-native-navigation v2 with redux-persist?

gabrielhpugliese commented 6 years ago

This is my attempt, don't know if it is recommended:

const store = configureStore();
const persistor = persistStore(store);

const createApp = (Component, ...props) => {
  return class App extends React.Component<TProps> {
    render() {
      return (
        <Provider store={store}>
          <PersistGate
            loading={<Spinner size="large" />}
            persistor={persistor}
          >
            <Component {...{
              ...this.props,
              ...props,
            }} />
          </PersistGate>
        </Provider>
      );
    }
  }
}

export const registerScreens = () => {
  Navigation.registerComponent('app.Login', () => createApp(LoginScreen));
  Navigation.registerComponent('app.Dashboard', () => createApp(DashboardScreen));
}
tiennguyen9988 commented 6 years ago

Thank you for helping but it still not working! i was config router.js the same your code and two file has folow content: ===== app.js === import { registerScreens } from 'rnapp/App/Router'; import { Navigation } from 'react-native-navigation'; registerScreens(); Navigation.events().registerAppLaunchedListener(() => { Navigation.setRoot({ root: { component: { name: 'app.Home' } } }); }); ===== index.js === import { AppRegistry } from 'react-native'; import App from './app'; AppRegistry.registerComponent('rnapp', () => App);

==== Error ====== screen shot 2018-07-20 at 4 58 54 pm Please help me, thank you!

gabrielhpugliese commented 6 years ago

First, you should replace your index.js content with the one from app.js. registerAppLaunchedListener should be called instead of registerComponent. Second, probably you are not exporting properly your screen component. For example, try to console.log(YourScreen) after you import it to check it's a function.

tiennguyen9988 commented 6 years ago

Yes! I tried this method, and this is app's error

screen shot 2018-07-20 at 5 27 48 pm

tiennguyen9988 commented 6 years ago

Oh thank you Pro: gabrielhpugliese. I resolved this error, it's exist in AppDelegate.m because i config not correct.

gabrielhpugliese commented 6 years ago

Keep in mind that I think this solution is not performant because I think it creates a Provider for each screen, but I do not have a better solution. Take a look on this one: https://github.com/wix/react-native-navigation/issues/1642

tiennguyen9988 commented 6 years ago

Yes i will find better solution, if found, i will share it in this issue. Thank you!

Zycon42 commented 6 years ago

You can pass 3rd parameter to persistStore which is callback that's triggered when store is rehydrated. So I simply call Navigation.setRoot after that.

...
import { persistStore as persistStoreRaw } from 'redux-persist';
...

const store = configureStore();

// promisify persistStore
const persistStore = (store: any) =>
  new Promise(resolve => {
    persistStoreRaw(store, undefined, () => {
      resolve();
    });
  });

// promisify app launched event
const onAppLaunched = () =>
  new Promise(resolve => {
    Navigation.events().registerAppLaunchedListener(() => {
      resolve();
    });
  });

(async () => {
  registerScreens(store);
  // persist store and w8 for app start simultaneously
  await Promise.all([persistStore(store), onAppLaunched()]);

  Navigation.setDefaultOptions({...});

  await Navigation.setRoot({
    root: {...}
  });
})();

I wrap each screen with hoc containing <Provider store={store} />. Because I wait for store rehydratation before I call Navigation.setRoot I don't have to use <PersistGate />. Also don't forget to use hoist-non-react-statics otherwise your static get options() on screens won't work.

tiennguyen9988 commented 6 years ago

Thank you for helping Zycon42! But it still not working and require , please tutorial for me method combine with hoist-non-react-statics. screen shot 2018-07-23 at 10 06 46 am

alexxsanchezm commented 6 years ago

@gabrielhpugliese I saw in the docs for V2 you should register the screens but with a different function: Navigation.registerComponentWithRedux('com.app.SearchScreen', () => Search, Provider, store);

tiennguyen9988 commented 6 years ago

Thanks you alexxsanchezm! May be this is new feature updated, i will try again.

traxx10 commented 6 years ago

When calling persistStore like this, the component does not render but when i remove the persistStore function it works

`import { Navigation } from 'react-native-navigation'; import { persistStore } from 'redux-persist'; import { registerScreens } from './src/screens/Screen'; import store from './src/store';

registerScreens();

persistStore(store, null, () => { Navigation.events().registerAppLaunchedListener(() => { Navigation.setRoot({ root: { stack: { children: [ { component: { name: 'BetGold.OddsScreen' } } ], options: { topBar: { title: { text: 'Blog' } } } } } }) }) }) `

rendomnet commented 5 years ago

@Zycon42 persistStore promise is never fulfilled for me on iOs.

rprasad-medullus commented 5 years ago

Can any one share source code ?

mrquanghuy commented 5 years ago

Im looking for RNN v2 and RN >=0.56 intit project. but it all error :(

alzalabany commented 5 years ago

How does registerComponentWithRedux react to store changes ?

Since store is immutable so store ref. Will change , does changes that occur in one root will propagate to other roots ?

creative-git commented 5 years ago

any full example please ?

twsuneth commented 5 years ago

@gabrielhpugliese I saw in the docs for V2 you should register the screens but with a different function: Navigation.registerComponentWithRedux('com.app.SearchScreen', () => Search, Provider, store);

@alexxsanchezm Thanks man...!

ItsNoHax commented 5 years ago

Closing as all solutions provided are not complete.

The listener for RNN's start up event must always be listening. This is a better solution.

import { Navigation } from 'react-native-navigation';
import { persistStore as persistStoreRaw } from 'redux-persist';
import store from './src/store/store';
import { registerScreens } from './src/screens/screens';
import { setInitialLayout } from './src/NavigationController';

/**
 * Wait till our store is persisted
 * @param {store} storeToPersist - The redux store to persist
 * @returns {Promise} - Promise that resolves when the store is rehydrated
 */
const persistStore = storeToPersist => new Promise((resolve) => {
  persistStoreRaw(storeToPersist, undefined, () => {
    resolve();
  });
});

/**
 * We register screens
 *  then we wait for
 *    - Store to be rehydrated
 * and then we finally initialize
 * layout accordingly.
 */
async function bootstrap() {
  registerScreens(store);
  // Add any more promises that must be resolved before layout is set
  await Promise.all([persistStore(store)]);

  setInitialLayout();
}

/**
 * The initial listener of our app,
 * this will get triggered on app start or when
 * the Android activity is recreated.
 * (For example by pressing back button on the
 * root screen)
 */
Navigation.events().registerAppLaunchedListener(() => {
  bootstrap();
});
RajenderDandyal commented 5 years ago

No need to do anything fancy with promises simply create store as described in redux-persist docs then in index.js file add a wrapper function on every screen like this.

/* @Format /

// import {AppRegistry} from 'react-native'; // import App from './App'; // import {name as appName} from './app.json';

// AppRegistry.registerComponent(appName, () => App); // // import {Navigation} from "react-native-navigation";

import themeConstants from "./src/theme"; import {componentIds, screenNames} from "./src/screens"; import { PersistGate } from 'redux-persist/integration/react' import TodoList from "./src/screens/ToDo/ToDo"; import CommentsScreen from "./src/screens/ToDo/commentsScreen"; import {persistStore} from "redux-persist"; import {Provider} from "react-redux"; import store from "./src/store/createStore/createStore"; import React from "react"; import {AsyncStorage} from "react-native"; import {Platform, ActivityIndicator} from "react-native";

//let persistor = persistStore(store().store)

const TodoListWithStore = (props) => { return (

); }; const Loading = () => { return ( <ActivityIndicator size={"large"} animating /> ); }; const CommentsScreenWithStore = (props) => {

return (
    <Provider store={store().store}>
    <PersistGate loading={Loading()} persistor={store().persistor}>
      <CommentsScreen {...props}/>
    </PersistGate>
    </Provider>
);

}; Navigation.registerComponentWithRedux(screenNames.toDo, () => TodoListWithStore, Provider, store().store); Navigation.registerComponentWithRedux(screenNames.commentsScreen, () => CommentsScreenWithStore, Provider, store().store); / topBar: { visible: true, animate: true, hideOnScroll: true, buttonColor: themeConstants.pink800, backButton: { color: themeConstants.pink400 }, background: { color: themeConstants.primary }, title: { color: themeConstants.offWhite, fontSize: 18, alignment: "center" } /

Navigation.setDefaultOptions({ topBar: { title: { ...Platform.select({ ios: { fontFamily: 'System', }, android: { fontFamily: 'Roboto' } }), color: '#262626', alignment: 'center', }, alignment: 'center', elevation: 0, noBorder: true, drawBehind: true, background: { color: 'transparent' } }, statusBar: { visible: true, style: "light", backgroundColor: themeConstants.primary }, layout: { backgroundColor: themeConstants.light, orientation: ["portrait"] //["portrait", "landscape"] An array of supported orientations }, bottomTabs: { visible: true, animate: true, // Controls whether BottomTabs visibility changes should be animated backgroundColor: themeConstants.secondary }, bottomTab: { iconColor: themeConstants.tertiary, selectedIconColor: themeConstants.pink400, textColor: themeConstants.tertiary, selectedTextColor: themeConstants.pink400, fontFamily: "Helvetica", fontSize: 10 } }); const stack = { children: [ { component: { id: componentIds.toDoStack, name: screenNames.toDo, passProps: {} } } ], options: {} }; Navigation.events().registerAppLaunchedListener(() => { Navigation.setRoot({ root: { stack: stack } }); });

RajenderDandyal commented 5 years ago

on android, if store is not updaed on some screen then call Persistor.persist() method in that component with some if condition ... these two simple steps worked for me

jojomillennium commented 5 years ago

Yup, agreed with the no need for fancy promises. Following these steps (initially for nav v1), I had it adapted for v2 by putting persistStore before my registerAppLaunchedListener, which was working fine on iOS, but had just a blank screen on Android.

I simply moved the persistStore inside the registerAppLaunchedListener, and that did the trick for me

Navigation.events().registerAppLaunchedListener(() => {
  persistStore(store, null, () => {
     ...
  })
})
Zefau commented 5 years ago

This is my attempt, don't know if it is recommended:

const store = configureStore();
const persistor = persistStore(store);

const createApp = (Component, ...props) => {
  return class App extends React.Component<TProps> {
    render() {
      return (
        <Provider store={store}>
          <PersistGate
            loading={<Spinner size="large" />}
            persistor={persistor}
          >
            <Component {...{
              ...this.props,
              ...props,
            }} />
          </PersistGate>
        </Provider>
      );
    }
  }
}

export const registerScreens = () => {
  Navigation.registerComponent('app.Login', () => createApp(LoginScreen));
  Navigation.registerComponent('app.Dashboard', () => createApp(DashboardScreen));
}

Looks good initially, but be aware that this wraps every single screen by itself and is thus executed every time a screen is called.

nbstr commented 5 years ago

@gabrielhpugliese I saw in the docs for V2 you should register the screens but with a different function: Navigation.registerComponentWithRedux('com.app.SearchScreen', () => Search, Provider, store);

This is deprecated now. Should be used like this now:

Navigation.registerComponent(
    "Authentication",
    () => props => (
      <Provider store={store}>
        <Authentication {...props} />
      </Provider>
    ),
    () => Authentication
  );
michaelknoch commented 4 years ago

Every component needs to be wrapped with all the providers? So We will have a lot redundant provider instances in Memory, right? @nbstr

johuder33 commented 4 years ago

Every component needs to be wrapped with all the providers? So We will have a lot redundant provider instances in Memory, right? @nbstr

I think this is not quite true, because RNN render just the current view that you are watching, so, when you push to another view, that view will get unmounted and then the new view will get mounted, that's why shouldn't have some memory issues about this.