useEffect hook

How to use the useEffect hook to make a component execute some operations on specific situations.


Description

The useEffect hook is one of the main hooks you will use in your application. This hook lets you execute a callback (effect) function after the first render and whenever one of the dependecies changes. This can be quite ideal for:

  • Initializing the component
  • Subscribing to events (e.g. window.addEventListener)
  • Using timeouts and intervals
  • Performing animations
  • Controlling a non-wompo widget or node in conjuctions with the useRef hook


Usage

useEffect(effectFn, dependencies);

The hook accepts two parameters: the effect function and the list of dependencies. The effect function will be executed on first render and any time one of the dependecies changes. This function can be a void function or can return a second function (called cleaning function) that will be executed before the execution of the next same effect or if the component unmount (is removed from the DOM).

If an empty array is given as a list of dependecies, the effect will be executed only after the first render.

If no dependencies are specified, the effect will be executed on every render.

Note: The effect will be executed asynchronously after the component has been rendered, not inline.


Example: timeout

Using the window.setTimeout function is a common use case for the useEffect hook. Here's an example:

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

function TimeoutComponent() {
  useEffect(() => {
    const timeoutId = setTimeout(() => {
      alert('I was first rendered 5 seconds ago!');
    }, 5000);
    return () => {
      clearTimeout(timeoutId);
    };
  }, []);
  return html`Nothing to see here, boss.`;
}

defineWompo(TimeoutComponent);

When using timeouts and intervals, remember to always cancel them using the cleaning function(like in the example). Not doing so can lead to unexpected behaviours, like the execution of code even if the element is no longer in the DOM.

Why the useEffect hook is needed for this case?
Because if you call the setTimoeut function directly inside the component, it will be executed every time the component renders. This usually causes unwanted loops when inside the timeout/interval callback a setState is called.


Example: fetching data

The useEffect hook can also be used to fetch data when the component renders.

Note: This example is only made to understand better how the hook works and how to do async operations inside of it. If you actually have to perform data fetching, use the useAsync hook instead.

Code:
import { useEffect, useState, defineWompo, html } from 'wompo';

function User({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`/get/user/${userId}`)
      .then((res) => res.json())
      .then((data) => setUser(data));
  }, [userId]);

  return html`...`;
}

defineWompo(User);

The effect callback cannot return a promise, so you cannot declare it as an async function and you cannot use the await keyword. Use .then functions or create an helper async function to call inside the effect.


Example: local storage

This example will show how you can use the useEffect hook to make operations into the window.localStorage to save and get data.

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

function Theme({ userId }) {
  const [theme, setTheme] = useState('light');

  // Get the user theme preference
  useEffect(() => {
    // Gets executed only on first render
    const savedThemePreference = localStorage.getItem('theme');
    if (savedThemePreference) setTheme(savedThemePreference);
  }, []);

  // Set the new user's theme preference
  useEffect(() => {
    // Gets executed on first render and every time that "theme" changes
    localStorage.setItem('theme', theme);
  }, [theme]);

  return html`...`;
}

defineWompo(Theme);

In this example we used two effects: one to get the user's theme preference, executed only once, and one to save the user's theme preference whenever the theme preference changes.

Effects will be executed in the order they are declared, so the order matters. In this example, if you execute the second effect before the other it will not work, because the theme in the localStorage will be always updated with the initial value of the state.


Example: Code highlighting

In this example we will combine the useRef hook with the third party library highlight.js and the useEffect hook to create an highlighted code component.

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

function Code({ code, lang }) {
  const codeRef = useRef();

  useEffect(() => {
    const highlighted = hljs.highlight(code, { language: lang });
    codeRef.current.innerHTML = highlighted.value;
  }, [code, lang]);

  return html`
    <pre>
      <code ref=${codeRef}></code>
    </pre>
  `;
}

defineWompo(Code);

In the above example the Code component accepts a code prop and a lang prop that will be used to create the highligted HTML that will be injected in the HTML element thanks to the useRef hook.