React Redux: Immutable JS

As we all probably know by now, Javascript was not built with immutability in mind. For this reason, in our reducers, we usually copy over the state using object.assign or a spread operator to copy over the current state to a new state and append to it. This method is ok UNLESS our state is very big. In big apps, when we continue appending to our state in a session, our state keeps growing and growing, keeping data we no longer need. This is where immutable JS comes in. Two main persistent immutable data structures that immutable js offers are Map (for objects) and List (for arrays).

Install

npm install --save immutable

Object/Array vs Map/List

Map

Javascript Object:

const obj = {name: 'Rochelle', surname: 'Cassar', parents: {mother: 'Pauline', father: 'France'}}

obj.name //Rochelle
obj.parents.mother //Pauline
obj.name = 'Rochelle Claire'

Immutable JS Map:

const obj = Map({name: 'Rochelle', surname: 'Cassar'})

obj.get('name') //Rochelle
obj.getIn(['parents','mother']) //Pauline
obj.set('Rochelle Claire')

List

Javascript Array:

const arr = ['Rochelle', 'Cassar']

arr[0] //Rochelle
arr[0] = 'Rochelle Claire'

Immutable JS List:

const arr = List(['Rochelle', 'Cassar'])

arr.get(0) //Rochelle
arr.set(0, 'Rochelle Claire')

In order to avoid an extra learning curve, immutable JS lists have all the normal Array methods you’re used to like size, push, pop, includes, reverse and unshift.

toJS

toJS helps us changing an immutable JS Map/List to a plain javascript object/array accordingly.

fromJS

On the contrary, fromJS helps us changing a plain javascript object / array to immutable JS Map/List.

merge

Immutable JS uses merge to merge objects together:

const objFirst = {name: 'Rochelle'}
const objSecond = {surname: 'Cassar'}
const obj = objFirst.merge(objSecond)

Immutable Reducer States

userReducer.js

import { Map, fromJS } from 'immutable'
const initialState = Map({
  isFetching: true,
  error: '',
})

export default function users (state = initialState, action) {
  switch (action.type) {
    case FETCHING_USERS :
      return state.merge({
        isFetching: true
      })
    caseFETCHING_USERS_SUCCESS :
      return state.merge({
        error: '',
        isFetching: false,
        users: action.users
      })
    default :
      return state
  }
}

In Container:

Since immutablejs has a different way of accessing properties, we need to also make sure that our mapStateToProps is using .get instead:

function mapStateToProps ({users}, props) {
  return {
    user: users.get(props.uid)
  }
}

Result:

If you use chrome redux extension or place a debugger in the mapStateToProps before the return, you can see that the state is always clean, consisting of the initialState plus the state we append in the reducer action. This was, any old state we do not need, is cleared out.

Advertisements

React + Redux: Managing State

Store

Redux allows us to manage state within our app. Lets starts by installing redux by using npm:

npm install --save redux
npm install --save-dev babel-plugin-transform-class-properties

In our app folder, lets create a folder called redux and another folder inside redux called modules. Create a js file called users inside modules.

In our users.js file we need to specify:

  • constants (in case we would like to re-use our action types)
  • action creators
  • initial states
  • reducers

Continue reading

React: configuring webpack.config.js for production

First of all, here we will be using es6 syntax so we will need to rename our webpack.config.js file to webpack.config.babel.js so that it can be compiled.

Once we do so, we can use import instead of require and export default {} instead of module.exports = {}

Then we need to add the following:

Import webpack:

import webpack from 'webpack'

Check for production:

const LAUNCH_COMMAND = process.env.npm_lifecycle_event

const isProduction = LAUNCH_COMMAND === 'production'

const productionPlugin = new webpack.DefinePlugin({
  'process.env': {
    NODE_ENV: JSON.stringify('production')
  }
})

Add common properties such as ‘base’, ‘output’, and ‘module’ in base json:

const base = {
  entry: [ ... ],
  output: { ... },
  module: {
    loaders: [ ... ]
  }
}

Specify dev and production configs:

const devConfig = {
  plugins: [...]
}

const productionConfig = {]
  plugins: [productionPlugin, ...]
}

Merge objects to export:

export default Object.assign({}, base,
  isProduction === true
    ? productionConfig
    : devConfig
)

Using eslint with react

When developing a big project in React, it is important to maintain some standard code practises. This is where lint comes in. I will be using the common standardjs.com rules in the article.

NPM

In your project install the following commands using npm:

npm install --save-dev eslint eslint-{config-standard,plugin-standard,plugin-promise,plugin-import,plugin-node,plugin-react}
npm install --save-dev babel-eslint@next

.eslintrc

In your project root add a file called ‘.eslintrc’ and add the following json:

{
  parser: "babel-eslint",
  env: {
    es6: true,
    "browser": true
  },
  parserOptions: {
    ecmaversion: 6,
    sourceType: "module",
    ecmaFeatures: {
      jsx: true,
      experimentalObjectRestSpread: true
    }
  },
  extends: ["eslint:recommended","plugin:react/recommended","standard"],
  plugins: [
    "react"

  ]
}

Parser will be used to specify that we need to parse lint using babel, for which we installed the babel-eslint@next in npm.

Env and parserOptions are optional and will simply specify the env options that lint may encounter during parsing.

Extends is the most important since it will specify what kind of rules will be set. You can use only “standard” if you would like however both eslint and react lint plugin come with inbuilt recommendations that I decided to use above too.

Plugins above is simply there to tell lint that react is used to avoid getting react errors such as unused imports etc.

package.json

In our package.json, add 2 scripts that will help you use lint quicker:

"scripts": {
  "lint": "eslint app/.",
  "fix": "eslint --fix app/.; exit 0;"
}

“lint” script will check for errors while “fix” script will TRY to auto fix as many errors/warnings as possible such as end of file line, extra spacings, etc.