useState vs useRef: Tussle of Titans

useState vs useRef: Tussle of Titans

What are Hooks? 🙄

Hooks let developers use state and other React features without writing a class. Hooks were introduced in React 16.8 to facilitate programmers in the reusability of React code.

They are the functions that "hook into" React state and lifecycle features from function components. It does not work inside classes.

When would I use a Hook?

If writing a function component and realizing the need to add some state to it, previously we had to convert it to a class. Now we can use a Hook inside the existing function component.

Rules of Hooks:

  • Hooks should only be called from the top level of your React function
  • Hooks must not be called from nested code (e.g., loops, conditions).
  • Hooks may also be called at the top level from custom Hooks.

Now let’s look at the two hooks which are most confused between 😬

useState Hook: 😍

Definition:

useState hook is the primary building block that enables functional components to hold state between re-renders. It enables the development of the component state for functional components.

Usage: 🙃

import React, { useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <button onClick={() => setCount(count + 1)}>Count is :{count}</button>
    </div>
  );
}

Above is the code for functional implementation of React useState and below is the equivalent CodeSandbox Output .

https://codesandbox.io/embed/priceless-fast-hijeoo?fontsize=14&hidenavigation=1&theme=dark

Let’s see the same code using class components-based method.

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

We see how much the code is reduced significantly and using functional components and hook also makes code much clearer by not using this keyword and eliminating a wrapper constructor within class component.

What does useState do? 🤔

First of all, it declares ‘state variables’. It is used to preserve value between the function calls. Normally, common Javascript variables like ( let, var, const) disappear after the function call. However, state variables preserve the values.

Explaining the Syntax:

[count,setCount]=useState(0)
  • count is the state variable that will be used to represent the value assigned using the hook.
  • setCount is a functional variable whose sole purpose is updating count state variable.
  • Within brackets, after useState initial argument is passed which is used for the initial value of the count after initial rendering.
  • Square Brackets mean that when we declare a state variable with useState it returns an array with two items. The first one is the current value and the second is a function that updates it.

React will remember its current value between re-renders, and provide the most recent one to our function. If we want to update the current count, we can call setCount When the App component re-renders, its children would re-render.

Gotcha! 😳

const [localState, setLocalState] = useState(props.theme);

useRef Hook: 😍

Definition:

The useRef Hook allows persisting values between renders. It can be used to store a mutable value that does not cause a re-render when updated. It can be used to access a DOM element directly.

It does everything that useState does but without re-rendering the components.

useRefreturns a mutable ref object whose .currentproperty is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.

The major question that arises is why use useRef if we have useState? 🤔

  • Easier to work with as updates are synchronous.
  • Do not trigger re-render. Performance optimization.
  • Prevents render thrashing
  • Can be used if we want the state to presist value throughout the component lifecycle.
  • If we want to access the DOM element directly useRef should be used.

Usage: 😜

import { useState, useRef } from "react";
import "./styles.css";
export default function AppDemo11() {
  const [value, setValue] = useState("");
  const valueRef = useRef();
  console.log("render");
  const handleClick = () => {
    console.log(valueRef);
    setValue(valueRef.current.value);
  };
  return (
    <div className="App">
      <h4>Value: {value}</h4>
      <input ref={valueRef} />
      <button onClick={handleClick}>click</button>
    </div>
  );
}

We see the code used for useRef and equivalent output in CodeSandbox.

https://codesandbox.io/embed/cranky-ellis-1s6jrt?fontsize=14&hidenavigation=1&theme=dark

We see that component only renders after button click function rather than rendering on each keystroke.

When we “submit” the input with a button to update the state variable value.With the refproperty, React provides direct access to React components or HTML elements.

<input ref={valueRef} />

If this had been done using useState and onChange which each keystroke input the component would have re-rendered. With each keystroke, component is re-rendered.

We see that in effect in the below code and it’s output in CodeSandBox.

import { useState, useRef, useEffect } from "react";
import "./styles.css";
export default function AppDemo11() {
  const [value, setValue] = useState("");
  const rendercount = useRef(0);

  useEffect(() => {
    rendercount.current = rendercount.current + 1;
    console.log(rendercount.current);
  });
  const handleChange = (e) => {
    setValue(e.target.value);
  };
  return (
    <div className="App">
      <input value={value} onChange={handleChange} />
      <h4>Renders:{rendercount.current} </h4>
    </div>
  );
}

(codesandbox.io/embed/competent-cerf-6e3vp0?..)

However, using useRef makes sure that the component is rendered only once after the button is clicked.

Explaining the Syntax:

const valueRef = useRef(null);
const onButtonClick = () => {

   console.log(valueRef.current.value);
  };
  • valueRef is the variable that stores the value to be persisted.
  • Within the brackets, after the useRef statement, it is provided the initial value which should be assigned to useRef on the first render.
  • We see that within the Button click function we access value within valueRef using .current keyword. This is because useRef is like a box that holds its mutable value in its .current property.

Keep in mind that useRefdoesn’tnotify when its content changes. Mutating the .currentproperty doesn’t cause a re-render.

Gotcha! 😳

Examples of different use cases for useState vs useRef: 🤠

We have already seen two major use cases for useRef over useState

  1. Accessing DOM input using ref directly.
  2. Persisting values of mutable variables and not causing re-renders on updation.

Let’s see some more use cases of these points.

  • In the first example, we simply try counting the no of renders using useRef and useState.

useState code:

import { useState, useRef, useEffect } from "react";
import "./styles.css";
export default function AppDemo11() {
  const [value, setValue] = useState("");
  const [rendercount,setrenderCount] = useState(0);

  useEffect(() => {
    setrenderCount(rendercount+ 1);

  });
  const handleChange = (e) => {
    setValue(e.target.value);
  };
  return (
    <div className="App">
      <input value={value} onChange={handleChange} />
      <h4>Renders:{renderco} </h4>
    </div>
  );
}

This above code will cause infinite renders as every time rendercount value is updated the entire component is re-rendered.

useRef code:

Equivalent use case can be achieved using useRef whererin within useEffect hook ( important to use within useEffect to avoid side effects) rendercount.current value is updated which doesn’t trigger any re rendering.

import { useState, useRef, useEffect } from "react";
import "./styles.css";
export default function AppDemo11() {
  const [value, setValue] = useState("");
  const rendercount = useRef(0);

  useEffect(() => {
    rendercount.current = rendercount.current + 1;
    console.log(rendercount.current);
  });
  const handleChange = (e) => {
    setValue(e.target.value);
  };
  return (
    <div className="App">
      <input value={value} onChange={handleChange} />
      <h4>Renders:{rendercount.current} </h4>
    </div>
  );
}
  • In the second example Let’s take a second use case of creating a timer using useState and useRef .

The code is for printing “A second has passed” in console for each passing second

useState code:

We see in this code that it goes into an infinite loop and keeps on re-rendering.

As with each update of state variable intervalUse component is re-rendered. Code goes into infinite loop hell.

import "./styles.css";
import React, { useRef, useEffect, useState } from "react";

export default function App() {

  const [intervalUse,setintevalUse]= useState()

  useEffect(() => {
    const id = setInterval(() => {
      console.log("A second has passed");
    }, 1000);


    setintevalUse(id)

  });

  return (
    <div>

    </div>
  );
}

useRef code:

So to prevent inifinte loop hell we use useRef.

With each passing second “A second has passed” is printed on the console and state variable val is updated which is rendered on the screen.

import "./styles.css";
import React, { useRef, useEffect, useState } from "react";

export default function App() {
  const[val,setVal]=useState(0)
  const intervalRef = useRef();

  useEffect(() => {
    const id = setInterval(() => {
      setVal(val+1)
      console.log("A second has passed");
    }, 1000);


    intervalRef.current = id;

    return () => clearInterval(intervalRef.current);
  });

  const handleCancel = () => clearInterval(intervalRef.current);

  return (
    <div>
     <div>Value is {val}</div>
    </div>
  );
}

See the ouput in CodeSandBox. Using useRef makes sure that there is no case for inifinite rendering.

https://codesandbox.io/embed/upbeat-ganguly-nbg47c?fontsize=14&hidenavigation=1&theme=dark

Finally all the Differences in Brief: 😌

  • Both preserve their data during render cycles and UI updates, but only the useState Hook with its updater function causes re-renders.
  • useRef returns an object with a current property holding the actual value. In contrast, useState returns an array with two elements: the first item constitutes the state, and the second item represents the state updater function
  • current property in useRef is mutable however useState state variable is not. We need updater function to update useState state variable.
  • useState and useRef both are Hooks, but only useRef can be used to gain direct access to React components or DOM elements.

Concluding: 🤩

It should be clear by now that useState is to be used if we want to update data and cause a UI update.

And useRef is to be used if data is to be persisted throughout the lifecycle without re-renders.

This is all meet you soon 🤗