A simpler cleaner way to update a nested state object in a reducer?

reasonreact

#1

Hi there!

I currently use something like this to change the value for a field in a nested state object:

let reducer = (action, state) =>
  switch (action) {
  | Records.ChangeSearch(text) =>
    R.Update({
      ...state,
      recordsState: {
        ...state.recordsState,
        searchQuery: text,
      },
    })
  | ...

This seems like a lot of “boilerplate” (this pattern occurs very often in my reducer functions), so I was wondering if there is a simpler/shorter way to do this in ReasonML?

Update:

I think this syntax would be nice:

R.Update({...state, recordsState.searchQuery: text})

Any love for something like this? :slight_smile:


#2

Maybe use a lens to reduce the boilerplate: https://github.com/jonlaing/rationale#infix-lens-composition


#3

Keep in mind what Evan Czaplicki (the Elm author) said about optics:

It is possible that the concept […] is harmful to code quality in that it can help you to be lax with abstraction boundaries. By making it easy to look deep inside of data structures, it encourages you to stop thinking about how to make these substructures modular, perhaps leading to an architecture that is not as nice and has extra conceptual complexity.

Source


#4

That’s an interesting point Hoichi, but I also find it interesting that Evan said that in the readme of a lens library that he himself wrote :slight_smile:


#5

Yeah, that’s funny, but I also think it lends the point more weight, coming from a guy who knows full well what lenses are and how to write them :slight_smile:
Besides, my takeout from that readme is not so much “never use lenses” as “design your boundaries”.


#6

If you don’t mind using ppxs, you can give a try with https://github.com/Astrocoders/lenses-ppx.

[%lenses type records = { searchQuery: string, foo: string }];
[%lenses type root = { recordsState: records }];

let reducer = (action, state) =>
  switch (action) {
  | Records.ChangeSearch(text) =>
    Update(
    RootLenses.(
     state->set(
       RecordsState,
       state.recordStates->RecordsLenses.set(SearchQuery, text))
    )
   )
  | ...

not sure if in this case this is better


#7

You should investigate if it’s not possible instead to have a relational structure for your data. Usually nested structures are bad practice (not always). Here is a video that explains how you can instead model it as relational. They use Elm but hopefully it’s still understandable.


#8

It was a great talk, thanks for sharing @MarcCoquand!

Relational modeling was also proposed as solution to accidental complexity in Out of the Tar Pit:

The relational model [Cod70] has — despite its origins — nothing intrinsically to do with databases. Rather it is an elegant approach to structuring
data, a means for manipulating such data, and a mechanism for maintaining
integrity and consistency of state. These features are applicable to state and
data in any context

Link to the paper.

One approach for reducers is to create domain types and compose them rather than treating the state as one big nested record. These types would be inside their own modules and will be opaque to outside modules. So to update them we’ll have to call their setter functions rather than through deep nested states. code example and explanation