DEV Community

Cover image for Learning React Hooks 1 useState
0xkoji
0xkoji

Posted on • Edited on

Learning React Hooks 1 useState

Finally, I researched react hooks quickly last week to consider introducing react hooks to the current react project since most components are using a class.

First, I checked all the components to understand which react hook I need to use.

  1. use state
  2. use lifecycle method
  3. use connect (for redux)

Before dive into useState(), would like to think about the benefit.

The benefit to introduce hooks

React Hooks allow us to use state and lifecycle methods with a functional component.

  • less code
  • more readable
  • easy to test
  • possibly improve performance

Also, allow us to create custom hooks(useWhatever) that helps to manage state with useState
For example, useFetchData that fetches data from the API.

In terms of redux, react hooks allow us to extract data from the Redux store state without connect useSelector() and useDispatch() allows us to call an action from a component we want to call.

Things that React Hooks do not support

reactjs.org/docs/hooks-faq.html#do-hooks-cover-all-use-cases-for-classes

In this post, write 3 simple react apps with useState() from a class component

  1. counter app
  2. display input (object)
  3. display input (array)

code
https://github.com/koji/typescript/tree/master/usestate

counter

counter app by class component

import React from 'react'

interface Props {
}

interface State {
  counter: number;
}

export class ClassCounter extends React.Component <Props,State> {
  constructor(props:Props) {
    super(props);
    this.state = {
      counter: 0,
    }
  }

  // +1
  handleIncrement = () => {
    this.setState({
      counter: this.state.counter + 1,
    });
  }

  // -1
  handleDecrement = () => {
    this.setState({
      counter: this.state.counter - 1,
    });
  }

  // reset count
  handleReset = () => {
    this.setState({
      counter: 0
    });
  }

  // +10
  handleIncrementTen = () => {
    this.setState({
      counter: this.state.counter + 10,
    });
  }

  render() {
    return (
      <div>
        <h1>class component</h1>
        <p>Count {this.state.counter}</p>
        <br/>
        <button onClick={this.handleIncrement}>increment</button>
        <button onClick={this.handleDecrement}>decrement</button>
        <button onClick={this.handleReset}>reset</button>
        <br />
        <button onClick={this.handleIncrementTen}>increment10</button>
      </div>
    )
  }
}
Enter fullscreen mode Exit fullscreen mode

counter app by hooks

import React, { useState } from 'react';

interface Counter {
  counter: number;
}

const FCCounter = () => {
  const initialValue = () => { 
    return 0 
  };

  const [counter, setCount] = useState<Counter>(() => initialValue()); // initial value 0

  // +1
  const handleIncrement = () => {
    setCount((prevCount:number) => prevCount + 1);
  }

  // -1
  const handleDecrement = () => {
    setCount((prevCount:number) => prevCount - 1);
  }

  // reset count
  const handleReset = () => {
    setCount({ counter: initialValue });
  }

  // +10
  const incrementTen = () => {
    setCount((prevCount:number) => prevCount + 10);
  }

  return (
    <div>
      <h1>class component</h1>
      <p>Count {counter.counter}</p>
      <br/>
      <button onClick={handleIncrement}>increment</button>
      <button onClick={handleDecrement}>decrement</button>
      <button onClick={handleReset}>reset</button>
      <br/>
      <button onClick={incrementTen}>increment10</button>
    </div>
  )
}

export {
  FCCounter
}
Enter fullscreen mode Exit fullscreen mode

index.ts

export { ClassCounter } from './ClassCounter';
export { FCCounter } from './FCCounter';
Enter fullscreen mode Exit fullscreen mode

There are 3 differences between a class component and a functional component with useState

  1. functional component is using useState<Counter>({ counter: initialValue}) instead of this.state = {counter: 0,}

  2. functional component is using setCount({ counter: counter.counter + 1 }); instead of this.setState({counter: this.state.counter + 1,});

  3. functional component is using a function instead of a method. In my understanding, this makes testing easier.

object

display input (object) by class component

import React, { Component } from 'react'

interface State {
  firstName: string;
  lastName: string;
}

interface Props {}

class ClassObjectDisplay extends Component <Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = {
      firstName: '',
      lastName: ''
    };
  }

  handleFirstName = (e: React.FormEvent<HTMLInputElement>) => {
    this.setState({
      firstName: e.currentTarget.value
    });
  }

  handleLastName = (e: React.FormEvent<HTMLInputElement>) => {
    this.setState({
      lastName: e.currentTarget.value
    });
  }

  render() {
    return (
      <div>
        <h1>class component</h1>
        <form>
          <input type="text" value={this.state.firstName} onChange={this.handleFirstName} placeholder="your first name" />
          <input type="text" value={this.state.lastName} onChange={this.handleLastName} placeholder="your last name" />
        </form>
        <h3>My Name: {this.state.firstName} {this.state.lastName}</h3>
      </div>
    )
  }
}

export {
  ClassObjectDisplay
}
Enter fullscreen mode Exit fullscreen mode

display input (object) by hooks

import React, {useState} from 'react'

interface person {
  firstName: string;
  lastName: string;
}

const FCObjectDisplay =() => {
  const [name, setName] = useState<person>({ firstName: '', lastName: ''});

  const handleFirstName = (e: React.FormEvent<HTMLInputElement>) => {
    setName((prevName: person) => { ...name, firstName: e.currentTarget.value });
  }
  const handleLastName = (e: React.FormEvent<HTMLInputElement>) => {
    setName((prevName: person) => { ...name, lastName: e.currentTarget.value });
  }
  return (
    <div>
      <h1>functional component with hooks</h1>
      <form>
        <input type="text" value={name.firstName} onChange={handleFirstName} placeholder="your first name" />
        <input type="text" value={name.lastName} onChange={handleLastName} placeholder="your last name"/>
      </form>
      <h3>My Name: {name.firstName} {name.lastName}</h3>
    </div>
  )
}

export {
  FCObjectDisplay
}
Enter fullscreen mode Exit fullscreen mode

In this case, useState's data structure is person that has firstName and lastName. Their initial values are empty.
useState<person>({ firstName: '', lastName: ''});

This case needs to use spread syntax to set value properly. If don't use spread, setName({}) will only set firstName.
setName({ ...name, firstName: e.currentTarget.value });

You can try this setName({ firstName: e.currentTarget.value }); to check the issue.

The array case is almost the same as above.
array
https://github.com/koji/typescript/tree/master/usestate/src/components/Array

[Thoughts]
The above samples are using very simple State so easy to name variables for useState, but if State is kind of complicated, probably creating a couple of variables could be good.
I think converting a class component that only uses State is good as a starting point.

Top comments (1)

Collapse
 
marklai1998 profile image
Mark Lai

This post is just horribly wrong on setting state
every React developer should use call back function when the new state value is referencing the old state, that's the basic

this.setState(({counter})=>({ counter: prevCount + 10}))
setCount(({counter})=>({ counter: prevCount + 10}))