useRef hook

How to use the useRef hook to keep a value of a variable stable across renders.


Description

The useRef hook will save the value of a variable across renders.
Consider the following code:

function Component() {
  const [changed, setChanged] = useState(false);
  let isChanged = 'State did not change';
  const performChange = () => {
    isChanged = 'State changed!';
    setChanged(true);
  };
  return html`
    ${isChanged}
    <button @click=${performChange}>Change me!</button>
  `;
}

The above code will not work. But why?
This will be the component's lifecycle:

  1. The component is in the DOM, so it will try to perform its first render and the Component() function will be executed.
  2. isChanged is set to "State did not change"
  3. The component is fully rendered
  4. The user clicks the button
  5. isChanged is set to "State changed!" and the changed stateful variable is set to true.
  6. The new state differs from the previous state: the component is reloaded and the Component() function is executed.
  7. Again, isChanged is set to "State did not change"
  8. The component is fully rendered

Usually, you never want to make "normal" variable declarations inside of your component if you plan to change the variable's value at some point of the component's lifecycle.
You may think: "What if I move the variable declaration outside of the component?".
This approach would actually work, but you don't want to do it, for two reasons:

  1. Every instance of the component will have the same value: they are not independent.
  2. The previous reason implies that the component is NOT Pure, and this can lead to unexpected behaviors.

If you plan to use the component only once though, feel free do to it (but it'll make us sad).

The useRef hook will solve this problem.

Warning: Seeing a "let" or "var" variable declaration inside of your component should always trigger some alarms. The only place you should use " let" or "var" variables instead of "const" variables is (maybe) inside other functions (events, etc.).

The useRef hook has also a second use (which is usually the most common): if you put the value returned by it in a "ref" attribute of any node, the value of the variable will become the actual node.


Usage

const ref = useRef(initialValue);

The useRef hook accepts a single parameter, the initial value, and will return an object having a "current" key, which will correspond to the current value of the variable.
To update the value of the variable, you have to update the value of the "current" key.

Note: unlike the useState hook, updating the value will not cause a re-render of the component.

As said in the Description chapter, you can also use the value returned by the hook as the value of a "ref" attribute of any node. The value will be assigned after the first render (not immediately).
Quick Example:

function Component() {
  const nodeRef = useRef();

  console.log(nodeRef.current); // ❌ On the first render it will be null!

  useEffect(() => {
    console.log(nodeRef.current); // ✅ It will already be valorized at this point!
    console.log(nodeRef.current.textContent); // "Hey!"
  }, []);

  return html` <div ref=${nodeRef}>Hey!</div> `;
}

Example: timer

In this example we will create a simple timer using the useRef hook to save the value of the intervalId once we start the timer.

import { useState, defineWompo, html, useRef } from 'wompo';

export default function Timer() {
  const [timer, setTimer] = useState(0);
  const intervalId = useRef(null);

  function startTimer() {
    intervalId.current = setInterval(() => {
      setTimer((oldTimer) => oldTimer + 1);
    }, 10);
  }

  function stopTimer() {
    clearInterval(intervalId.current);
    intervalId.current = null;
  }

  function resetTimer() {
    setTimer(0);
  }

  return html`
    <div>
      <button @click=${startTimer} disabled=${intervalId.current !== null}>Start</button>
      <button @click=${stopTimer} disabled=${intervalId.current === null}>Stop</button>
      <button @click=${resetTimer} disabled=${timer === 0}>Reset</button>
      <p>${(timer / 100).toFixed(2)}</p>
    </div>
  `;
}

defineWompo(Timer);

Result:


Example: password revealer

In this example we will get the reference of an input node using the useRef hook and display an alert showing it's value.

import { defineWompo, html, useRef } from 'wompo';

export default function PasswordRevealer() {
  const inputRef = useRef(null);

  const revealPassword = () => {
    alert(`Your password is: "${inputRef.current.value}" 😈`);
  };

  return html`
    <div>
      <label>
        Type your password here:
        <input ref=${inputRef} type="password" />
        <button @click=${revealPassword}>I'll show your password to everyone!</button>
      </label>
    </div>
  `;
}

defineWompo(PasswordRevealer);

Result: