Skip to content

State and Events

State allows you to have the user interace with your application, update some data under the hood, and have that reflected in the page.

Events

Events means how you handle some user actions in your application. A full list of possible events can be found here. Essentially, you define functions that are called when certain actions are performed on elements in your page.

tsx
function App() {
  const handleClick = () => {
    console.log("button clicked");
  };

  return (
    <div>
      <button onClick={handleClick}>Click me</button>;
      <button
        onClick={() => {
          console.log("Inline function click");
        }}
      >
        Click me too!
      </button>
    </div>
  );
}

useState

State tells your application to rerender the page when something changes in your application.

tsx
import { useState } from "react";

function App() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1);
  };

  return <button onClick={handleClick} />;
}

useReducer

A Reducer is a way of combining multiple state variables into one object. This allows you to reduce the number of rerenders if multiple pieces of state are changing at the same time. useReducer takes a reducer function and an initial state. This reducer function will run whenever we call dispatch, and the argument to dispatch will be passed as an argument action along with the current state. The action argument is usually an object which tells the reducer what type of update to do. Whatever is returned from this function will be the new state. The reducer function cannot include async/await, promises, requests, etc. It is only for updating the state. You should not update the state directly unless you are using Immer, which is a library that automatically returns the new state for you.

tsx
import produce from "immer";
import { useReducer } from "react";

// Use constants for action to prevent typos
const INCREMENT_COUNT = "increment";
const SET_VALUE_TO_ADD = "change_value_to_add";
const DECREMENT_COUNT = "decrement";
const ADD_VALUE_TO_COUNT = "add_value_to_count";

const reducer = (state, action) => {
  switch (action.type) {
    case INCREMENT_COUNT:
      state.count = state.count + 1;
      return;
      // If not using immer, you would need to do something like...
      return {
        ...state,
        count: state.count + 1,
      };
    case DECREMENT_COUNT:
      state.count = state.count - 1;
      return;
    case ADD_VALUE_TO_COUNT:
      state.count = state.count + state.valueToAdd;
      state.valueToAdd = 0;
      return;
    case SET_VALUE_TO_ADD:
      state.valueToAdd = action.payload;
      return;
    default:
      return;
  }
};

function CounterPage({ initialCount }) {
  // wrapper reducer with produce to use immer
  const [state, dispatch] = useReducer(produce(reducer), {
    count: initialCount,
    valueToAdd: 0,
  });

  const increment = () => {
    dispatch({
      type: INCREMENT_COUNT,
    });
  };
  const decrement = () => {
    dispatch({
      type: DECREMENT_COUNT,
    });
  };
  const handleChange = (event) => {
    const value = parseInt(event.target.value) || 0;

    dispatch({
      type: SET_VALUE_TO_ADD,
      payload: value,
    });
  };
  const handleSubmit = (event) => {
    event.preventDefault();

    dispatch({
      type: ADD_VALUE_TO_COUNT,
    });
  };
}

export default CounterPage;

Binding inputs

tsx
function Input({ onSubmit }) {
  const [text, setText] = useState("");

  const handleChange = (event) => {
    setText(event.target.value);
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    onSubmit(text);
    setText("");
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input className="input" value={title} onChange={handleChange} />
        <button className="button">Submit</button>
      </form>
    </div>
  );
}

useEffect

useEffect is a function that runs when the application is initially rendered and sometimes when it is rerendered. The arrow function provided is always called on the initial render, and if a variable passed in the second argument is changed in the rerender, then the arrow function is called again. If no second argument is passed, then the function is called on every rerender.

tsx
import { useEffect, useState } from "react";

function Comp() {
  useEffect(() => {
    console.log("Run on initial render");
  }, []);

  useEffect(() => {
    console.log("Run on every render");
  });

  const [text, setText] = useState("");

  useEffect(() => {
    console.log("Run when 'text' is changed");
  }, [text]);
}

The only thing we can return from useEffect is a function, and this function gets called before the next time the useEffect function is run, so from the second render on.