React tarjoaa yhteensä 10 erilaista valmista hookia, näistä ylivoimaisesti eniten käytetyt ovat meillekin jo tutut useState ja useEffect.

Custom hookit

React tarjoaa mahdollisuuden myös omien eli custom-hookien määrittelyyn. Customhookien pääasiallinen tarkoitus on Reactin dokumentaation mukaan mahdollistaa komponenttien logiikan uusiokäyttö.

Building your own Hooks lets you extract component logic into reusable functions.

Custom hookit ovat tavallisia Javascript-funktioita, jotka voivat kutsua mitä tahansa muita hookeja kunhan vain toimivat hookien sääntöjen puitteissa. Custom hookin nimen täytyy alkaa sanalla use.

Teimme osassa 1 laskurin, jonka arvoa voi kasvattaa, vähentää ja nollata. Sovelluksen koodi on seuraava

import React, { useState } from 'react'
const App = (props) => {
  const [counter, setCounter] = useState(0)

  return (
    <div>
      <div>{counter}</div>
      <button onClick={() => setCounter(counter + 1)}>
        plus
      </button>
      <button onClick={() => setCounter(counter - 1)}>
        minus
      </button>      
      <button onClick={() => setCounter(0)}>
        zero
      </button>
    </div>
  )
}

Eriytetään laskurilogiikka custom hookiksi. Hookin koodi on seuraavassa

const useCounter = () => {
  const [value, setValue] = useState(0)

  const increase = () => {
    setValue(value + 1)
  }

  const decrease = () => {
    setValue(value - 1)
  }

  const zero = () => {
    setValue(0)
  }

  return {
    value, 
    increase,
    decrease,
    zero
  }
}

Hook siis käyttää sisäisesti useState-hookia luomaan itselleen tilan. Hook palauttaa olion, joka sisältää kenttinään hookin tilan arvon sekä funktiot hookin tallettaman arvon muuttamiseen.

React-komponentti käyttää hookia seuraavaan tapaan:

const App = (props) => {
  const counter = useCounter()

  return (
    <div>
      <div>{counter.value}</div>
      <button onClick={counter.increase}>
        plus
      </button>
      <button onClick={counter.decrease}>
        minus
      </button>      
      <button onClick={counter.zero}>
        zero
      </button>
    </div>
  )
}

Näin komponentin App tila ja sen manipulointi on siirretty kokonaisuudessaan hookin useCounter vastuulle.

Samaa hookia voitaisiin uusiokäyttää sovelluksessa joka laski vasemman ja oikean napin painalluksia:

const App = () => {
  const left = useCounter()
  const right = useCounter()

  return (
    <div>
      {left.value}
      <button onClick={left.increase}>
        left
      </button>
      <button onClick={right.increase}>
        right
      </button>
      {right.value}
    </div>
  )
}

Nyt sovellus luo kaksi erillistä laskuria, toisen käsittelyfunktioineen se tallentaa muuttujaan left ja toisen muuttujaan right.

Lomakkeiden käsittely on Reactissa jokseenkin vaivalloista. Seuraavassa sovellus, joka pyytää lomakkeella käyttäjän nimen, syntymäajan ja pituuden:

const App = () => {
  const [name, setName] = useState('')
  const [born, setBorn] = useState('')
  const [height, setHeight] = useState('')

  return (
    <div>
      <form>
        name: 
        <input
          type='text'
          value={name}
          onChange={(event) => setName(event.target.value)} 
        /> 
        <br/> 
        birthdate:
        <input
          type='date'
          value={born}
          onChange={(event) => setBorn(event.target.value)}
        />
        <br /> 
        height:
        <input
          type='number'
          value={height}
          onChange={(event) => setheight(event.target.value)}
        />
      </form>
      <div>
        {name} {born} {height} 
      </div>
    </div>
  )
}

Jokaista lomakkeen kenttää varten on oma tilansa. Jotta tila pysyy synkroonissa lomakkeelle syötettyjen tietojen kanssa, on jokaiselle input-elementille rekisteröity sopiva onChange-käsittelijä.

Määritellään custom hook useField, joka yksinkertaistaa lomakkeen tilan hallintaa:

const useField = (type) => {
  const [value, setValue] = useState('')

  const onChange = (event) => {
    setValue(event.target.value)
  }

  return {
    type,
    value,
    onChange
  }
}

Hook-funktio saa parametrina kentän tyypin. Funktio palauttaa kaikki input-kentän tarvitsemat attribuutit eli tyypin, kentän arvon sekä onChange-tapahtumankäsittelijän.

Hookia voidaan käyttää seuraavalla tavalla:

const App = () => {
  const name = useField('text')
  // ...

  return (
    <div>
      <form>
        <input
          type={name.type}
          value={name.value}
          onChange={name.onChange} 
        /> 
        // ...
      </form>
    </div>
  )
}

Spread-attribuutit

Pääsemme itseasiassa helpommalla. Koska oliolla name on nyt täsmälleen ne kentät, mitä input-komponentti odottaa saavansa propsina, voimme välittää propsit hyödyntäen spread-syntaksia, seuraavasti:

<input {...name} /> 

Eli kuten Reactin dokumentaation esimerkki sanoo, seuraavat kaksi tapaa välittää propseja komponentille tuottavat saman lopputuloksen:

<Greeting firstName='Arto' lastName='Hellas' />

const person = {
  firstName: 'Arto',
  lastName: 'Hellas'
}

<Greeting {...person} />

Sovellus pelkistyy muotoon

const App = () => {
  const name = useField('text')
  const born = useField('date')
  const height = useField('number')

  return (
    <div>
      <form>
        name: 
        <input  {...name} /> 
        <br/> 
        birthdate:
        <input {...born} />
        <br /> 
        height:
        <input {...height} />
      </form>
      <div>
        {name.value} {born.value} {height.value}
      </div>
    </div>
  )
}

Lomakkeiden käsittely yksinkertaistuu huomattavasti kun ikävät tilan synkronoimiseen liittyvät detaljit on kapseloitu custom hookin vastuulle.

Custom hookit eivät selvästikään ole pelkkä uusiokäytön väline, ne mahdollistavat myös entistä paremman tavan jakaa koodia pienempiin, modulaarisiin osiin.

Hookien säännöt revisited

Kuten osassa 1 mainittiin, on hookeja käytettävä tiettyjä rajoituksia noudattaen. Seuraavassa vielä hookien käytön säännöt suoraan Reactin dokumentaatiosta kopioituna:

Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function.

Don’t call Hooks from regular JavaScript functions. Instead, you can:

  • Call Hooks from React function components.
  • Call Hooks from custom Hooks

On olemassa ESlint-sääntö, jonka avulla voidaa varmistaa, että sovellus käyttää hookeja oikein.

Kun sääntö eslint-plugin-react-hooks on asennettu, se saadaan käyttöön muuttamalla tiedostoa .eslintrc.js seuraavasti:

module.exports = {
  // ...
  "plugins": [
    // ...
    "react-hooks"  ],
  "rules": {
    "react-hooks/rules-of-hooks": "error",    // ...
  }
}; 

Lint valittaa nyt jos hookeja käytetään kielletyllä tavalla:

fullstack content

Lisää hookeista

Internetistä alkaa löytyä yhä enenevissä määrin hyödyllistä hookeihin liittyvä materiaalia, esim. seuraavia kannattaa vilkaista