Structuring your State

Principles for Structuring State

There are a few rules when it comes to structuring state. It's overal important that you make state easy to update without introducing mistakes, since state can be difficult on its own, its not very convenient to introduce even more problems inside of it.

  1. Group related state: If you update two or more state variables at the same time, you can probably merge them into a single state variable.

  2. Avoid contradictions in state: If you have several pieces of state that may contradict and disagree with each other, you should avoid that.

  3. Avoid redundant state: You should not put information into a component's state when you can calculate some information from the component's props or its existing stat variables during rendering.

  4. Avoid duplication in state: It is difficult to keep states in sync if you are working with the same data between multiple state variables or within nested objects.

  5. Avoid deeply nested state: Structure state in a flat way.

One: Group related states

If you have two state variables that will change together, always, you can obviously do the following:

const [x, setX] = useState(0)
const [y, setY] = useState(0)

But, if you put these two together in one state, it can actually be completely in sync and used easier. Like so:

const [position, setPosition] = useState({ x: 0, y: 0 })

Two: Avoiding contradictions in state

If two states are intertwined and maybe one of them follows up the other, it can be quite difficult to keep it waterproof. There might be situations where one state is not correct and the other state should be sent.

The above describes why it can be very convenient to create just one variable that takes up multiple valid states.

Let's say you have created the following states:

const [isSending, setIsSending] = useState(false)
const [isSent, setIsSent] = useState(false)

The second one, isSent, will follow up on the first one, isSending.

But, instead of having two different states, it is much more convenient to create one state, that can have multiple different variables. Like the following piece of code:

const [status, setStatus] = useState('typing')

async function handleSubmit(e) {
  e.preventDefault()
  setStatus('sending')
  await sendMessage(text)
  setStatus('sent')
}

const isSending = status === 'sending'
const isSent = status === 'sent'

if (isSent) {
  return <h1> Thanks for your feedback! </h1>
}

It's always a good idea to still add constants for readability, as you can see above with isSending and isSent. It works differently now because there are no more state variables that we have to worry about because they might go out of sync.

Three: Avoid redundant state

Adding things into a state could be seen as a last resort. First, try to calculate the information in a different way, like from the component's props or an existing state variable during rendering.

A good example of this is when you create the following two states:

const [firstName, setFirstName] = useState('')
const [lastName, setLastName] = useState('')

You could want to add the following state then as well:

const [fullName, setFullName] = useState('')

But this last state of the full name can easily be calculated if you have the other two states. This third state is redundant since it is definitely not needed.

Four: Avoid duplication in state

You should never store the contents of one state in to the contents of another state, like so:

const [data, setData] = useState([])
const [answers, setAnswers] = useState(data[0])

It makes things difficult to understand, especially because there are more simple and effective ways to work around this.

Five: Avoid deeply nested states

When you want to update nested states, you will have to make copies of objects all the way up from the part you changed. If your state is too nested, it gets really difficult to update. You should consider making this 'flat' by storing your data differently, like changing around the objects and arrays.