Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using react-native-router-flux with redux, how to update state in the view component?

The issue:

I have tried to use redux in combination with the routing provided by react-native-router-flux.

In simple words, it is not working:

  • redux state modifications will not appear in view, but can be logged successfully to console
  • every time an action is taken, the whole component tree with the scenes will be recreated, which results in lots of warnings in console, that a scene with the key "xyz" was already created.

What i did:

I used the official example application of react-native-router-flux and added a simple counter-example based on redux:

  • a simple state: "counter" (might be an integer)
  • reducers and actions (increment, decrement)

Below i will show some code, but also one will find my example application on github, please feel free to check it out: https://github.com/itinance/react-native-router-flux. The "Example" Directory is the original example application. And "ExampleRedux" is a copy of the example with a redux stack and example reducers (a counter), what i am talking about here.

The main application component with provider and store:

import * as reducers from './components/reducers';

const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const reducer = combineReducers(reducers);

const store = createStoreWithMiddleware(reducer);

export default class Example extends React.Component {
    render() {
        return (
            <Provider store={store}>
                <ExampleContainer/>
          </Provider>
        );
    }
}

My reducers:

const initialState = {
  count: 0
};

export default function counter(state = initialState, action = {}) {

  switch (action.type) {
      case "INCREMENT":
        console.log("do increment", state,
            {
                ...state,
                count: state.count + 1
            }
        );

      return {
        ...state,
        count: state.count + 1
      };
    case "DECREMENT":
      return {
        ...state,
        count: state.count - 1
      };
    default:
      return state;
  }
}

The inner application container for redux' "connect":

I pass the state and actions to the scene component:

<Scene key="launch" component={Launch} title="Launch" initial={true} state={state} {...actions} />

The whole code of this ExampleContainer:

class ExampleContainer extends React.Component {

    constructor(props) {
        super(props);
    }

    render() {
        const { state, actions } = this.props;
        console.log("Props", this.props, state, actions); // everything ok here

        return <Router createReducer={reducerCreate}>
            <Scene key="modal" component={Modal} >
                <Scene key="root" hideNavBar={true}>
                    <Scene key="echo" clone component={EchoView} />
                    <Scene key="register" component={Register} title="Register"/>
                    <Scene key="home" component={Home} title="Replace" type="replace"/>
                    <Scene key="launch" component={Launch} title="Launch" initial={true} state={state} {...actions} />
                    .... lots of other scenes ...
                    </Scene>
                </Scene>
                <Scene key="error" component={Error}/>
            </Scene>
        </Router>;
    }
}


export default connect(state => ({
   state: state.counter
 }),
 (dispatch) => ({
   actions: bindActionCreators(actions, dispatch)
 })
)(ExampleContainer);

And this is the dead easy LaunchScreen having simple counter functionality (and example routes for demonstrating the routing):

class Launch extends React.Component {

    constructor(props) {
        super(props);
        console.log(props);
    }

    render(){
        const { state, increment, decrement } = this.props;

        console.log("Props 2: ", this.props, state, increment, decrement);

        return (
            <View {...this.props}  style={styles.container}>
                <Text>Launch page</Text>

                <Text>{state.count}</Text>

                <Button onPress={increment}>Increment</Button>

                <Button onPress={()=>Actions.login({data:"Custom data", title:"Custom title" })}>Go to Login page</Button>
                <Button onPress={Actions.register}>Go to Register page</Button>
                <Button onPress={Actions.register2}>Go to Register page without animation</Button>
                <Button onPress={()=>Actions.error("Error message")}>Popup error</Button>
                <Button onPress={Actions.tabbar}>Go to TabBar page</Button>
               <Button onPress={Actions.pop}>back</Button>
            </View>
        );
    }
}

The actions are perfectly dispatched when clicking on Increment-Button. In the console the new state can be logged successfully. It increments one by one with every click. But not in the view.

When i ommit the router and scenes and only activate the launch screen as a single component, everything works fine.

Question:

How to make this redux application work, that views will update their state correctly?

For the sake of completeness, the whole code can be found on github.

like image 783
itinance Avatar asked May 16 '16 12:05

itinance


People also ask

How is redux different from Flux?

The primary difference of Flux vs Redux is that Flux includes multiple Stores per app, but Redux includes a single Store per app. Rather than placing state information in multiple Stores across the application, Redux keeps everything in one region of the app.

What is actions react-native router flux?

react-native-router-flux is a different API over react-navigation . It helps users to define all the routes in one central place and navigate and communicate between different screens in an easy way. But it also means that react-native-router-flux inherits all limitations and changes from updated versions.

Can you use react router with react-native?

React Router Native is published to npm. You can install it with either npm or yarn . Once you've initialized a new React Native project, you can copy/paste any of the examples into your index.


1 Answers

You have probably forgotten to connect the Launch component to the store. What you want to do is similar to what you have done in ExampleContainer, i.e.

export default connect(state => ({
   state: state.counter
 }),
 (dispatch) => ({
   actions: bindActionCreators(actions, dispatch)
 })
)(Launch);

and then the correct values will show up in your log

like image 160
vonovak Avatar answered Oct 05 '22 23:10

vonovak