Searching with useReducer

With useReducer the code is not so different from the previous module:

  • Store Context with initialContext in useReducer
  • Define Event type
  • Update Context when the user types in the input
  • Add function to perform async request to fetch posts
import { useReducer } from "react";
import {
  initialContext,
  searchRequest,
  type Context,
  type Post,
} from "./shared";

type Event =
  | { type: "update-query"; value: string }
  | { type: "update-posts"; newPosts: Post[] };

const reducer = (context: Context, event: Event): Context => {
  if (event.type === "update-query") {
    return { ...context, query: event.value };
  } else if (event.type === "update-posts") {
    return { ...context, posts: event.newPosts };
  }

  return context;
};

export default function UseReducer() {
  const [context, dispatch] = useReducer(reducer, initialContext);

  const submitSearch = async () => {
    const newPosts = await searchRequest(context.query);
    dispatch({ type: "update-posts", newPosts });
  };

  return (
    <div>
      <div>
        <input
          type="search"
          value={context.query}
          onChange={(e) =>
            dispatch({ type: "update-query", value: e.target.value })
          }
        />
        <button type="button" onClick={submitSearch}>
          Search
        </button>
      </div>

      {context.posts.map((post) => (
        <div key={post.id}>
          <p>{post.title}</p>
          <p>{post.body}</p>
        </div>
      ))}
    </div>
  );
}

useReducer alone cannot perform async requests. Therefore, the initial fetch request is implemented the exact same way as we did with useState, by using the useEffect hook with an empty dependency array:

export default function UseReducer() {
  const [context, dispatch] = useReducer(reducer, initialContext);

  const submitSearch = async () => {
    const newPosts = await searchRequest(context.query);
    dispatch({ type: "update-posts", newPosts });
  };

  useEffect(() => {
    submitSearch();
  }, []);

  return (
    <div>
      <div>
        <input
          type="search"
          value={context.query}
          onChange={(e) =>
            dispatch({ type: "update-query", value: e.target.value })
          }
        />
        <button type="button" onClick={submitSearch}>
          Search
        </button>
      </div>

      {context.posts.map((post) => (
        <div key={post.id}>
          <p>{post.title}</p>
          <p>{post.body}</p>
        </div>
      ))}
    </div>
  );
}

With both useState and useReducer we are required to introduce also useEffect to perform the initial request to fetch the default list of posts.