Thinking in Redux (when all you’ve known is MVC)

53,839 reads

When we at Spoil decided to roll out our mobile app, one of the first decisions we had to make was: what language do we use? After some deliberation, the decision was made: React-Native it was going to be. Learning a new “language” or framework isn’t a huge issue, but man oh man, did react-native and redux give me a hard time. This article doesn’t explain how React-Native works (as that is not the hard part). The purpose of the next few paragraphs is to help anybody reading this transition from “thinking in MVC” to “thinking in Redux”. Hope it helps.

React-Native and Redux?

As soon as you start learning about react-native (or react), you are about 3 stack overflow questions or medium posts away before somebody mentions redux.

You were so happy. You started to understand state vs props, you know what componentDidMount does and you even understand how to properly create your components so they are re-usable. Now all of a sudden you found yourself on egghead.io, and some guy is talking about stores, reducer compositions, actions and mapping state to props.

You also realize that while before you were able to do:

$(“.my-button”).click();

to get a button to do something; it has now been about 3 hours and your one button doesn’t do anything.

Some Analogies

If you are coming from an MVC (or MVVC) world, you are used to models, views and controllers (duh). However in Redux we are dealing with actions, reducers, stores and components. Trying to “translate” MVC to Redux is tricky but here is how I would do it:

Actions = Controller. Think of your actions as the controller. Whenever you want something to happen in your app (i.e. load some data, change a isLoading flag from true to false…) you will have to dispatch an action. Just like in MVC where you would have to call a controller endpoint.

Reducer = Model. Sort of. Your reducers will be in charge of holding the current state of your application (i.e. user info, information loaded from the api, items you want to display…). It will also be the part that decides what to do when an action is called. While in MVC you might have a model with the method setName(), with Redux you would have a reducer handle an action to set the name in the state.

Edit thanks to Prabin Varma. This explains the Store much better.

Stores = ???. The store is Redux specific and doesn’t really have an equivalent in MVC. Not to worry though. This part is taken care off behind the scenes. The store is like a container for the state that aggregates all of the reducers. It has a method to the get the current state, and exposes ways to subscribe to the state changes (using the “connect()” method). This is what will allow you to call actions and pass them in as props to your components.

Components = Views. Components are kind of like your smart views. They display the information that they get from the state. I recommend splitting up your components into two parts. One just for the presentational part (dumb components) and one to handle all of the actions and state changes (smart components).

Moving From MVC Thinking to Redux Thinking

One of the main differences between MVC and Redux is that, while in MVC data can flow in a bidirectional manner, in Redux it strictly moves in one direction.

Typical MVC. When life was easy.

As you can see (and know from experience) in the diagram above data can flow two ways. You press a button in your view, it sends a message to the controller and that updates the model. The model changes some value, returns the value to the controller, and the controller refreshes the view. Easy peezy!

Redux flow. Life sucks now

With Redux things work a little differently. Let’s say you have a component and you want to do something when a button gets pressed. Where do you start? Here is how I go about it.

Define your Action Define your Reducer Define the Actions as a Prop in your Component Wire it up in your View

Here is a simple code example to explain these concepts. In this example I will show how to edit a text input and when a user presses a button it will call an action to save it.

First let’s start with the Action file.

export const MODIFY_NAME = "MODIFY_NAME";

export const SAVE_NAME = "SAVE_NAME";

/**

* This is the action we will call from our component whenever the user presses a button. Literally every letter that they will type, this action will be called with the new value in the text input field. Pay attention to the type and payload in this function. This is what we will use in the reducer to "modify" our model with the new values.

**/

export function modifyName(name){

return {

type: MODIFY_NAME,

payload:{

name

}

}

}

/**

This is the action we will call when the user presses the save name button. Notice how we don't pass any value in. That is because the reducer already holds that value. Also there is no payload. The reason for that is the reducer doesn't need one. There is no extra information needed for the reducer step.

Also, normally this would call an api endpoint and all that jazz, but for brevity's sake I won't include that.

**/

export function saveName(){

return {

type: SAVE_NAME

}

}

Now onto our Reducer. Basically, the reducer has to handle the actions that come in.

//import the actions file we defined earlier

import * as constants from '../actions.js';

/**

The initial state is used to define your reducer. Usually you would just set this to default values and empty strings. The reason this is needed is so that when using these values you are guaranteed to at least have some default value. Think of it as the default constructor.

**/

const initialState = {

name:'',

isSaved: false

}

/**

This action part is the part that will "listen" for emitted actions. So the saveName and modifyName functions that we defined earlier will be handled in here. The action parameter is what is being returned (the type and payload) in the functions above.

**/

function name(state=initialState,action){

switch (action.type){

/**

in REDUX the state is immutable. You must always return a new one, which is why use the ES6 spread operator to copy the values from the states that's passed in.

**/

case constants.MODIFY_NAME:

return {

...state,

name:action.payload.name

}

case constants.SAVE_NAME:

return {

...state,

isSaved:!state.isSaved

}

}

}

export default name;

Notice how the constants.MODIFY_NAME and constants.SAVE_NAME are exactly what is being returned by our actions in the type field. That is how you let the reducer know what action is happening.

Now to define our “smart” component. Really all this means is this is the component that will define the call to the actions.

/**

* The home page for the app

*/

‘use strict’;

import React, { Component } from ‘react’;

import { connect } from ‘react-redux’;

import Name from ‘./presentational/Name’;

import * as actions from ‘./actions/name’;

/**

Both the actual values (name and isSaved) as well as the function to call those actions are passed in as props.**/

class NameContainer extends Component {

render() {

return (

<Name

name = {this.props.name}

isSaved = {this.props.isSaved}

modifyName = {this.props.modifyName}

saveName = {this.props.saveName}

/>

);

}

}

/**

All this does is get the values that are saved in the reducer, and return it to the component so that we can call them using this.props

**/

const mapStateToProps = (state,ownProps) =>{

/**

using REDUX stores, it allows us to just access the reducer values by going state.name. Notice how name is what is being exported in the reducer above

**/

const { name, isSaved } = state.name;

return {name,isSaved };

}

/**

In mapStateToProps we were mapping the state variables as properties to pass into our presentational component. In mapDispatchToProps we are mapping the action functions to our container to be able to pass it into our presentational component.

**/

const mapDispatchToProps = (dispatch) => {

return {

modifyName:(name)=>{

dispatch(actions.modifyName(name))

},

saveName:()=>{

dispatch(actions.saveName())

},

}

/**

This is the reason we are able to pass in the functions and variables as props to our container. It's the connect function from the react-redux library that does all the magic.

**/

export default connect(mapStateToProps,mapDispatchToProps)(NameContainer);

Now for the easiest part. You create your presentational component that the user will interact with (the V in MVC).

/**

* the dumb component which will use the props passed in from the smart component based on a user's actions

*/

‘use strict’;

import React, { Component } from ‘react’;

import {Text,TextInput, TouchableOpacity} from ‘react-native’;

import { Actions, ActionConst } from ‘react-native-router-flux’;

class Name extends Component

render() {

return (

<View>



<TextInput

multiline={false}

//from the component above

value={this.props.name}

placeholder=”Full Name”

//from the component above

onChangeText={(name)=>this.props.modifyName(name)}

/>



<TouchableOpacity onPress= {()=>{this.props.saveName()}>

<Text>Save</Text>

</TouchableOpacity>

</View>

);

}

}

export default Name;

And that’s it! You still have to do some basic boilerplate setup stuff, but I hope this clears up how to think in redux.

This was something that tripped me up for a little while (i.e. what information was being passed where and how…) so I’m hoping to save you guys some time and heartache.

If you want to see what my team and I built using React-Native and Redux check out our app Spoil.

If you enjoyed the read, please just hit the ❤️ below so other people will see this here on Medium.

My Twitter: @mlevkov

Join my newsletter below and get more cool things 👇

Tags