Thoughts on Redux

I have been working on my first project with Redux for the last few weeks. An admin interface to manage and create questionnaires about patients data collection.

When writing a small application, everything seems easy and clear, if the application starts to grow and new features are added, obstacles arise. I had the feeling of writing the same code over and over again. Mostly for CRUD operations.

The first problem was to denormalize the entire state (the use of the library “normalizr” is encouraged by redux docs, I didn’t know it when I started and I did it manually) in a way to split it in small pieces, each with a reducer. This improves the ease to handle the state, but handling the relationships through ids by hand adds complexity to the reducers especially when deleting records.

During the development I had to refactor at least three times the structure to respond to new required functionalities. Refactoring has been an hard work with redux, since each action and the reducers (so the related tests) must be rewritten manually to adapt the changes. I didn’t find anything to automate these tasks and I really missed a kind of a framework.

Another thing I found frustrating, it’s the need to create different actions for every single state attribute’s change. To solve this problem I finally wrote more generic actions that can serve for different attributes.

Test driven development with redux surprised me in a positive manner. It’s useful and speeds up a lot the development process.

Demo

Summary of my pros and cons

Pros

  • Test driven development is made easy, thanks to pure functions, and increases the development productivity
  • Only the root component (I call it container) is connected to the reducer, all the actions and state are passed through props. This makes it easy to use component composition and write stateless components.
  • Code linearity, everything is simple and doesn’t differ much from one project to another.
  • Immutability: forcing to keep an immutable state helps a lot avoiding weird bugs.
  • The Log middleware in dev mode, showing all the different states before and after an action is dispatched, is of a great help.

Cons

  • It’s difficult to handle relationships and there isn’t any official library with documentation and support to help you with it.
  • Redundant code, every action is written manually, even the most common, like changing an attribute of the state.
  • Normalizing a complex state with many level of nested objects doesn’t always seem the best approach.

My best practices

  • Directory structure by module instead of scope. Example:

foo/

….actions.js

….reducer.js

….tests__/

….constants.js

bar/

….actions.js

….reducer.js

….__tests__/

….constants.js

  • Divide the logic between server and client. My mistake was to implement all the logic on client side. A better approach it could be to retrieve the new state from the server when necessary and handle the state relationships on the database layer.
  • TDD on reducers, tests on reducers not only speed up the development but also cover you on possible “silent” bugs on the state.
  • Keep components simple and use component composition.
  • Normalize the state with the use of Reselect library

Handling complex store relationships (#386)

Note from Dan Abramov

Deleting is always tricky because there is no first class notion of a schema. We don’t know which parts of state are actually references to IDs and need to be updated.
I see three solutions:
For entities which are rarely deleted, reload the page. It’s not too bad if it happens rarely (e.g. selecting something like a Facebook group or a blog post leading to refresh is normal IMO, you don’t do that every day).
For entities that may often get deleted, you may consider a status field. For example, deleting may set isDeleted on the model. This way it’s still there, but it’s up to the UI to filter deleted items out.
You can make schema a first class concept, just like it’s first class in normalizr. If you have a schema, you can write a reducer factory that creates reducers adhering to this schema. These reducers will know when to remove IDs from foreign key fields because they know what the schema looks like.

Dependencies I will consider in future projects

Redux-form

Perfect for managing complex form state

Redux-orm

A small, simple and immutable ORM to manage relational data in your Redux store.

It would be great if CRUD operations were managed with the model declaration with no need to write actions manually.

Redux UI

Good solution to separate the UI state from the application state.

Normalizr

Library suggested in the official redux documentation for normalizing the application state.

Main dependencies for this project

  • React
  • Redux
  • React router v2
  • Redux Thunk
  • React DnD
  • Reselect
  • Styled components
  • React Bootstrap
  • Bootstrap Material Design
  • Jest

Conclusion

Redux is a good solution for handling complex interface, it is very similar to flux architecture, but if I have to rewrite the application, I would do it in a different way.

I would avoid to specify all the logic on the client, moving a part on the server side.

A good approach I have seen in some projects, it is to dispatch the actions to the server and with a websockets connection, notify all the connected clients of the changes made.

This way the client is responsible only to denormalize and normalize the state received by the server, handle the UI state and presenting the data.

On the server side is much easier to handle relationships with an ORM provided by a web framework.

This project has been of a great help to make me understand all the caveats redux can reserve for a medium size application. When I first started I didn’t think of that much work would be implied as the majority of redux examples available on the internet are about simple todo lists and chat app where the state and logic is minimal.

Documentation references