wix / react-native-navigation

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

Using redux-persist gate in react-native-navigation #3927

Closed nimabk82 closed 5 years ago

nimabk82 commented 5 years ago

how to use PersistGate from redux-persist.

ammoradi commented 5 years ago

i think RNN team should make registerComponentWithRedux expandable to support persist gate

phuongwd commented 5 years ago

@nimamyscreen

ammoradi commented 5 years ago

@phuongwd i saw these comments. but they are not an official solution. for me, app crashed with these solutions !

ItsNoHax commented 5 years ago

@ammoradi This is a snippet of my index.js. I wait till RNN is ready and my store is persisted. This way there is no need for a persistGate since the store is already filled when it gets used.

import { Navigation } from 'react-native-navigation';
import { Provider } from 'react-redux';
import { persistStore } from 'redux-persist';

// import store from 'PATH_TO_YOUR_STORE';

/**
 * Resolves when the store is succesfully rehydrated.
 * @param {store} storeToHydrate - The store to rehydrate
 * @returns {Promise} - Returns a promise that resolves when the store is hydrated.
 */
const rehydrateStore = storeToHydrate => new Promise((resolve) => {
  persistStore(storeToHydrate, undefined, () => {
    resolve();
  });
});

/**
 * Wait till RNN navigation is ready.
 * @returns {Promise} - Resolves when RNN navigation has fully launched
 */
const onNavigationReady = () => new Promise((resolve) => {
  Navigation.events().registerAppLaunchedListener(() => {
    resolve();
  });
});

/**
 * Async function that bootstraps our application
 * Registers screens ->
 *      Wait for store to hydrate
 *      Wait for Navigation to be ready
 *      Wait for icons to be loaded
 * Then set our layout according to user's logged in state.
 */
async function bootstrap() {
  registerScreens(Provider, store);
  await Promise.all([rehydrateStore(store), onNavigationReady()]);
// RNN is ready, store is filled
// Now show layout according to state.
}

bootstrap();
ammoradi commented 5 years ago

@ItsNoHax thanks for your responsibility. I will test your solution :)

devjaw commented 5 years ago

@ItsNoHax can you share your registerScreens() please ?

ammoradi commented 5 years ago

@devjaw i think he meant registerComponentWithRedux(name, component, provider, store)

devjaw commented 5 years ago

@ammoradi i tried to use this solution but it doesn't work with me and also there is no error in my app. it just stuck on blank white screen

damathryx commented 5 years ago

@devjaw happening to me as well, it works fine on iOS but on android redux-persist getting error on rehydrating. i switched to redux storage, worked fine

talyuk commented 5 years ago

@ItsNoHax there is one strange thing on Android: if you exit app via back button, and then open application, registerAppLaunchedListener event is fired, but not persistStore callback. persistStore in this case is already loaded

ItsNoHax commented 5 years ago

Yeah, happens in Debug mode @talyuk. Not sure why

rendomnet commented 5 years ago

@ItsNoHax I have white screen on IOS await Promise.all([rehydrateStore(store), onNavigationReady()]); rehydrateStore(store) is never resolved

jinshin1013 commented 5 years ago

I had a similar problem with Mobx as well. If you use any async function to do anything before the first screen is registered, the app wouldn't start only on Android while IOS works fine.

rendomnet commented 5 years ago

Can anyone show your store file?

ItsNoHax commented 5 years ago

Here is a little example project: https://github.com/ItsNoHax/EventFinder

stale[bot] commented 5 years ago

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.

ItsNoHax commented 5 years ago

Closing this as the solutions here including mine are bad. The listener for RNN's startup should always be available so that it can also handle Android back button.

My working solution is the following:

import { Navigation } from 'react-native-navigation';
import { persistStore as persistStoreRaw } from 'redux-persist';
import store from './src/store/store';
import { loadIcons } from './src/lib/AppIcons';
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
 *    - Icons to be loaded.
 * and then we finally initialize
 * layout accordingly.
 */
async function bootstrap() {
  registerScreens(store);

  await Promise.all([loadIcons(), 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();
});
ammoradi commented 5 years ago

@ItsNoHax please reopen this issue. there is not yet any official way to do this!

ItsNoHax commented 5 years ago

@ammoradi There will never be official support for Redux, the plan is to keep the navigation open to all sort of data management solutions.

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 (
      <PersistGate loading={null} persistor={store().persistor}>
        <TodoList {...props}/>
      </PersistGate>
  );
};

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 } }); });

pkyeck commented 5 years ago

Closing this as the solutions here including mine are bad. The listener for RNN's startup should always be available so that it can also handle Android back button.

My working solution is the following:

...

@ItsNoHax Your approach adds a couple of seconds to the bootstrap-process :( The rehydration of the store takes like 5 seconds in my dummy project with just one reducer. Did you ever have similar problems/delays? Really annoying while developing and not really great for user experience in production either ...

2019-01-14 16:53:34.848 [info][tid:com.facebook.react.JavaScript] bootstrap()
2019-01-14 16:53:34.885 [info][tid:com.facebook.react.JavaScript] rehydrateStore()
2019-01-14 16:53:34.886 [info][tid:com.facebook.react.JavaScript] onNavigationReady()
2019-01-14 16:53:34.894 [info][tid:com.facebook.react.JavaScript] onNavigationReady() => resolve
2019-01-14 16:53:39.888 [info][tid:com.facebook.react.JavaScript] rehydrateStore() => resolve
2019-01-14 16:53:39.889 [info][tid:com.facebook.react.JavaScript] init()

update Looks like this only happens in the simulator - performance on an actual device is way better.

ppfmagno commented 5 years ago

Does anyone have a simple project working with redux persist and RNN v2? I know maybe this is not the best place for it, but I can't implement persist on my project for the heck of it, and I'm losing hope.

Can someone help me, please??

I tried to use some of the solutions presented on this issue, but none of them seems to be complete itself, and I'm not understanding what should I put on or not on the code for it to work. :(

Sorry for my lack of knowledge about it.

ammoradi commented 5 years ago

@ppfmagno because of this limitation, in my case I decided to write a simple redux subscriber for asyncstorage.

the key point is:

When app state is changed, store the state in AsyncStorage. And when the root component is getting mounted, on its componentWillMount() method read state from AsyncStorage and create another state from it.

there is a useful link for this pattern: https://medium.com/@sumitkushwaha/syncing-redux-store-with-asyncstorage-in-react-native-2b8b890b9ca1

if you concentrate on redux-persist's work procedure, you will find out that its key concept looks like above