Redux-like functional setState in React

Ernesto F
5 min readJul 6, 2017

While working on small feature for a big project I found myself trying to avoid keep adding to our current flux code. We are using alt (https://www.npmjs.com/package/alt) a flux implementation that is very feature complete and easy to use.

Being a small feature that was isolated from the rest of the app this seemed a fine candidate to try to avoid using any flux library at all and prove that for small SPAs (Single Page App) React’s own state management is more than enough. We were greatly motivated after reading a nice article about functional setState https://medium.freecodecamp.org/functional-setstate-is-the-future-of-react-374f30401b6b

What is functional setState?

React’s setState is a method used to asynchronously change the state of a react component. We can pass an object with bits of data we want to change and React will merge it to the current state, forcing a re-render of the component asynchronously.

Functional setState is the name some suggested while using the functional version of setState, which takes a function instead of an object in the form.

The complete function can be like this

const fn = (state, props) => ({...})

As you can see, both current state and props are passed and it can perfectly be an external function, with all the benefits that implies, as testing, composition, etc.

So, how to take this a step further? We will try to illustrate our point with the solution to the feature itself.

So, what where we trying to build? simple, a file uploader.

4 States for the file upload

This simple design shows 4 possible states of our component.

  1. Empty file drop zone.
  2. Immediately after dropping a file the component starts uploading it. A blue bar shows progress and a Cancel button allows us to cancel the operation while uploading.
  3. If any error occurs (Ex: connectivity issues) a the progress bar will turn red and we will have two choices, Retry upload or cancel and go back to step 1.
  4. Once completely uploaded the progress bar will turn green and we will show a Success button that will continue whatever our App whats to do with the uploaded file.

We had 2 basic requirements:

  1. The solution’s state should be predictable.
  2. The solution’s state management should be testable with 100% coverage.

Container and Visual components

This distinction is nothing new. You can find lots of good opinions online on how to differentiate components that manage data and visual component. We will follow the same pattern here.

So, our container component will look like this:

Note that being a container component no styles are applied. This component will render one of two components depending on current state, where current state is passed down as properties.

Redux-like state management

Redux (http://redux.js.org/) is a predictable state container for javascript apps. Heavily used across many organizations is kind of the de facto state management library for React.

With redux:

  1. The state of the app resides on a single object tree in a single store.
  2. The only way to change the object tree is to emit an action, which is an object describing what happened.
  3. To specify how this actions transforms the state, you need reducers, pure functions with the form: (currentState, action) => newState

We can accomplish the same features using functional setState, reducers are not exclusive of redux being just pure functions and action creators are also simple object creators describing state changes. Of course, the redux dispatcher, the method responsible of sending actions objects to the reducer(s) is a little bit different in this implementation, but, still complaint with the flux paradigm: actions -> dispatcher -> store -> view -> actions…

As you can see our uploader state management code has:

  1. Actions types: Ex: START_UPLOADING
  2. Initial state
  3. State reducer, so far similar that redux implementations.
  4. Actions creator: This one is a little bit different. This is a factory function, that receives an object with two functions, get & set. Internally has a dispatch function that encapsulates the functional setState approach
const dispatch = (type, params = {}) => set(state => reducer(state, {type, ...params}))

By calling dispatch(…) with the action type and its extra data (if necessary) we are actually calling the function set with our functional version of the reducer. Function set is no more that an abstraction so we can reuse this code in a different library other that React.

Also, note that the createActions function only gives us 3 interface functions to operate: retryUpload, cancelUpload and uploadFile, this should be the only 3 functions we actually need to link to our components.

Being this state management reusable, we need a wrapper for React components, so we can plug it and get the actions and properties we need.

connect will wrap our component by specifying the actions creator function and the initial state, will initialize the local state and actions accordingly and will pass down as properties to our component, all properties from the state, the actions functions and any passed through props.

The interesting part in the connect wrapper is the attach function, it returns an object with the two functions get & set our state management abstraction needs. get will just return the attached instance’s state. set will in the case of React use setState passing the state handling function and the resolve function from a promise, so, set will returns a promise with all the niceties this implies.

Where is the state being hold? In the wrapper component’s state.

To finish we need to wrap our FileUploader component.

And thats all. (Except for testing)

Testing.

Being the reducer a predictable pure function, testing it is basically providing actions for all the posible cases and asserting the results. Same as testing any redux reducer.

Testing actions is also simple, the action creator function only gave us 3 functions as interface. We need to mock the get & set functions so we can have good assertions.

This of course is not complete, testing could be time consuming.

My take

Flux is a nice data management design pattern. There are a lot of cool implementations of it, being redux the most popular.

Functional setState is part of React’s core, so you already have it at hand. Although it doesn’t replace a full library like redux is simple enough for isolated components or full single pages. Its testable and predictable at some degree (I still don’t have fully understanding on how setState works specially on async environments with lots of calls)

If you want to test the uploader code by yourself, be my guest: https://github.com/ernestofreyreg/file-uploader

btw I used nextjs (https://github.com/zeit/next.js/) and its amazing.

Edit: I took the connect wrapper code and made a npm module of it: https://www.npmjs.com/package/react-setstate-connect You are welcome to give your feedback or contribute. (https://github.com/ernestofreyreg/react-setstate-connect)

--

--