Initialization

I’ll start a new project from scratch:

react-native init ReactNavigationRedux

We’ll add the two main libraries we’ll be working with: React Navigation and Redux. We’ll also add a few helper libraries.

yarn add react-navigation

yarn add redux

yarn add react-redux

yarn add redux-logger

react-navigation : Handles screen-to-screen navigation and associated UI

: Handles screen-to-screen navigation and associated UI redux : Manages a single app-wide state store

: Manages a single app-wide state store react-redux : Provides bindings for react components to hook into Redux

: Provides bindings for react components to hook into Redux redux-logger: A handy tool for inspecting the state of our store from the debugger

I’m going to breeze over the part where I create a basic app with a StackNavigator , a few routes with corresponding screen components, and set up a UI with basic routing and param passing. If any of this sounds foreign to you, check out my tutorial on getting Up and Running with React Navigation.

Here’s our starting Navigator.js :

Our default export will be a navigator wrapped in a component. We’ll also export the bare StackNavigator as a named export. It’ll be clear why we’re doing this in a bit.

Our app has a feed and each item has a detail screen

What is Redux?

Redux is a library used to maintain a single “source of truth” for an application’s data. Understanding its inner workings is beyond the scope of this post, and if you’re already comfortable with Redux feel free to skip this section, but I’ll give an overview for those who could use a refresher.

The concept isn’t too complicated but requires the coordination of a few elements. A single “store” object is created to hold whatever data we want. To ensure that our store’s data is predictable, we aren’t allowed to directly alter or even access it.

In order to alter data, we “dispatch” an “action” object to the store that contains all the information the store needs in order to make the change. The “action” object we dispatch must include a type property and may include a payload of extra data:

const action = {

type: 'USER_LOGGED_IN',

payload: { user: 'Daniel' }

} store.dispatch(action)

Then, we listen for these actions in a reducer function and return a new state object based on the type of the action, payload data, and/or previous state of the store:

Note: Never mutate old state. Always return a new object instead.

To access our data, we must call store.getState() . After the action above is dispatched, store.getState().user will be 'Daniel' .

Once our store is set up, we can inject up-to-date state from the store into any component we like using the the connect function from react-redux. Maintaining this “single source of truth” solves many headaches that arise in passing state around an application.

To recap:

store : Single repository for all data

: Single repository for all data dispatch : A method of the store that we use to pass actions to a reducer in order to update stored data

: A method of the that we use to pass actions to a reducer in order to update stored data action : An object with a type property that describes something that should result in a change of state.

: An object with a property that describes something that should result in a change of state. reducer : A function that receives the current Redux state and the action dispatched, and returns a new state object that replaces the current state in the store.

: A function that receives the current Redux state and the action dispatched, and returns a new state object that replaces the current state in the store. connect : A higher-order function from react-redux that can wrap one of our components. connect takes up to two arguments, usually named mapStateToProps and mapDispatchToProps . We use these functions to tell Redux which pieces of state to pass to our component as props , and also to give us a convenient reference to the store’s dispatch method. See examples here.

Check out the cartoon guides to Flux and Redux for a friendly walkthrough of this pattern.

Add Redux to our app

In order to initialize Redux, we’ll need to create a store with a corresponding reducer. I want to get all the basic pieces in place before I begin fleshing out my reducer, so I’ll make a dummy reducer and we’ll come back to it. This one does nothing except return whatever state is currently held in the store (or an empty object if we don’t have any current state):

Dummy reducer

We’ll import our reducer into App.js and set up our Redux store:

Note that our app is wrapped in a <Provider> component. Under the hood, Provider uses react context to allow any child component to access the store.

Connect our Navigator

We’re now ready to connect our navigator to Redux. We’ll use the connect function from react-redux to include our Redux navigation state on our Navigator component’s props.

Then, we’ll add a navigation prop to our Navigator using a helper function from React Navigation called addNavigationHelpers . This will replace the default navigation prop that React Navigation passes to its child components:

Since we wrapped our Navigator in a component, we can `connect` it to Redux and inject our navigation state and our store’s `dispatch` method (`dispatch` is automatically added to our props when we use `connect`).

For those using React Navigation 1.0.0 and above please follow the additional setup steps: https://github.com/computerjazz/ReactNavigationRedux/issues/1#issuecomment-372718115

Give it a refresh and…

Whoops! We’re breaking things already. Unfortunately, a few changes have to land at the same time in order to get up and running, so we’ll have to go through a few rounds of breaking and fixing our app.

In this case, addNavigationHelpers is expecting a “valid” state object that contains an index and a routes array, which we need to provide now that we’ve replaced the default navigation prop. The state we’re currently passing to it is the result of our dummy reducer, which we initialized as an empty object.

We’ll fix this by providing our reducer with a valid default initialState . This could be a complex tree if we want to initialize our app en media res, but we’ll feed it an object that represents the initial route we defined in our StackNavigator .

Thankfully, we don’t need to manually create this object—React Navigation can do this for us. Remember earlier when we exported our bare Navigator as a named export? We’ll now import it into our reducer, along with the NavigationActions object from React Navigation.

When we call Navigator.router.getStateForAction() with an action of type NavigationActions.Init , we get back an object that represents our initial navigation state:

Remember, we exported our plain Navigator as a named export in Navigator.js, separate from the component-wrapped version.

If we log initialState we see an object with a shape that will become familiar:

Our navigation stack is initialized at index 0, which is the route with name ‘Feed’

Now, we can refresh our app and it won’t crash. However, tapping on a list item no longer routes to the correct screen:

This is because React Navigation now expects us to explicitly provide navigation state, and our current dummy reducer only returns the state that’s already in the store. Since we defaulted our state.navigation to initialState we’re stuck on the initial screen. Let’s go back and fix that now.

Before we replaced the navigation prop, React Navigation handled all calls to props.navigation.navigate() internally. Now, when we call props.navigation.navigate() from within our component, an action is dispatched to our store (remember that we provided our own store’s dispatch method to our navigator within addNavigationHelpers ).

Let’s update our reducer to return the correct navigation state. Since we have the handy getStateForAction function that returns a navigation state object based on the current state and the passed-in action, all we have to do is change one line,—the return value:

…and our app is back to behaving as expected:

Let’s take a moment to recap what we did: