ReactJS - Using useState



useState is a basic React hook, which allows a function component to maintain its own state and re-render itself based on the state changes. The signature of the useState is as follows −

const [ <state>, <setState> ] = useState( <initialValue> )

where,

  • initialValue − Initial value of the state. state can be specified in any type (number, string, array and object).

  • state − Variable to represent the value of the state.

  • setState − Function variable to represent the function to update the state returned by the useState.

The signature of the setState function is as follows −

setState( <valueToBeUpdated> )

Where, valueToBeUpdated is the value to be updated for the state. The sample usage to set and update the user's name is as follows −

// initialize the state
const [name, setName] = useState('John')

// update the state
setName('Peter)

Features

Notable features of useState are as follows −

Function Parameter − It Accepts a function (returns initial state) instead of initial value and execute the function only once during initial rendering of the component. This will help to improve the performance, if the computation of initial value is expensive.

const [val, setVal] = useState(() => {
   var initialValue = null
   // expensive calculation of initial value
   return initialValue
})

Verifies the previous values − It checks the current and previous value of the state and only if they are different, React renders its children and fires the effects. This will improve performance of the rendering.

// ...
setName('John') // update the state and rerender the component
// ...
// ...
setName('John') // does not fire the rendering of the children because the value of the state have not changed.
// ...

Batches multiple state updates − Multiple state updates are batched and processed by React internally. If multiple state update has to be done immediately, then the special function flushSync provided by React can be used, which will immediately flush all the state changes.

flushSync(() => setName('Peter'))

Applying state hook

Let us create a login form component and maintain the values of the form using useState hook.

First of all, create and start a React application using below commands,

create-react-app myapp
cd myapp
npm start

Next, create a react component, LoginForm under component folder (src/components/LoginForm.js)

import { useState } from 'react';
export default function LoginForm() {
   // render code
}

Next, create two state variable, username and password using useState hook as shown below −

import { useState } from 'react';
export default function LoginForm() {
   const [username, setUsername] = useState('')
   const [password, setPassword] = useState('')
   
   // render code
}

Next, create a function to validate the login data as shown below −

import { useState } from 'react';
export default function LoginForm() {
   const [username, setUsername] = useState('')
   const [password, setPassword] = useState('')
   
   let isEmpty = (val) => {
      if(val == null || val == '') {
         return true;
      } else {
         return false;
      }
   }
   
   let validate = (e) => {
      e.preventDefault()
      if(!isEmpty(username) && !isEmpty(password)) {
         alert(JSON.stringify({
            username: username,
            password: password
         }))
      } else {
         alert("Please enter username / password")
      }
   }
   // render code
}

Here, isEmpty is a function to check whether the data is available or empty.

Next, render a login form with two input fields and use state variables (username and password), state updated methods (setUsername and setPassword) and validate method to process the form.

import { useState } from 'react';
export default function LoginForm() {
   return (
      <div style={{ textAlign: "center", padding: "5px" }}>
         <form name="loginForm">
            <label for="username">Username: </label>
               <input id="username" name="username" type="text"
               value={username}
               onChange={(e) => setUsername(e.target.value)} />
            <br />
            <label for="password">Password: </label>
               <input id="password" name="password" type="password"
               value={password}
               onChange={(e) => setPassword(e.target.value)} />
            <br />
            <button type="submit" onClick={(e) => validate(e)}>Submit</button>
         </form>
      </div>
   )
}

Here,

  • onChange uses the state setting function returned by hooks.

  • onClick uses the validate function to validate and show the user entered data.

The complete code of the LoginForm component is as follows −

import { useState } from 'react';
export default function LoginForm() {
   const [username, setUsername] = useState('')
   const [password, setPassword] = useState('')
   
   let isEmpty = (val) => {
      if(val == null || val == '') {
         return true;
      } else {
         return false;
      }
   }
   
   let validate = (e) => {
      e.preventDefault()
      if(!isEmpty(username) && !isEmpty(password)) {
         alert(JSON.stringify({
            username: username,
            password: password
         }))
      } else {
         alert("Please enter username / password")
      }
   }
   
   return (
      <div style={{ textAlign: "center", padding: "5px" }}>
         <form name="loginForm">
            <label for="username">Username: </label>
               <input id="username" name="username" type="text"
               value={username}
               onChange={(e) => setUsername(e.target.value)} />
            <br />
            <label for="password">Password: </label>
               <input id="password" name="password" type="password"
               value={password}
               onChange={(e) => setPassword(e.target.value)} />
            <br />
            <button type="submit" onClick={(e) => validate(e)}>Submit</button>
         </form>
      </div>
   )
}

Next, update the root application component, App.js as below,

import './App.css';
import HelloWorld from './components/HelloWorld';
import LoginForm from './components/LoginForm';
function App() {
   return (
      <LoginForm />
   );
}
export default App;

Next, open the browser and check the application. Application will gather the user entered data using state variable and validate it using validate function. if user entered proper data, it will show the data as shown below −

Applying State Hook

Otherwise, it will throw the error as shown below −

Applying State Hook

Object as state

In class based state management, setState method supports partial update of the state object. For example, let us consider login form data is maintained in state as an object.

Applying State Hook
{
   username: 'John',
   password: 'secret'
}

Updating the username using setState will only update the username in the state object and preserve the password field.

this.setState({
   username: 'Peter'
})

In hooks, the setData (function return by useState) will update the whole object as shown below −

// create state
const [data, setDate] = useState({
   username: 'John',
   password: 'secret'
})
// update state - wrong
setData({
   username: 'Peter'
})

The updated state don't have password field as shown below −

{
   username: 'Peter'
}

To fix the issue, we can use spread operator in javascript as shown below −

setData({
   ...data,
   username: 'Peter'
})

Let us create a new component by converting our LoginForm component and use object state variable as shown below −

import { useState } from 'react';
export default function LoginFormObject() {
   const [data, setData] = useState({})
   let isEmpty = (val) => {
      if(val == null || val == '') {
         return true;
      } else {
         return false;
      }
   }
   let validate = (e) => {
      e.preventDefault()
      if(!isEmpty(data.username) && !isEmpty(data.password)) {
         alert(JSON.stringify(data))
      } else {
         alert("Please enter username / password")
      }
   }
   return (
      <div style={{ textAlign: "center", padding: "5px" }}>
         <form name="loginForm">
            <label for="username">Username: </label>
               <input id="username" name="username" type="text"
               value={data.username}
               onChange={(e) => setData( {...data, username: e.target.value} )} />
            <br />
            <label for="password">Password: </label>
               <input id="password" name="password" type="password"
               value={data.password}
               onChange={(e) => setData({...data, password: e.target.value})} />
            <br />
            <button type="submit" onClick={(e) => validate(e)}>Submit</button>
         </form>
      </div>
   )
}

Here,

  • State is maintained in object (data).

  • setData is returned by useState hook and used as state update function.

  • data.* syntax is used to get the details of the state.

  • …data spread operator is used along with setData function to update the state.

Summary

useState hook is simple and easy way to do state management in the function component. useState can be used to handle single value or multiple value in the state. It support both basic data type and complex objects. It allows multiple state setting function (set*) and internally batches to simplify the process. Due to the introduction of useState hook, function component are finally improved to do any functionality (from stateless to stateful).

Advertisements