Intro

The tutorial will be covering the following hooks:

useState, useEffect, useRef, useLayoutEffect, useCallback, useMemo, useReducer, and useContext.

We will explain how to use and why we are using each hook. And how hooks are better than class components.

Important: hooks can only be used with functions (meaning: with functional components), they can't be used with class components.

We can either start from a normal function (component), like this:

import React from 'react'

function App(){
    return <div>hey</div>
}

export default App

Or an arrow function, like this:

import React from 'react'

const App = () => {
    return <div>hey</div>
}

export default App

Both can work with hooks.

Note on differences between class and functional components and how they relate to hooks: https://medium.com/@Zwenza/functional-vs-class-components-in-react-231e3fbd7108

Note: in general, it is expected that you use a functional component whenever you can.

Now, another important thing, there are rules concerning the use of hooks: https://reactjs.org/docs/hooks-rules.html

Main ones are: Don’t call Hooks inside loops, conditions, or nested functions.

For example, the following is a wrong way of calling a hook:

import React from 'react'

const App = () => {
    if(true){
        React.useState() //wrong to call inside an if conditional
    }
    
    return <div>hey</div>
};

export default App

Note, you installed an extension in visual studio code called ESLint so that it warns about stuff, like error in usage in hooks. But it didn't quite work, and it's not a problem because you will end up facing the error later anyway. Okay bye.

useState hook

Before introducing this hook, there was no way by which one could use (or deal with) state inside a functional component (example: we couldn't - and still can't - use setState() in a functional component, but now we have useState).

Here is one way of how to call a hook (not only this one):

import React from 'react'

const App = () => {
    React.useState()
    return <div>hey</div>
}

export default App

Here is a cleaner way of calling it (import it first):

import React, {useState} from 'react'

const App = () => {
    useState()
    return <div>hey</div>
}

export default App

Remember that is hook is replacing state.

Inside the parameter of useState(here) - we can put the initial value of state. Example: useState(10). It is better to put it in arrow function syntax, so that in case it is expensive (example, a function that initializes state) then it is only executed one time:

import React, {useState} from 'react'

function expensiveIntialState(){
    //lot of loops and whatever
}

const App = () => {
    useState( () => expensiveInitialState());
    //better than calling expensiveInitialState directly
    return <div>hey</div>
}

export default App

The thing is, the useState() hook will always return an array, and we are calling it to make use of that array. It is common practice to 'destructure' the array we are receiving.

Highlight: destructuring in JS is a process by which elements inside an array are assigned to other variables. Example:

var thing = ["Table", "Chair", "Fan"];
var [a, b, c] = thing;

//a = 'Table'
//b = 'Chair'
//c = 'Fan'

More on destructuring: https://www.freecodecamp.org/news/array-destructuring-in-es6-30e398f21d10/

Going back:

So instead of:

const arr = useState(10) //not expensive, use 10 directly

It is better to do:

const [count, setCount] = useState(10)

In the above: as said earlier, useState() returns an array where the value of the first element is the value of the state (so count is now assigned to the value of the state).

However, setCount is meant to be a function. I don't understand how exactly this works, but what I understood so far: setCount code is not yet defined, it is to be defined later (inside its parameter space for some reason). However, since setCount is included in the same destructuring array as count, it has direct access to the count variable. So when we write the code for setCount later on, we can directly access count in it.

To further explain: useState(10) is returning an array with only one element, count, and setCount is being assigned to undefined (because that's what happens in a destructuring array when there is no corresponding value). However we are going to define setCount later on as a function and make use of its ability to access the value of count.

Here is a code:

import React, {useState} from 'react'

const App = () => {
    const [count, setCount] = useState(10)
    return (
    <div>
        <button onClick = {() => setCount(count +1)}> add </button>
        <div>{count}</div>
    </div>
            )
}

export default App

Note: I really don't understand this is a lot, but here is what we can also do:

 <button onClick = 
     {() => setCount(currentCount => currentCount + 1)}>     
         add </button>

In the above, we now passed a 'function' inside setCount instead of writing a code directly into it. This new 'function' takes a parameter, currentCount, and returns it added to 1. Since we are talking about setCount, then the parameter it takes is automatically assigned to the value of count (so I can rename it to whatever I want, here renamed to 'currentCount').

Both ways (direct code and function) are valid ways to deal with setCount.

Unrelated note: when you normally use setState in a class component, you probably don't need to return all of the state: setState deals only with the piece of state you deal with, and returns the rest as is. However here (useState) -> you are responsible to also return the rest of state. That's why had to use the ... operator below, and then overwrite just the piece of state that we were changing.

Now let's deal with a bit more bigger stuff. Imagine you have a js object as a state, and that object has two properties, and you just want to increment one of them. Here is how we could do it:

import React, {useState} from 'react'

const App = () => {
    const [{count1, count2}, setCount] = useState({state1: 10, state2: 20})
    
    return (
    <div>
        <button onClick = {() => setCount(currentState => ({...currentState,  currentState.count1 +1})}> 
        add 
        </button>
        <div>{count1}</div>
        <div>{count2}</div>
    </div>
            )
}

export default App

So the above increments count1 while returning count2 intact.

You can use more than one 'useState' in the same component. So for the above, we could have easily broken our states into separate useState statements and dealth with them individually:

//so instead of this:
  const [{count1, count2}, setCount] = useState({state1: 10, state2: 20})
  
  //we could have had it as this:
  
  const [count1, setCount1] = useState({state1: 10})
    const [count2, setCount2] = useState({state2: 20})
    
    //and here we wouldn't have to use the ... operator

So why is useState useful?

So far, useState has been no different than directly using a state (or setState) in a class component. But now we are going to learn some additional stuff.

Here is where they come really useful: using repeated logic.

So imagine we have a form with email and password input fields, and on submit [or, onChange], we handle the values by having functions that take the 'value' from the input and put it in state (very normal way of handling forms). Since this is a type of logic that is constantly used (whenever you have a form, you'll want to take the values of fields on a submit), we can create a hook (in a standalone file), export it, and then use it (or use the logic in it) whenever we needed it. Here is how this would go:

This is how a form setup would normally look (actually we are using a hook here but we are using it just as if it were a setState. After it you'll see a hook in full power):

App.js //before using full capabilities of hooks

import React, {useState} from 'react'

const App = () => {
    const [email, setEmail] = useState("")
    const [password, setPassword] = useState("")
    
    return (
    <div>
      <input
        name="email"
        value= {email}
        onChange = {e => setEmail(e.target.value)}
        />
        <input
        name="password"
        value= {password}
        onChange = {e => setPassword(e.target.value)}
        />

    </div>
            )
}

export default App

So the above is kind of using useState the same way that we usually use setState. Now, here is how we would make use of the full power of useState:

Create a new file (hook), name it useForm.js (for example) - since it will be used in all kinds of forms.

useForm.js //naming convetion for hook files

import {useState} from 'react'

export const useForm = (initialValues) =>{
    const [values, setValues] = useState(initialValues);
    
    //the below could have also returned an object
    return [values, e => {
        setValues({
            ...values,
            [e.target.name]: e.target.value
        });
    }
}

App.js // using full capabilities of hooks:

import React, {useState} from 'react'
import {useForm} from './useForm'

const App = () => {
    const [values, handleChange] = useForm({email:'', password: ''})
    
    return (
    <div>
      <input
        name="email"
        value= {values.email}
        onChange = {handleChange}
        />
        <input
        type="password"
        name="password"
        value= {values.password}
        onChange = {handleChange}
        />

    </div>
            )
}

export default App

So what the hell is happening above. You might say that we really didn't 'minimize' code that much - well f u. We did minimize code. Imagine we have much more complex code that we need to use again and again. We can now simply put it inside a hook, export it, and import it into whatever functional component that needs. Good.

Okay, in the useForm.js file: we now have a hook code (which is kind of like a function - or a functional component that has nothing to do with UI), and this function is based on the useState hook. So we pass our 'initial' state to this function (which we will pass from whatever component that will call it), that initial state will go into the 'values' part of the array destructuring, and then we can define the setValues part to the code that we will use again and again - but we defined the setValues code right inside the array that we will return.

Notice how, when we call the useForm from within a component, it will return the array that was defined in hook - and the setValue part is already defined elsewhere but will be run here.

Okay that's it.

Stuff to note: hooks are not related to method.

There is something called that you might want to use later on for forms (but hooks are better).

That was it for useState. We are happy that you learned this. Good luck with the rest!

useEffect hook

Before introducing this hook, we couldn't use lifecycle methods inside functional components.