This site lists the protips that we shared with students during our courses
When you start to use Hooks in React, some JavaScript trickery is in place that can be confusing when you first see them in action;
import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0); // <= WHAT ON EARTH IS GOING ON HERE
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Line 4 there is pretty advanced. It’s using - returning more than one thing, returning functions and destructuring all at once.
This post tries to explain these things a bit more
In JavaScript each function returns one thing, has exactly one return value. Even if you don’t return anything explicitly:
const noReturn = () => { console.log('Not returning a thing')}
const result = noReturn()
console.log(result) //undefined
The default value returned from a function is … undefined. So value may be debated but still - it’s returning something.
But what if we want to return more than one thing?
The most common way of doing that is to return an array:
const returningTwoNumbers = () => { return [1, 2] }
console.log(returningTwoNumbers()) // [1, 2]
We could also use an object with named parameters:
const returningTwoNumbersObj = () => { return { valueA: 1, valueB: 2 } }
const resultObj = returningTwoNumbersObj();
console.log(resultObj.valueA) // 1
console.log(resultObj.valueB) // 2
That last part is particularly interesting because it requires us to use the name that is returned. What if we wanted to decide what the values were called? Like getting the array values into variables.
Luckily there is - it’s called array destructuring and with the power of using it we can write this code:
// reusing the function that returns numbers
// const returningTwoNumbers = () => { return [1, 2] }
const [valueA, valueB] = returningTwoNumbers();
console.log(valueA) // 1
console.log(valueB) // 2
Using array destructuring we literary pick apart the array into a local constants valueA
and valueB
.
Pretty cool - and it just leaves one more thing before we can fully understand the trickery of that hook thing.
Before that, let’s take a detour of array destructuring. What if there were more than two items returned from the function and our destructuring looked the same?
const returningNumbers = () => { return [1, 2, 3, 4, 5] }
const [a, b] = returningNumbers();
console.log(a) // 1
console.log(b) // 2 (!?)
That is strange. The rest of the array values are just swalloed. But if you wanted to, you can get them in a final variable for the “rest” of the values. Like this:
const [a1, b1, ...rest] = returningNumbers();
console.log(a1) // 1
console.log(b1) // 2
console.log(rest) // [3, 4, 5]
By using the spread operator ...
we can spread the rest of the values, that are not mapped to named constants into a new array, in this case, called rest.
Ok - before we can put the whole thing together we need to revisit an old friend; higher-order functions. Meaning function that takes or returns functions. Because not only can we pass functions into another function we can also return functions from a function. Like this:
const makeMeADivider = (dividend) => {
return (num) => num / divident;
}
const fiveDivider = makeMeADivider(5);
console.log(`15 / 5 is ${fiveDivider(15)}`) // 15 / 5 is 3
In the makeMeADivider
all we are doing is just returning a new function, that uses the dividend
parameter passed to makeMeADivider
.
So now we have a little function-factory and can use it by calling it passing a suitable parameter; const fiveDivider = makeMeADivider(5);
which means that fiveDivider
is now a function that can divide stuff by five. Not super useful maybe but still…
We can then use it fiveDivider(15)
and get 3 back.
We can obviously return more than one function if we wanted to, like we did with other values:
const getFunctions = () => {
const a = (inparam) => { console.log(`${inparam}`) }
const b = (inparam) => { console.log(`${inparam.toUpperCase()}`) }
return [a, b]
}
const [myFunc1, myFunc2] = getFunctions()
myFunc1('marcus') // marcus
myFunc2('marcus') // MARCUS
Ok that is fine, but that’s not why we are here. Instead, let’s
Because now we have all the moving parts to understand that funky line from React Hooks:
const [count, setCount] = useState(0);
// and later
setCount(count + 1);
We can now understand that useState
returns more than one thing, and we destruct these into two local constants; count
and setCount
. Notice that we decide the name of these; const [kalle, ada]
would also work…
One of these happens to be a function, setCount
and we can use it as such setCount(count + 1)
.
So internally my naive implementation of useState would look like this:
const useState = (initialState) => {
const stateSetter = (newState) => {
// some function implementation to store state
// and then
return newState
}
return [ initialState, stateSetter ]
}
const [count, setCount] = useState(0)
console.log(typeof(count)) // number
console.log(typeof(setCount)) // function
Disclaimer: My implementation above will not work - it’s just a placeholder
I hope this made the things that happen under the covers with setState
a bit more clear.