Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it bad to use complex objects in react state?

I am writing an application with React, it has some data stored in a component's state.

I initially chose to wrap the data in a function, this function would encapsulate all the actions that one could do on the data.

To keep this question generic, I will show my examples as a todo. My real-world use-case is more complex.

function Todo(todo = EmptyTodo) {
  // helper function to easily create immutable methods below
  function merge(update) {
    return Todo(Object.assign({}, todo, update))
  }

  return {
    getComplete() { 
      return todo.complete
    },
    getText() { 
      return todo.text 
    },
    toggleComplete() {
      return merge({ complete: !todo.complete })
    },
    setText(text) {
      return merge({ text })
    }
  }
}

This example falls short a bit. A better example might ideally be more than just getters and setters. It would probably contain something closer to business logic.

Moving on, now I used the Todo in a react component like this:

class TodoRow extends React.Component {
  state = {
    todo: Todo()
  }

  handleToggleComplete = () => this.setState(state => ({
    todo: state.todo.toggleComplete()
  }))

  handleTextChange = e => {
    const text = e.target.value
    this.setState(state => ({
      todo: state.todo.setText(text)
    }))
  }

  render() {
    return <div>
      <input 
        type="checkbox" 
        onChange={this.handleToggleComplete} 
        value={this.state.todo.getComplete()}
      />
      <input 
        type="text" 
        onChange={this.handleTextChange} 
        value={this.state.todo.getText()}
      />
      {this.state.todo.getText()}
    </div>
  }
}

Please forgive the contrived and simplistic example. The point is that the state is no longer a simple key-value store. It has a complex object that attempts to hide the primitive data and encapsulate the business logic (I know there is no business logic in the above example, just imagine it with me).

I do not need to serialize this state. No time travel or restoring state in localstorage.

Can anyone help me understand why this could be a bad practice?

Pros I can think of-

  • If a todo wants to be used elsewhere, the data logic can be reused.
  • todo object can enforce/implement immutability in one place.
  • The separation between display and data operations.
  • Can implement polymorphic behavior with the Todo object now (ReadOnlyTodo that can't be toggled or edited, CompleteOnly todo that can be completed but text can't be edited) without modifying the React component.

Cons I can think of-

  • Cannot serialize the state as JSON (easily fixed with a Todo.toJSON() method).
  • Doesn't follow normal pattern where setState operates on a key/value pair.
  • Calling todo.setText() might be confusing since it doesn't update state.

Codepens: Key/Value Store Complex object

like image 629
Justin Avatar asked Nov 06 '22 13:11

Justin


1 Answers

It may not be a good idea because your implementation of immutability is simply, recreating a new object completely with your changes. Also, you are making a shallow copy, and in real-life applications you will realize that nested objects are a common practice, especially in JSON.

Libraries such as Immutable.js create internal hash tables for their data structures that only recreate changed nodes instead of recreating the whole object while abstracting this logic to allow efficient immutability.

As an example, let's say you have an immutable object with 200 properties. For every update in every property, a new object with these 200 properties needs to be recreated. Note that depending on your application, a simple type on an input could recreate the complete object.

// ❗️ Merge would need to reassign all 200 properties for every change.
// ❗️ Plus, it won't work for nested properties as it is a shallow copy:

function merge(update) {
  return Todo(Object.assign({}, todo, update))
}

Instead, Immutable.js would, as an example, create a Hash Map with 20 nodes, each node containing 10 properties. If you update one property, only one node is dumped and recreated while the other are kept.

Also, consider that if any child components depend on this said state, their renders will probably be triggered as there is no memoization. That is why libraries such as reselect for Redux exist.

Note: immer.js is a newcomer immutability library that many devs are vouching for, but I can't say much about how it works internally as I don't know it that well.

like image 112
Yuan-Hao Chiang Avatar answered Nov 14 '22 22:11

Yuan-Hao Chiang