Tell us about your project

Two subspace tunnels intercrossing and elements traveling in them colliding creating a small outburst

In this article, I`m going to expand the Example app by adding Redux logic, and create simple actions to the Apps, both on the Web version and the Native version.

Example app

So we are going to continue extending the App with increment and decrement of a number with Redux.

We must add new packages to our App, and those are:

$ yarn add react-redux redux

 

 

In the app/ folder we'll create a configureStore/ folder and inside create an index.js file. Inside the index.js file we'll put the following code:

import {  createStore } from "redux";
 
import reducer from "./../reducers";
export default createStore(reducer);

We create a store by calling the createStore function and supply it with reducers (that we will create later).

The next thing to do is to add the created store to our App, and that is done in the app/components/appscreen/index.js file, where we have to import a Provider from react-redux and then wrap our root component inside the Provider with a store added:

import React from 'react';
import { Provider } from "react-redux"
import ScreenView from './screenView';
import store from './../../configureStore/';
 
export default class screenContainer extends React.Component {
    render() {
        return (
            <Provider store={store}>
                <ScreenView/>
            </Provider>
        )
    }
}

The important thing is that the Provider must always have a store applied. We have imported our store from the configureStore/ folder.

Now we can move on to creating a Reducer. Start by creating a new folder reducers/ inside the app/ folder. In this folder, we create an index.js file and a counterReducer.js file. Index.js is the main file where all our Reducers are joined together using the combineReducers function. There can be many Reducers added, but in this article, we will have only one:

import { combineReducers } from "redux";
import counterReducer from "./counterReducer";
export default combineReducers({
    counterReducer,
});

In the file counterReducer.js we'll create our first Reducer. Reducers specify how the application's state changes in response to actions sent to the store.

export default function counterReducer(state = {
    number: 0
}, action) {
    switch (action.type) {
        case 'INCREMENT': {
            return {
                ...state,
                number: state.number + 1
            };
        }
        case 'DECREMENT': {
            return {
                ...state,
                number: state.number - 1
            };
        }
        default:
            return state;
    }
}

Breakdown of this code snippet: our Reducer accepts two parameters. The first one is a state, with which we initialize the defaults of the App's state, so by that our state at the beginning of the App creates an object with the counterReducer item and it has a number attribute (state.counterReducer.number). We could have as many of these objects as we have Reducers, and each of those Reducers would only be responsible for one piece of state. The second parameter is action, and this object must always have an attribute conveying the action type. An additional attribute can be the payload. We call a switch statement on the action.type attribute. Depending on what the action is, we'll return the new App's state.

We'll now go the screenViews and add new components, two buttons and a display for our number. In the screenView.native.js file below the Lorem Ipsum text we add the new piece of code:

<View style={styles.counterWrapper}>
  <TouchableOpacity style={styles.button}>
    <Text style={styles.buttonText}> + </Text>
  </TouchableOpacity>
  <View style={styles.textWrapper}>
    <Text style={styles.mainText}> 0 </Text>
  </View>
  <TouchableOpacity style={styles.button}>
    <Text style={styles.buttonText}> - </Text>
  </TouchableOpacity>
</View>

and in StyleSheet.create we add style for that piece of code:

   counterWrapper: {
        flexDirection: 'row',
        marginTop: 20
    },
    button: {
        backgroundColor: 'steelblue',
        width: 50,
        height: 50,
        alignItems: 'center',
        justifyContent: 'center',
        borderRadius: 50,
   },
    buttonText: {
        fontWeight: 'bold',
        fontSize: 30,
        color: '#fff'
    },
    textWrapper: {
        alignItems: 'center',
        justifyContent: 'center',
        borderWidth: 1,
        borderColor: '#000',
        width: 50,
        marginLeft: 10,
        marginRight: 10
    },
    mainText: {
        fontSize: 30,
        fontWeight: 'bold'
    }

We'll add the same elements to screenView.js, but with React JS style of HTML:

<div className="counter-wrapper">
  <button className="button">
    <p className="button-text"> + </p>
  </button>
  <div className="text-wrapper">
    <p className="main-text"> 0 </p>
  </div>
  <button className="button">
    <p className="button-text"> - </p>
  </button>
</div>

For the Web version we must add the new styles in public/css/styles.css

.counter-wrapper{
    margin-top: 20;
}
.button{
    width: 50px;
    height: 50px;
    border: 0;
    background: none;
    box-shadow: none;
    border-radius: 50%;
    background: steelblue;
}
.button-text{
    font-size: 30px;
    font-weight: bold;
    color: #fff;
}
.text-wrapper{
    display: inline-block;
    width: 50px;
    height: 50px;
    border: 1px solid #000;
    margin-left: 15px;
    margin-right: 15px;
}
.main-text{
    font-size: 30px;
    font-weight: bold;
}

Now that we've added the new elements, it's time to connect the components with Redux and create our logic. For both screenView files, we import the connect function, a function that connects our Redux store to our component, so we can access the store object in components' props:

import { connect } from "react-redux"

For both files we'll remove "export default" from the following line:

export default class Screen extends React.Component {
class Screen extends React.Component {

because there can be only one export default per component. Since we are going to use it in another place, we need to get rid of it here. At the end of both files, we'll add two important functions mapStateToProps and matchDispatchToProps.

MapStateToProps is a function that confuses many people because in every single tutorial it's like they are trying to make this as hard to understand as possible. In fact, it's the easiest function in the world. All it does is to take a piece of your store and it passes it to your component as a prop:

function mapStateToProps(state){
  return{
     number: state.counterReducer.number,
  }
}

We took a piece of state that counterReducer is responsible for (that piece was initialized when creating a reducer) and passed it to the component via props.

MatchDispatchToProps sounds confusing, they name those functions so confusing, but they are so easy to understand, it's kind of annoying. So all we are doing here is passing actions to a component again as a prop:

function matchDispatchToProps(dispatch){
    return {
        incrementFunction: () => {
            dispatch({type: "INCREMENT"});
        },
        decrementFunction: () => {
            dispatch({type: "DECREMENT"});
        }
    }
}

We are creating two new functions that will be forwarded via props to components, which then will be triggered on button press. Those functions call function dispatch. Dispatch is the only way to trigger a state change. In this case, we are dispatching an action.type to Reducers, which will trigger one of the cases in the counterReducer, and with that change the state.

Before we can add a trigger to the button, we must connect mapStateToProps, matchDispatchToProps, and its components, and that is done with the redux connect function:

export default connect(mapStateToProps, matchDispatchToProps)(screen);

Now we'll add a trigger to buttons on Web and Native Apps. In screenView.native.js we'll change both lines where we have TouchableOpacity. Add onPress to those lines like this:

<TouchableOpacity onPress={() => this.props.incrementFunction()} style={styles.button}>
  <Text style={styles.buttonText}> + </Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => this.props.decrementFunction()} style={styles.button}>
 <Text style={styles.buttonText}> - </Text>
</TouchableOpacity>

The last thing to do is to wire up the display number, change the line with mainText to:

<Text style={styles.mainText}> {this.props.number} </Text>

The same thing must be done for the Web version as well. The only change is that in the Web version we use onClick and a button, instead of onPress and TouchableOpacity:

<button className="button" onClick={() => this.props.incrementFunction()}>
  <p className="button-text"> + </p>
</button>
<button className="button" onClick={() => this.props.decrementFunction()}>
 <p className="button-text"> - </p>
</button>
<p className="main-text"> {this.props.number} </p>
With coding done now we start up both apps
React js: gulp serve
React Native: react-native run-android / run-ios

Conclusion

With this extended App, we can now begin to see the power of this approach -  we didn't have to write duplicate code when creating a store and creating Reducers. This is good when we have multiple Reducers. In the next article, we will add more reducers, and we will see what is middleware and how it's used.

 

Similar content can be found on: Sharing Code between React and React Native Apps part 1

 


Boris Žmaher