Immutability in JavaScript using Redux
BY David XU - DEVELOPER At Toptal
In an ever growing ecosystem of rich and complicated JavaScript applications, there’s more state to be managed than ever before: the current user, the list of posts loaded, etc.
Any set of data that needs a history of events can be considered stateful. Managing state can be hard and error prone, but working with immutable data (rather than mutable) and certain supporting technologies- namely Redux, for the purposes of this article- can help significantly.
Immutable data has restrictions, namely that it can’t be changed once it’s created, but it also has many benefits, particularly in reference versus value equality, which can greatly speed up applications that rely on frequently comparing data (checking if something needs to update, for example).
Using immutable states allows us to write code that can quickly tell if the state has changed, without needing to do a recursive comparison
The key aspect that separates Redux from most other state containers such as MobX, Relay, and most other
Dealing
- Time travel (Going back in time to a previous state)
- Logging (Track every single action to figure out what caused a mutation in the store)
- Collaborative environments (Such as GoogleDocs, where actions are plain JavaScript objects and can be serialized, sent over the wire, and replayed on another machine)
- Easy bug reporting (Just send the list of actions dispatched, and replay them to get the exact same state)
- Optimized rendering (At least in frameworks that render virtual DOM as a function of state, such as React: due to immutability, you can easily tell if something has changed by comparing references, as opposed to recursively comparing the objects)
- Easily test your reducers, as pure functions can easily be unit tested
Redux’s action creators help in keeping code clean and testable. Remember that actions in Redux are nothing more than plain JavaScript objects describing a mutation that should occur. That being said, writing out the same objects over and over again is repetitive and error prone.
An action creator in Redux is simply a helper function that returns a plain JavaScript object describing a mutation. This helps reduce repetitive code, and keeps all your actions in one place:While the very nature of reducers and actions make them easy to test, without an immutability helper library, there’s nothing protecting you from mutating objects, meaning the tests for all your reducers have to be particularly robust.
Consider the following code example of a problem you’ll run into without a library to protect you:In this code example, time travel will be broken as the previous state will now be the same as the current state, pure components may potentially not update (or re-render) as the reference to the state has not changed even though the data it contains has changed, and mutations are a lot harder to reason through.
Without an immutability library, we lose all the benefits that Redux provides. It’s therefore highly recommended to use an immutability helper library, such as immutable.js or seamless-immutable, especially when working in a large team with multiple hands touching code.
Regardless of which library you use, Redux will behave the same. Let’s compare the pros and cons of both so that you’re able to pick whichever one is best suited for your use case:Immutable.js is a library, built by Facebook, with a more functional style take on data structures, such as Maps, Lists, Sets, and Sequences. Its library of immutable persistent data structures
Pros:
- Structural sharing
- More efficient at updates
- More memory efficient
- Has a suite of helper methods to manage updates
Cons:
- Does not work seamlessly with existing JS libraries (i.e
lodash ,ramda ) - Requires conversion to and from (toJS / fromJS), especially during
hydration / dehydration and rendering
Seamless-immutable is a library for immutable data that is
It’s based on ES5 property definition functions, such defineProperty(..)
Pros:
- Works seamlessly with existing JS libraries (i.e
lodash ,ramda ) - No extra code needed to support conversion
- Checks can be disabled in production builds, increasing performance
Cons:
- No structural sharing -
objects / arrays are shallow-copied, makes it slower for large data sets - Not as memory efficient
combineReducers
currentUser
postsList
By default, you can only dispatch plain JavaScript objects to Redux. With middleware, however, Redux can support impure actions such as getting the current time, performing a network request, writing a file to disk, and so on.
‘Middleware’ is the term used for functions that can intercept actions being dispatched. Once intercepted, it can do things like transform the action or dispatch an asynchronous action, much like middleware in other frameworks (such as Express.js).
Two very common middleware libraries are Redux Thunk and Redux-saga. Redux Thunk is written in an imperative style, while Redux-saga is written in a functional style. Let’s compare both.Redux-saga supports impure actions through an ES6 (ES2015) feature called generators and a library of
Let’s see how we can improve readability and testability of the previous thunk method using sagas!
First, let’s mount the Redux-saga middleware to our store:Note that run(..)
We defined two generator functions, one that fetches the users list and rootSaga
api.fetchUsers
rootSaga
yields a single call to a function takeEvery,
USERS_FETCH
fetchUsers
Let’s see how generators make our sagas easy to test. We’ll be using mocha in this part to run our unit tests and
call
take
put
While Redux isn’t tied to any specific companion library, it works especially well with React.js since React components are pure functions that take a state as input and produce a virtual DOM as output.
React-Redux is a helper library for React and Redux that eliminates most of the hard work connecting the two. To most effectively use React-Redux, let’s go over the notion of presentational components and container components.
Presentational components describe how things should look visually, depending solely on their props to render; they invoke callbacks from props to dispatch actions. They’re written by hand, completely pure, and are not tied to state management systems like Redux.
Container components, on the other hand, describe how things should function, are aware of Redux, dispatch Redux actions directly to perform mutations and are generally generated by React-Redux. They are often paired with a presentational component, providing its props.React-Redux provides a helper function connect( .. )
React emphasizes extensibility and re-usability through composition, which is when you wrap components in other components. Wrapping these components can change their behavior or add new functionality. Let’s see how we can create a higher order component out of our presentational component that is aware of Redux - a container component.
Here’s how you do it:Note that we defined two functions, mapStateToProps
mapDispatchToProps
mapStateToProps
is a pure function of (state: Object) that returns an object computed from the Redux state. This object will be merged with the props passed to the wrapped component. This is also known as a
mapDispatchToProps
is also a pure function, but one of (dispatch: (Action) => void) that returns an object computed from the Redux dispatch function. This object will likewise be merged with the props passed to the wrapped component.
Provider
component in React-Redux to tell the container component what store to use: Provider
Hey there!!! Great Article and also i know a company which have a great team of dedicated and professional react js developers. Check out this: React JS
Hey there!!! Great Article and also i know a company which have a great team of dedicated and professional react js developers. Check out this: React JS
Very useful information