Loading...

React Native - Redux

Our goal

In this tutorial we will go over how to combine React Native with Redux.

What is Redux

Redux is a predicatable state container. We can store the state of our app using redux, the state is stored in our redux store. The state can only be changed by calling store.dispatch(action) Where an action is a pure function describing what happened in our app. The store then decide how the state will change using a reducer. To practice react native with redux we will create a simple list application. Our app will contain a text field to enter a list item when submitting the text field we will add an item to our redux state. Our app will then display the list of items from the state.

Bootstrapping our app

Lets create a new react native using the react-native-cli. If it's not already installed then install the cli using npm:

        
            > npm install -g react-native-cli
            > react-native init RnRedux
        
    

You can now run your app on the emulator

        
            > react-native run-ios
        
    
Installing Redux

Using Redux with React-Native is similar to using Redux with React. Lets start by installing Redux

        
            > npm install redux --save
        
    

This library contains the core elements of redux, like the redux store. Now lets install the library that connects redux and react together.

        
            > npm install react-redux --save
        
    
Our state

Our state will contain an array of strings, from what the user typed in the text field. In the project root create a folder called redux In that folder create a file called actions.js We will have a single action to add an item to our array in the state. actions.js will look like this:

        
            export const ADD_ITEM = 'ADD_ITEM';

            export function addItem(item) {
                return {
                    type: ADD_ITEM,
                    payload: item
                }
            }
        
    

An action is a simple object with a key of type which is a simple string to identify the action, and another key of payload that contains extra data to pass to the reducer where the reducer will decide what to do with it, and in our case the reducer will add the item to the array of strings. Lets create our reducer, inside the redux folder we created earlier, create another file called reducer.js With the following code

        
            import {ADD_ITEM} from './actions';

            const initialState = {
                items: []
            }

            export function appReducer(state = initialState, action) {
                switch(action.type) {
                    case ADD_ITEM:
                        return {
                            ...state,
                            items: [...state.items, action.payload]
                        }
                    default:
                        return state;
                }
            }
        
    

As you can see a reducer is a simple pure function that get the current state as first argument, the action issued as the second parameter and this function should decide how the next state will look like. The state is immutable so we duplicate the state with the spread operator and duplicate the array and add the new item from the action payload. The reducer is also a good place to set our initial state, when redux will init the store, it will call our reducer with an undefined state, which means the initial state will be returned. Lets create our redux store. Create a file called store.js in the redux folder with the following code

        
            import {createStore} from 'redux';
            import {appReducer} from './reducer';

            export const store = createStore(appReducer);
        
    

We are calling the redux createStore, and we pass our reducer. This will also be the place to add middlewares like redux dev tools, or redux thunk. Middlewares help us expend the power of our store.dispatch We now need to connect our redux to our react native

React Components

Before connecting the store to react, lets first create our components. Our app will have 2 components, one with a text input that is in charge of adding items to the state. Another component is in charge of displaying the list of items. Create a file in the root directory of the project called ItemForm.js With the following code

        
            import React, {Component} from 'react';
            import { TextInput } from 'react-native';

            export default class ItemForm extends Component {
                addItem = (event) => {
                    const text = event.nativeEvent.text;
                    console.log(text);
                }
                
                render() {
                    return (
                        <TextInput 
                            placeholder="Add Item"
                            style={{borderColor: 'black', borderWidth: 2, width: '90%', height: 50, fontSize: 20, paddingLeft: 10}}
                            onSubmitEditing={this.addItem} />
                    )
                }
            }
        
    

The following code created a new react component. The render method places a TextInput We are connecting to the submit event and activating the addItem method. In the addItem method we are grabbing the text in the input and printing it to the log. We will later on, fill the addItem method and add items to the state.

Time to create the second component that displays the list of items from the state. For now we won't connect the component to the state yet, and we will just create an hard coded list. Create another file called: ItemsList.js with the following code:

        
            import React, { Component } from 'react';
            import { FlatList, Text, View } from 'react-native';

            export default class ItemsList extends Component {
                render() {
                    return (
                        <FlatList 
                            data={[
                                'item1',
                                'item2',
                                'item3',
                                'item4',
                            ]}
                            renderItem={({item}) => (<Text>{item}</Text>)}
                            />
                    )
                }
            }
        
    

We are using a FlatList to display our list. The FlatList will render only the items viewable on screen. The FlatList requires a data source which we are passing an array of strings, and also a render function for each item from the source.

Connecting components to state

Time to make our components a bit smarter and connect them to the state. We will start by connecting the ItemForm.js to the addItem action we created earlier. Modify the ItemForm.js:

        
            import React, {Component} from 'react';
            import { TextInput } from 'react-native';
            import { connect } from 'react-redux';
            import { addItem } from './redux/actions';

            class ItemForm extends Component {
                addItem = (event) => {
                    const text = event.nativeEvent.text;
                    console.log(text);
                    this.props.addItem(text);
                    this.textInput.setNativeProps({ text: ' ' });
                    setTimeout(() => {
                        this.textInput.setNativeProps({ text: '' });
                    },3);  
                }
                
                render() {
                    return (
                        <TextInput 
                            placeholder="Add Item"
                            ref={input => this.textInput = input}
                            style={{borderColor: 'black', borderWidth: 2, width: '90%', height: 50, fontSize: 20, paddingLeft: 10}}
                            onSubmitEditing={this.addItem} />
                    )
                }
            }

            export default connect(
                null,
                (dispatch) => ({
                    addItem: (item) => dispatch(addItem(item)) 
                })
            )(ItemForm)
        
    

The react-redux connect method job is to connect part of the state to the component properties, or connect the dispatch with the actions and set them to the component properties. Since we don't need nothing from the state and we would just like to connect the actions to the props, the first argument of the connect is null, and the second is a function to connect the dispatch to the component properties. Now lets connect the state to the list component. Modify the ItemsList.js to the following:

        
            import React, { Component } from 'react';
            import { FlatList, Text, View } from 'react-native';
            import { connect } from 'react-redux';

            class ItemsList extends Component {
                render() {
                    return (
                        <FlatList 
                            data={this.props.items}
                            renderItem={({item}) => (<Text>{item}</Text>)}
                            />
                    )
                }
            }

            export default connect(
                state => ({
                    items: state.items
                }),
                null
            )(ItemsList);
        
    

Again we are using the connect method to connect the state and transfer properties from the state to the component.

Provider

The Provider is a component in react-redux, which job is to wrap the entire react application and pass the store down to every component. Modify the App.js to look like this:

        
            import React, {Component} from 'react';
            import {StyleSheet, View} from 'react-native';
            import ItemForm from './ItemForm';
            import ItemsList from './ItemsList';
            import { Provider } from 'react-redux';
            import { store } from './redux/store';

            export default class App extends Component {
            render() {
                return (
                <Provider store={store}>
                    <View style={styles.container}>
                    <ItemForm />
                    <ItemsList />
                    </View>
                </Provider>
                );
            }
            }

            const styles = StyleSheet.create({
            container: {
                flex: 1,
                justifyContent: 'center',
                alignItems: 'center',
                backgroundColor: '#F5FCFF',
            }
            });
        
    

We are wrapping our entire app with the Provider component and passing the store to that component so the store will be available in our entire application. You can now launch your app and examine how you can add item in the text input and the list is populated with values.

Debugging Redux

When our app will get more complex, so will our state. When our state becomes more complicated, it becomes crucial to be able to conveniently debug the state, and know on every moment what actions happened and what is the current state. For this we can use remote-redux-devtools. Install it with npm:

        
            > npm install remote-redux-devtools --save-dev
        
    

Modify the store.js file we created earlier

        
            import {createStore} from 'redux';
            import {appReducer} from './reducer';
            import devToolsEnhancer from 'remote-redux-devtools';

            export const store = createStore(appReducer, devToolsEnhancer());
        
    

Now you can visit the following url: http://remotedev.io/local/ and you should see the state of your app in that url.

Summary

Using Redux with react native is just as simple as using it on the web. Redux is almost a must for every application which will grow and will have a complex app . Although this app was quite simple but the state will grow with the app, and you can also split your state to multiple sections and multiple reducers. Debugging the state is also easy with remote redux devtools.