Reacting with Input to State
It is easy to manipulate pieces of code in JavaScript, it's actually a big part of it's functionality. But with React, instead of manipulating individual pieces of the UI directly, you can describe multiple different states that your component can be in.
With React, you do not directly manipulate the UI, like you would in regular JavaScript. You do not enable, disable, etc. directly.
Instead, you declare what you want to show and React then figures out how to update the UI to make that happen.
Declarative Thinking about UI
There are multiple steps when it comes to declarative thinking about your UI.
The steps
Identify your component's different visual states.
Determine what triggers those state changes.
Represent the state in memory using
useState
.Remove any non-essential state variables..
Connect the event handlers to set the state.
Step 1: Identifying your component's different visual states
The first step when working with declarative thinking will be to identify which states your component actually has.
It's a good idea to write these down in a list so that they are clear for you.
Let's say we are making some kind of review form for a delivery service. A few states could be: empty, typing, submitting, success and error.
When you have thought about all possible states, it's important that the pages or mock ups of these states exist before you are going to add any logic to the states.
A way to do this is by adding a prop called status, then you can give this either success
or error
and work that in to a simple if else
statement.
Step 2: Determine what triggers the state changes
Now that you know what states you are working with, you should know what is triggering them. The triggers come from inputs, and there are two kinds of inputs:
Human Input
Computer Input
Human input is things like changing some text in the text input area or clicking on a submit button. Whilst computer input are more things like having a successful network response or a failed network response.
It's good to know where this trigger is coming from before continuing.
Step 3: Represent the state in memory with useState
It's important to keep it simple when working with the actual states. Each piece can be seen as one moving piece, and you will want as little amount of moving pieces as possible, to risk the smallest amount of bugs.
To begin, you should check which states are the absolute most important states for your application or part of it. For my example, this would be the following two:
const [answer, setAnswer] = useState('')
const [error, setError] = useState(null)
Now we can look at the other states for the delivery service review application, you could come up with five states.
const [isEmpty, setIsEmpty] = useState()
const [isTyping, setIsTyping] = useState()
const [isSubmitting, setIsSubmitting] = useState()
const [isSuccess, setIsSuccess] = useState()
const [isError, setIsError] = useState()
Step 4: Removing non-essential state variables
In the above step, we simply created a state for every state we defined in step one, but it might not even be needed to have five states. And, to be clear, you want less than five states, you want as little amount of states as possible.
That's why there are a few things you can do to check if you actually need all these states.
Does this state cause a paradox?
If we look at isTyping
and isSubmitting
, you'll realize that it is impossible for these two to both be true at the same time. And if this does happen, your state might not be constrained enough and it is not a useful state.
You can combine the states that have the same values, and turn it in to one state that has multiple values. To continue with the above states, you could take isTyping
, isSubmitting
, and isSuccess
and combine these in to one state like so:
const [status, setStatus] = useState('typing') // or 'submitting' or 'success'
Is the same information already available in another state?
You can also have two states that are always in sync and that actually risk going out of sync by placing them in to two different states.
A good example with the above code could be that isEmpty
and isTyping
have to be opposites, all the time. If they are both true at the same time, it would cause problems.
For this specific example, you can work with a different state that just checks the message.length
, because if this is not 0
, the message is not empty. You would get the following:
const [answer, setAnswer] = useState('')
Can you get the same information from the inverse of another state variable
It's always important to check if you do not have two states that work with the exact same information. You should probably combine the two to make it easier to work with.
A good example would be the error
state and the isError
state that was created earlier on. Since these are practically the same.
This would leave you with the following:
const [error, setError] = useState(null)
Step 5: Connect the event handlers to setState
To be able to work with the states and use them in your component, you will have to add event handlers to your code. This can be done in multiple different ways and depend on what type of component you are working on.