Use Reducer

Post demo

useState

const Post = (): JSX.Element => {
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<boolean>(false);
const [post, setPost] = useState<{ [key: string]: any }>({});
const handleFetch = (): void => {
  setLoading(true);
  setError(false);
  fetch("https://jsonplaceholder.typicode.com/posts/1")
    .then((res) => res.json())
    .then((data) => {
      setLoading(false);
      setPost(data);
    })
    .catch((err) => {
      setLoading(false);
      setError(true);
    });
};

 return (
    <div>
      <button onClick={handleFetch}>
        {loading ? "Wait..." : "Fetch the post"}
      </button>
      <p>{post?.title}</p>
      <span>{error && "Something went wrong!"}</span>
    </div>
  );
}

useReducer

// postActionTypes.ts
export const ACTION_TYPES = {
  FETCH_START: "FETCH_START",
  FETCH_SUCCESS: "FETCH_SUCCESS",
  FETCH_ERROR: "FETCH_ERROR",
};
// postReducer.tsx
import { ACTION_TYPES } from "./postActionTypes";

export interface PostState {
  loading: boolean;
  post: { [key: string]: any };
  error: boolean;
}

export const INITIAL_STATE: PostState = {
  loading: false,
  post: {},
  error: false,
};

export const postReducer = (state: PostState, action: { type: string, payload?: { [key: string]: any }}) => {
  switch (action.type) {
    case ACTION_TYPES.FETCH_START:
      return {
        loading: true,
        error: false,
        post: {},
      };
    case ACTION_TYPES.FETCH_SUCCESS:
      return {
        ...state,
        loading: false,
        post: action.payload,
      };
    case ACTION_TYPES.FETCH_ERROR:
      return {
        error: true,
        loading: false,
        post: {},
      };
    default:
      return state;
  }
};
  // Post.tsx
import { useReducer, useState } from "react";
import { ACTION_TYPES } from "./postActionTypes";
import { INITIAL_STATE, postReducer } from "./postReducer";
const Post = (): JSX.Element  => {
  const [state, dispatch] = useReducer(postReducer, INITIAL_STATE);
  const handleFetch = (): void => {
    dispatch({ type: ACTION_TYPES.FETCH_START });
    fetch("https://jsonplaceholder.typicode.com/posts/1")
      .then((res) => {
        return res.json();
      })
      .then((data) => {
        dispatch({ type: ACTION_TYPES.FETCH_SUCCESS, payload: data });
      })
      .catch((err) => {
        dispatch({ type: ACTION_TYPES.FETCH_ERROR });
      });
  };
  return (
    <div>
      <button onClick={handleFetch}>
        {loading ? "Wait..." : "Fetch the post"}
      </button>
      <p>{post?.title}</p>
      <span>{error && "Something went wrong!"}</span>
    </div>
  );
}

Form demo

useState

//demo2 Form
import { useState, useRef } from "react";

interface Product {
  title: string;
  desc: string;
  price: number;
  category: string;
  tags: string[];
  images: {
    sm: string;
    md: string;
    lg: string;
  };
  quantity: number;
}

const MyComponent = (): JSX.Element => {
  const [product, setProduct] = useState<Product>({
    title: "",
    desc: "",
    price: 0,
    category: "",
    tags: [],
    images: {
      sm: "",
      md: "",
      lg: "",
    },
    quantity: 0,
  });

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
    setProduct((prev) => ({ ...prev, [e.target.name]: e.target.value }));
  };

  const tagRef = useRef<HTMLInputElement>(null);

  const handleTags = (): void => {
    const tags = tagRef.current?.value.split(",") || [];
    tags.forEach((tag) => {
      setProduct((prev) => ({ ...prev, tags: [...prev.tags, tag] }));
    });
  };

  const handleRemoveTag = (tag: string): void => {
    setProduct((prev) => ({
      ...prev,
      tags: prev.tags.filter((t) => t !== tag),
    }));
  };

  const handleIncrease = (): void => {
    setProduct((prev) => ({ ...prev, quantity: prev.quantity + 1 }));
  };

  const handleDecrease = (): void => {
    setProduct((prev) => ({
      ...prev,
      quantity: prev.quantity - 1,
    }));
  };

  return (
   <div>
      <form>
        <input
          type="text"
          name="title"
          onChange={handleChange}
          placeholder="Title"
        />
        <input
          type="text"
          name="desc"
          onChange={handleChange}
          placeholder="Desc"
        />
        <input
          type="number"
          name="price"
          onChange={handleChange}
          placeholder="Price"
        />
        <p>Category:</p>
        <select name="category" id="category" onChange={handleChange}>
          <option value="sneakers">Sneakers</option>
          <option value="tshirts">T-shirts</option>
          <option value="jeans">Jeans</option>
        </select>
        <p>Tags:</p>
        <textarea
          ref={tagRef}
          placeholder="Seperate tags with commas..."
        ></textarea>
        <button type="button" onClick={handleTags}>
          Add Tags
        </button>
        <div className="tags">
          {product.tags.map((tag) => (
            <small key={tag} onClick={() => handleRemoveTag(tag)}>
              {tag}
            </small>
          ))}
        </div>
        <div className="quantity">
          <button type="button" onClick={handleDecrease}>
            -
          </button>
          <span>Quantity ({product.quantity})</span>
          <button type="button" onClick={handleIncrease}>
            +
          </button>
        </div>
      </form>
    </div>
  );
};

useReducer

// formReducer.tsx
interface FormState {
  title: string;
  desc: string;
  price: number;
  category: string;
  tags: string[];
  images: {
    sm: string;
    md: string;
    lg: string;
  };
  quantity: number;
}

interface FormAction {
  type: string;
  payload?: any;
}

export const INITIAL_STATE: FormState = {
  title: "",
  desc: "",
  price: 0,
  category: "",
  tags: [],
  images: {
    sm: "",
    md: "",
    lg: "",
  },
  quantity: 0,
};

export const formReducer = (state: FormState, action: FormAction): FormState => {
  switch (action.type) {
    case "CHANGE_INPUT":
      return {
        ...state,
        [action.payload.name]: action.payload.value,
      };
    case "ADD_TAG":
      return {
        ...state,
        tags: [...state.tags, action.payload],
      };
    case "REMOVE_TAG":
      return {
        ...state,
        tags: state.tags.filter((tag) => tag !== action.payload),
      };
    case "INCREASE":
      return {
        ...state,
        quantity: state.quantity + 1,
      };
    case "DECREASE":
      return { ...state, quantity: state.quantity - 1 };
    default:
      return state;
  }
};

// Form.tsx
import React, { useReducer, useRef, useState } from "react";
import { formReducer, INITIAL_STATE } from "./formReducer";

const Form = () => {
  const [state, dispatch] = useReducer(formReducer, INITIAL_STATE);
  const tagRef = useRef<HTMLTextAreaElement>(null);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
    dispatch({
      type: "CHANGE_INPUT",
      payload: { name: e.target.name, value: e.target.value },
    });
  };

  const handleTags = () => {
    const tags = tagRef.current?.value.split(",");
    if (tags) {
      tags.forEach((tag) => {
        dispatch({ type: "ADD_TAG", payload: tag });
      });
    }
  };

  return (
    <div>
      <form>
        <input
          type="text"
          placeholder="Title"
          onChange={handleChange}
          name="title"
          value={state.title}
        />
        <input
          type="text"
          placeholder="Desc"
          onChange={handleChange}
          name="desc"
          value={state.desc}
        />
        <input
          type="number"
          placeholder="Price"
          onChange={handleChange}
          name="price"
          value={state.price}
        />
        <p>Category:</p>
        <select onChange={handleChange} name="category" value={state.category}>
          <option value="sneakers">Sneakers</option>
          <option value="tshirts">T-shirts</option>
          <option value="jeans">Jeans</option>
        </select>
        <p>Tags:</p>
        <textarea ref={tagRef} placeholder="Seperate tags with commas..."></textarea>
        <button onClick={handleTags} type="button">
          Add Tags
        </button>
        <div className="tags">
          {state.tags.map((tag) => (
            <small
              onClick={() => dispatch({ type: "REMOVE_TAG", payload: tag })}
              key={tag}
            >
              {tag}
            </small>
          ))}
        </div>
        <div className="quantity">
          <button onClick={() => dispatch({ type: "DECREASE" })} type="button">
            -
          </button>
          <span>Quantity ({state.quantity})</span>
          <button onClick={() => dispatch({ type: "INCREASE" })} type="button">
            +
          </button>
        </div>
      </form>
    </div>
  );
};

Last updated