Sharing Code between React and React Native Apps part 2

In this article I am 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:

  1. import {  createStore } from "redux";
  2.  
  3. import reducer from "./../reducers";
  4. 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 store added:

  1. import React from 'react';
  2. import { Provider } from "react-redux"
  3. import ScreenView from './screenView';
  4. import store from './../../configureStore/';
  5.  
  6. export default class screenContainer extends React.Component {
  7.     render() {
  8.         return (
  9.             <Provider store={store}>
  10.                 <ScreenView/>
  11.             </Provider>
  12.         )
  13.     }
  14. }

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:

  1. mport { combineReducers } from "redux";
  2. import counterReducer from "./counterReducer";
  3. export default combineReducers({
  4.     counterReducer,
  5. });

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.

  1. export default function counterReducer(state = {
  2.         number: 0
  3.     }, action)
  4. {
  5.  
  6.     switch(action.type){
  7.         case 'INCREMENT':{
  8.             return {
  9.                 ...state,
  10.                 number: number ++
  11.             }
  12.         }
  13.         case 'DECREMENT':{
  14.             return {
  15.                 ...state,
  16.                 number: number --
  17.             }
  18.         }
  19.     }
  20.     return state;
  21. }

Breakdown of this code snippet: our reducer accepts two parameters. The first one is 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 payload. We call a switch statement on the action.type attribute. According to 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:

  1.  <View style={styles.counterWrapper}>
  2.                     <TouchableOpacity style={styles.button}>
  3.                         <Text style={styles.buttonText}> + </Text>
  4.                     </TouchableOpacity>
  5.                     <View style={styles.textWrapper}>
  6.                         <Text style={styles.mainText}> 0 </Text>
  7.                     </View>
  8.                     <TouchableOpacity style={styles.button}>
  9.                         <Text style={styles.buttonText}> - </Text>
  10.                     </TouchableOpacity>
  11.                 </View>

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

  1.  
  2.       counterWrapper: {
  3.         flexDirection: 'row',
  4.         marginTop: 20
  5.     },
  6.     button: {
  7.         backgroundColor: 'steelblue',
  8.         width: 50,
  9.         height: 50,
  10.         alignItems: 'center',
  11.         justifyContent: 'center',
  12.         borderRadius: 50,
  13.    },
  14.     buttonText: {
  15.         fontWeight: 'bold',
  16.         fontSize: 30,
  17.         color: '#fff'
  18.     },
  19.     textWrapper: {
  20.         alignItems: 'center',
  21.         justifyContent: 'center',
  22.         borderWidth: 1,
  23.         borderColor: '#000',
  24.         width: 50,
  25.         marginLeft: 10,
  26.         marginRight: 10
  27.     },
  28.     mainText: {
  29.         fontSize: 30,
  30.         fontWeight: 'bold'
  31.     }

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

  1. <div className="counter-wrapper">
  2.         <button className="button">
  3.                 <p className="button-text"> + </p>
  4.         </button>
  5.         <div className="text-wrapper">
  6.                 <p className="main-text"> 0 </p>
  7.         </div>
  8.         <button className="button">
  9.                 <p className="button-text"> - </p>
  10.         </button>
  11. </div>

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

  1. .counter-wrapper{
  2.     margin-top: 20;
  3. }
  4. .button{
  5.     width: 50px;
  6.     height: 50px;
  7.     border: 0;
  8.     background: none;
  9.     box-shadow: none;
  10.     border-radius: 50%;
  11.     background: steelblue;
  12. }
  13. .button-text{
  14.     font-size: 30px;
  15.     font-weight: bold;
  16.     color: #fff;
  17. }
  18. .text-wrapper{
  19.     display: inline-block;
  20.     width: 50px;
  21.     height: 50px;
  22.     border: 1px solid #000;
  23.     margin-left: 15px;
  24.     margin-right: 15px;
  25. }
  26. .main-text{
  27.     font-size: 30px;
  28.     font-weight: bold;
  29. }

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.

  1. import { connect } from "react-redux"

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

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

because it there can be only one export default per component, and 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 so many people, because in every single tutorial it's like they are trying to make this as hard to understand as possible, but it's the easiest function in the world. All this does is take a piece of your store and it passes it to your component as a prop.

  1. function mapStateToProps(state){
  2.     return{
  3.         number: state.counterReducer.number,
  4.     }
  5. }

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.

  1. function matchDispatchToProps(dispatch){
  2.     return {
  3.         incrementFunction: () => {
  4.             dispatch({type: "INCREMENT"});
  5.         },
  6.         decrementFunction: () => {
  7.             dispatch({type: "DECREMENT"});
  8.         }
  9.     }
  10. }

We are creating two new functions that will be forwarded via props to components, that 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 components, and that is done with the redux connect function:

  1. 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:

  1. <TouchableOpacity onPress={() => this.props.incrementFunction()} style={styles.button}>
  2.         <Text style={styles.buttonText}> + </Text>
  3. </TouchableOpacity>
  4. <TouchableOpacity onPress={() => this.props.decrementFunction()} style={styles.button}>
  5.         <Text style={styles.buttonText}> - </Text>
  6. </TouchableOpacity>

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

  1. <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

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


Conclusion

With this extended app we can now begin to see the power of this approach, that is that 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.

Boris Žmaher

back to top