React-native-router-flux: Redux integration

Created on 12 Apr 2016  路  34Comments  路  Source: aksonov/react-native-router-flux

Hi,
this is probably not the place to ask but ...
I'm trying to get redux integration working with your Reducer.
I'v managed to do this but am unsure if it's the right approach?

const reducerCreate = params=>{
    const defaultReducer = Reducer(params);
    injectAsyncReducer(store, 'router', defaultReducer);
    return (state, action)=>{
        store.dispatch(action);
        return defaultReducer(state, action);
    }
};

So from the code above I'v created a store, combined a couple of reducers and then added the defaultReducer via the store.replaceReducer method ( inside injectAsyncReducer ).
This seems to work nicely, and the store.dispatch is dispatching the correct actions and state is being updated.
My only qualm here is that return defaultReducer(state, action); seems to be doing the same thing as the dispatch ..

Maybe an example on the best practice for a redux integration wouldn't go amiss ?

Cheers,
Stephen.

Most helpful comment

Hi guys, I just updated the README to make redux integration more clear. Please take a look and let me know if it can be improved:

https://github.com/lynndylanhurley/react-native-router-flux#reduxflux

All 34 comments

Having a very similar issue here. The awesome thing about this router in 2.x was that it made so much sense where we easily were able to hook into each route change. Now, the integration doesn't seem to work as expected. Actions.BEFORE_ROUTE allowed us to easily hook into our reducers.

@maitriyogin Yes, looks like it is duplicate.
@maitriyogin @crockpotveggies Have you looked at Example project to see integration? It doesn't use store.dispatch and see all your actions there....

@aksonov i'm having a similar issue as @maitriyogin. While the example shows how to undertake actions on router changes it does not show how to take the default reducer and, for instance, use it with combineReducers to update the store state when using multiple reducers. I would like (expect) to be able to connect() the Router and then do something like this:

import { Reducer as routerReducer } from 'react-native-router-flux';

const rootReducer = combineReducers({
  application: applicationReducer,
  auth: authReducer,
  router: routerReducer,
});

export default rootReducer;

But that won't work. Instead it will complain about the initialState being empty.

Hi tijs,
that's kind of what I was hoping for, in that it would follow the same pattern as react-redux-router ...
I got round this by adding the reducer after it's created with the replaceReducers function injectAsyncReducer(store, 'router', defaultReducer);
combineReducers

export default function createReducer(asyncReducers) {
  return combineReducers({
    login,
    ...asyncReducers
  });
}
function injectAsyncReducer(store, name, asyncReducer) {
  store.asyncReducers[name] = asyncReducer;
  store.replaceReducer(createReducer(store.asyncReducers));
}

const reducerCreate = params=>{
    const defaultReducer = Reducer(params);
    injectAsyncReducer(store, 'router', defaultReducer);
    return (state, action)=>{
        console.log("ACTION:", action);
        store.dispatch(action);
        return defaultReducer(state, action);
    }
};

with reducerCreate being fed into the router

 <Provider store={store}>
        <Router createReducer={reducerCreate}>
....

this works but is kind of messy.
/Stephen.

Yeah that does seem a bit ehmm.. roundabout :)

I'm having the same issue. Why is it complaining about initialState?

@maitriyogin With your hack are you seeing all the Actions provided from react-native-router-flux? I'm only seeing the push & BackAction actions in my logs.

Here is my workaround:

Create the reducer (Router.js file):

import { Reducer } from 'react-native-router-flux';

// needs to fake params to pass sanity checks
const routerReducer = Reducer({initialState: {key: true}, scenes: true});

export default function(state = null, action) {
  if (action.type === 'RootContainerInitialAction') {
      return action.initialState;
  } else {
      return routerReducer(state, action);
  }
}

The default Reducer is a wrapper to set the initial state of the inner function returned, which is the actual reducer. I will deal with the initial state with the RootContainerInitialAction (an action passed to initialize by default), so I just mock an object to pass the sanity check.

But now you can go do:

...
import router from './Router';

export default combineReducers({
  awesomeReducer,
  ...
  router,
});

On the router component:

import store from 'path/to/store';

const createReducer = (params) => {
  // initial state
  const { initialState , scenes} = params;
  const routerInitialState = {...initialState, scenes};
  // dispatch actions
  return (state, action) => {
    // dispatch initial state of router
    if(action.type === 'RootContainerInitialAction') {
      action.initialState = routerInitialState;
    }
    store.dispatch(action);
    return store.getState().router;
  };
};

and then:

Javascript: ... <Router createReducer={createReducer} > <Scene ... > ... </Scene> </Router> ...

I'm using the intended reducer to actually dispatch the router action, and never used the state because the redux store is the source of truth (also, don't do it twice as which I think is the approach of @maitriyogin).

Unfortunately, I was not able to use the connect function of 'react-redux' because I need to dispatch action and then to return the state (and I think is not possible to do it even with mergeProps). So I grabbed the store.


I'm not using the package as intended, but I did not find entry points to do it better. Maybe if the Router component should have these entry points (custom getRouterState instead of hard coded as this.state, custom function to call to dispatch action instead of passing the reducer to the Router, etc.)

@deoqc - what do you think about this solution: https://github.com/aksonov/react-native-router-flux/pull/531 ?

Right now it only dispatches the FOCUS action, but it could be extended to dispatch all the actions.

@lynndylanhurley your objective is to find the current screen? Can't you find it from the router state? If so, you could just find it from the now router state in the redux store (if following my approach).

I think if you decouple the the dispatch and the state from the router, and having a default reducer and some other helpers (like getting the current state from router state), you could be more generic.

The current approach seems to be independent of flux implementation, instead of trying to have entry points to integrate with other implementations (redux, fb flux, nuclear).

Don't think will be always possible to seamlessly integrate with them all (like nuclear require state to be immutable), but maybe a PR that could integrate redux, and shed some light on how do it for other libraries (like, how to convert the immutable state back and forth for nuclear) would be good.

@deoqc - my app just needs to know which screen is the current screen. In v2, this was possible by listening for the BEFORE_FOCUS and AFTER_FOCUS actions. None of the actions dispatched by v3 provide this information. The FOCUS_ACTION action from #531 provides exactly that information.

I hear what you're saying about creating a framework-agnostic system, but honestly I don't care about that at all. I don't think that matters to anyone in practice. We're tired and we just want this to be easy.

531 is unobtrusive, it can't possibly break anything, and it makes redux integration as easy as possible.

If you're worried about continued maintenance, I was the one that implemented the original BEFORE_FOCUS and AFTER_FOCUS actions. This is me trying to continue maintenance on a feature that my apps depend on.

Mostly agree with you @lynndylanhurley. Just thought that this PR is redux specific and only integrate with one feature, so maybe it would not conform with the purpose of the package creator/maintainer (but it is just a assumption and I came across this package few weeks ago, so you surely know better).

@lynndylanhurley just saw that to get the current screen is easy (from Reducer.js):

function getCurrent(state) {
  if (!state.children){
        return state;
    }
  return getCurrent(state.children[state.index]);
}

So, with my workaround just need getCurrent(store.getState().router). It just works, if you did the workaround to begin with, and it gives you more integration with redux (the router state is actually in the redux store, and every change of state passes to it).

If the router actually got it state from the store by design (like this.state = getRouterState || {}; in router instead of passing it in the createReducer) and if the Reducer had minor adaptions to not feel such hack solution, wouldn't it be better for you?

I think we can get in a unobtrusive solution; really integrate with using combineReducers, which seems people that want to integrate with redux tries to do; and solve problems that we didn't anticipate (like yours need to get current view with just getCurrent).

@deoqc - I appreciate you efforts in trying to help me. Your solution does work.

I made #531 in an effort to reduce the amount of glue-code in my app, but I appreciate the reasoning behind createReducer system.

537 should work as the createReducer system intended.

Hi guys, I just updated the README to make redux integration more clear. Please take a look and let me know if it can be improved:

https://github.com/lynndylanhurley/react-native-router-flux#reduxflux

@lynndylanhurley - This is seriously awesome! Thanks so much for doing this. +1 +1 +1 +1 - MERGE IT @aksonov !

@caledhwa thx!! (^^,)

this is seriously awesome!
Huge thanks, I'm sure the community is gonna love you for this...

I'm still a bit confused as to why the RNRF Reducer is needed?
In the reducerCreate function why can't one use the reducer created in step1 ?
I'll try this out and hopefully it'll make sense.
Smiles,
/Stephen.

I don't completely understand reducerCreate's intended purpose. But as far as we're concerned, its purpose is to dispatch the action so that our app's reducer can consume it.

In other words, RNRF has nothing to do with redux. If we want to dispatch actions to redux, we must do it ourselves manually using the reducerCreate hook.

@lynndylanhurley

truly thanks to you.
lots of folks are confused and keep asking about this until you.
you established a shortcut for all of us can focus on how to improve this repo instead keep away from trying to figure out what is the proper way to integrate with redux.

best regards.

@zxcpoiu - your comment just made my day. happy to help :crocodile:

all, @lynndylanhurley, @zxcpoiu
I've added dispatch call (if it exists) within Actions now to make redux integration much easier (without needed to create custom reducer). So now you could see all Router actions in your redux reducers with zero effort (you need just to connect Router).

That's fantastic!! Thanks @aksonov!!!

So, with the description on the README and the changes that have been made here to help out with REDUX, can we close this issue @aksonov? Just thought it might reduce noise.

The only thing I'd love to see is some changes to the TYPE names of the actions. I implemented @lynndylanhurley 's approach to hook up Redux and it works fabulously. It'd be great to have the action types be called RNRF_PUSH, RNRF_FOCUS, etc. so I could much easier differentiate the actions amongst my redux actions. If you're interested I could dig in and do a PR or something. Anyhow, I love this library and want to see it continue to have success! Go team!

Is it possible to consolidate this conversation into fresh doc posted on master? After re-reading a few times, I'm still not 100% sure if what I got working is the best way of doing this with redux.

@allenmanning if you follow the steps in the redux section of the current README it works like a charm!

Thanks. Great project.

Closing it for now. I've also added possible alternative to redux, react-native-reactive library to docs.

I see that this discussion is closed, however, related t o this discussion, I was wandering if it is possible for the Router to use the store on both directions - dispatching its actions to the store AND subscribing/reading its state from the store?

Hi @lynndylanhurley follow your documentation
https://github.com/lynndylanhurley/react-native-router-flux#reduxflux
Made a demo as per your instruction:
https://github.com/krishbhattacharyya/Testroute
Not working for me . Am I done something wrong here?

@lynndylanhurley Hi,
I see that the actions are dispatched and arrive in redux reducers, that's great.

But if I want to use redux devtools to time travel and it seems that react-native-router-flux ignores the changes in state and nothing happens.

Basically our question, what do we have to do to make react-native-router-flux render state changes to enable time travel and generally control of navigation via state?

But if I want to use redux devtools to time travel and it seems that react-native-router-flux ignores the changes in state and nothing happens.

Basically our question, what do we have to do to make react-native-router-flux render state changes to enable time travel and generally control of navigation via state?

I'd be also interested

Was this page helpful?
0 / 5 - 0 ratings

Related issues

xnog picture xnog  路  3Comments

sylvainbaronnet picture sylvainbaronnet  路  3Comments

maphongba008 picture maphongba008  路  3Comments

rafaelcorreiapoli picture rafaelcorreiapoli  路  3Comments

llgoer picture llgoer  路  3Comments