React keywords: createContext, useContext, memo, useEffect, useState

Project overview:

Project shows: Using the useContext() hook eliminates prop drilling (passing props to deeply nested children). It works on a Provider-value-Consumer paradigm. Props are no longer passed into components, instead, a context API is accessed directly by the component.

– In App-v1.js const PostContext = createContext(); is used
   <PostContext.Provider value={{…}} >
      {children}
   </PostContext.Provider>

and individual components access states through: const { state1, state2… } = useContext(PostContext);

– In App.js all of the state access and manipulation is placed inside a custom provider called PostProvider(…). This functionality is placed in a stand alone file called PostContetx.js.

– In App.js the custom context provider ‘function PostProvider({ children }) {…}’ is used

   <PostProvider>
      {children}
   </PostProvider >

where PostProvider returns ‘<PostContext.Provider value={value}>{children}</PostContext.Provider>’
and individual components access states through: const { state1, state2… } = usePosts(); where

   function usePosts() {
const context = useContext(PostContext);

      return context; }

Specifically, the parent app returns a set of nested child components inside a context provider:

   …
      <PostProvider>
        <Header />
        <Main />
        <Archive />
        <Footer />
      </PostProvider>
    </section>

and one of the <PostProvider> children (<Header />) gets access to the context data:
function Header() {
  const { onClearPosts } = usePosts();
  return (
    <header>
      <h1>
        <span>⚛️</span>The Atomic Blog
      </h1>
      <div>
        <Results />
        <SearchPosts />
        <button onClick={onClearPosts}>Clear posts</button>
      </div>
    </header>
  );
}
Note PostContext.js has ‘export { PostProvider, usePosts }’ and App.js has ‘import { PostProvider, usePosts }’.

Code:

<!DOCTYPE html>
<html lang=“en”>
  <head>
    <meta charset=“utf-8” />
    <link rel=“icon” href=“%PUBLIC_URL%/favicon.ico” />
    <meta name=“viewport” content=“width=device-width, initial-scale=1” />
    <meta name=“theme-color” content=“#000000” />
    <meta name=“description” content=“The atomic Blog” />
    <title>The Atomic Blog</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id=“root”></div>
  </body>
</html>
import React from “react”;
import ReactDOM from “react-dom/client”;
import “./style.css”;
import App from “./App”;

const root = ReactDOM.createRoot(document.getElementById(“root”));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
import { useEffect, useState } from “react”;
import { faker } from “@faker-js/faker”;

 

function createRandomPost() {
  return {
    title: `${faker.hacker.adjective()} ${faker.hacker.noun()}`,
    body: faker.hacker.phrase(),
  };
}

 

function App() {
  const [posts, setPosts] = useState(() =>
    Array.from({ length: 30 }, () => createRandomPost())
  );
  const [searchQuery, setSearchQuery] = useState(“”);
  const [isFakeDark, setIsFakeDark] = useState(false);

 

  // Derived state. These are the posts that will actually be displayed
  const searchedPosts =
    searchQuery.length > 0
      ? posts.filter((post) =>
          `${post.title} ${post.body}`
            .toLowerCase()
            .includes(searchQuery.toLowerCase())
        )
      : posts;

 

  function handleAddPost(post) {
    setPosts((posts) => [post, posts]);
  }

 

  function handleClearPosts() {
    setPosts([]);
  }

 

  // Whenever `isFakeDark` changes, we toggle the `fake-dark-mode` class on the HTML element (see in “Elements” dev tool).
  useEffect(
    function () {
      document.documentElement.classList.toggle(“fake-dark-mode”);
    },
    [isFakeDark]
  );

 

  return (
    <section>
      <button
        onClick={() => setIsFakeDark((isFakeDark) => !isFakeDark)}
        className=“btn-fake-dark-mode”
      >
        {isFakeDark ? “☀️” : “🌙”}
      </button>

 

      <Header
        posts={searchedPosts}
        onClearPosts={handleClearPosts}
        searchQuery={searchQuery}
        setSearchQuery={setSearchQuery}
      />
      <Main posts={searchedPosts} onAddPost={handleAddPost} />
      <Archive onAddPost={handleAddPost} />
      <Footer />
    </section>
  );
}

 

function Header({ posts, onClearPosts, searchQuery, setSearchQuery }) {
  return (
    <header>
      <h1>
        <span>⚛️</span>The Atomic Blog
      </h1>
      <div>
        <Results posts={posts} />
        <SearchPosts
          searchQuery={searchQuery}
          setSearchQuery={setSearchQuery}
        />
        <button onClick={onClearPosts}>Clear posts</button>
      </div>
    </header>
  );
}

 

function SearchPosts({ searchQuery, setSearchQuery }) {
  return (
    <input
      value={searchQuery}
      onChange={(e) => setSearchQuery(e.target.value)}
      placeholder=“Search posts…”
    />
  );
}

 

function Results({ posts }) {
  return <p>🚀 {posts.length} atomic posts found</p>;
}

 

function Main({ posts, onAddPost }) {
  return (
    <main>
      <FormAddPost onAddPost={onAddPost} />
      <Posts posts={posts} />
    </main>
  );
}

 

function Posts({ posts }) {
  return (
    <section>
      <List posts={posts} />
    </section>
  );
}

 

function FormAddPost({ onAddPost }) {
  const [title, setTitle] = useState(“”);
  const [body, setBody] = useState(“”);

 

  const handleSubmit = function (e) {
    e.preventDefault();
    if (!body || !title) return;
    onAddPost({ title, body });
    setTitle(“”);
    setBody(“”);
  };

 

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder=“Post title”
      />
      <textarea
        value={body}
        onChange={(e) => setBody(e.target.value)}
        placeholder=“Post body”
      />
      <button>Add post</button>
    </form>
  );
}

 

function List({ posts }) {
  return (
    <ul>
      {posts.map((post, i) => (
        <li key={i}>
          <h3>{post.title}</h3>
          <p>{post.body}</p>
        </li>
      ))}
    </ul>
  );
}

 

function Archive({ onAddPost }) {
  // Here we don’t need the setter function. We’re only using state to store these posts because the callback function passed into useState (which generates the posts) is only called once, on the initial render. So we use this trick as an optimization technique, because if we just used a regular variable, these posts would be re-created on every render. We could also move the posts outside the components, but I wanted to show you this trick 😉
  const [posts] = useState(() =>
    // 💥 WARNING: This might make your computer slow! Try a smaller `length` first
    Array.from({ length: 10000 }, () => createRandomPost())
  );

 

  const [showArchive, setShowArchive] = useState(false);

 

  return (
    <aside>
      <h2>Post archive</h2>
      <button onClick={() => setShowArchive((s) => !s)}>
        {showArchive ? “Hide archive posts” : “Show archive posts”}
      </button>

 

      {showArchive && (
        <ul>
          {posts.map((post, i) => (
            <li key={i}>
              <p>
                <strong>{post.title}:</strong> {post.body}
              </p>
              <button onClick={() => onAddPost(post)}>Add as new post</button>
            </li>
          ))}
        </ul>
      )}
    </aside>
  );
}

 

function Footer() {
  return <footer>&copy; by The Atomic Blog ✌️</footer>;
}

 

export default App;
import { memo, useEffect, useState } from “react”;
import { faker } from “@faker-js/faker”;
import { PostProvider, usePosts } from “./PostContext”;

function createRandomPost() {
  return {
    title: `${faker.hacker.adjective()} ${faker.hacker.noun()}`,
    body: faker.hacker.phrase(),
  };
}

function App() {
  // Whenever `isFakeDark` changes, we toggle the `fake-dark-mode` class on the HTML element (see in “Elements” dev tool).
  const [isFakeDark, setIsFakeDark] = useState(false);

  useEffect(
    function () {
      document.documentElement.classList.toggle(“fake-dark-mode”);
    },
    [isFakeDark]
  );

  return (
    <section>
      <button
        onClick={() => setIsFakeDark((isFakeDark) => !isFakeDark)}
        className=“btn-fake-dark-mode”
      >
        {isFakeDark ? “☀️” : “🌙”}
      </button>

      <PostProvider>
        <Header />
        <Main />
        <Archive />
        <Footer />
      </PostProvider>
    </section>
  );
}

function Header() {
  // 3) CONSUMING CONTEXT VALUE
  const { onClearPosts } = usePosts();

  return (
    <header>
      <h1>
        <span>⚛️</span>The Atomic Blog
      </h1>
      <div>
        <Results />
        <SearchPosts />
        <button onClick={onClearPosts}>Clear posts</button>
      </div>
    </header>
  );
}

function SearchPosts() {
  const { searchQuery, setSearchQuery } = usePosts();

  return (
    <input
      value={searchQuery}
      onChange={(e) => setSearchQuery(e.target.value)}
      placeholder=“Search posts…”
    />
  );
}

function Results() {
  const { posts } = usePosts();

  return <p>🚀 {posts.length} atomic posts found</p>;
}

const Main = memo(function Main() {
  return (
    <main>
      <FormAddPost />
      <Posts />
    </main>
  );
});

function Posts() {
  return (
    <section>
      <List />
    </section>
  );
}

function FormAddPost() {
  const { onAddPost } = usePosts();
  const [title, setTitle] = useState(“”);
  const [body, setBody] = useState(“”);

  const handleSubmit = function (e) {
    e.preventDefault();
    if (!body || !title) return;
    onAddPost({ title, body });
    setTitle(“”);
    setBody(“”);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder=“Post title”
      />
      <textarea
        value={body}
        onChange={(e) => setBody(e.target.value)}
        placeholder=“Post body”
      />
      <button>Add post</button>
    </form>
  );
}

function List() {
  const { posts } = usePosts();

  return (
    <>
      <ul>
        {posts.map((post, i) => (
          <li key={i}>
            <h3>{post.title}</h3>
            <p>{post.body}</p>
          </li>
        ))}
      </ul>

      {/* <Test /> */}
    </>
  );
}

function Archive() {
  const { onAddPost } = usePosts();

  // Here we don’t need the setter function. We’re only using state to store these posts because the callback function passed into useState (which generates the posts) is only called once, on the initial render. So we use this trick as an optimization technique, because if we just used a regular variable, these posts would be re-created on every render. We could also move the posts outside the components, but I wanted to show you this trick 😉
  const [posts] = useState(() =>
    // 💥 WARNING: This might make your computer slow! Try a smaller `length` first
    Array.from({ length: 10000 }, () => createRandomPost())
  );

  const [showArchive, setShowArchive] = useState(false);

  return (
    <aside>
      <h2>Post archive</h2>
      <button onClick={() => setShowArchive((s) => !s)}>
        {showArchive ? “Hide archive posts” : “Show archive posts”}
      </button>

      {showArchive && (
        <ul>
          {posts.map((post, i) => (
            <li key={i}>
              <p>
                <strong>{post.title}:</strong> {post.body}
              </p>
              <button onClick={() => onAddPost(post)}>Add as new post</button>
            </li>
          ))}
        </ul>
      )}
    </aside>
  );
}

function Footer() {
  return <footer>&copy; by The Atomic Blog ✌️</footer>;
}

export default App;
import { createContext, useContext, useMemo, useState } from “react”;
import { faker } from “@faker-js/faker”;

function createRandomPost() {
  return {
    title: `${faker.hacker.adjective()} ${faker.hacker.noun()}`,
    body: faker.hacker.phrase(),
  };
}

// 1) CREATE A CONTEXT
const PostContext = createContext();

function PostProvider({ children }) {
  const [posts, setPosts] = useState(() =>
    Array.from({ length: 30 }, () => createRandomPost())
  );
  const [searchQuery, setSearchQuery] = useState(“”);

  // Derived state. These are the posts that will actually be displayed
  const searchedPosts =
    searchQuery.length > 0
      ? posts.filter((post) =>
          `${post.title} ${post.body}`
            .toLowerCase()
            .includes(searchQuery.toLowerCase())
        )
      : posts;

  function handleAddPost(post) {
    setPosts((posts) => [post, posts]);
  }

  function handleClearPosts() {
    setPosts([]);
  }

  const value = useMemo(() => {
    return {
      posts: searchedPosts,
      onAddPost: handleAddPost,
      onClearPosts: handleClearPosts,
      searchQuery,
      setSearchQuery,
    };
  }, [searchedPosts, searchQuery]);

  return (
    // 2) PROVIDE VALUE TO CHILD COMPONENTS
    <PostContext.Provider value={value}>{children}</PostContext.Provider>
  );
}

function usePosts() {
  const context = useContext(PostContext);
  if (context === undefined)
    throw new Error(“PostContext was used outside of the PostProvider”);
  return context;
}

export { PostProvider, usePosts };
* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}

body {
  font-family: system-ui, -apple-system, BlinkMacSystemFont, “Segoe UI”, Roboto,
    Oxygen, Ubuntu, Cantarell, “Open Sans”, “Helvetica Neue”, sans-serif;

  color: #222;
  line-height: 1.25;
  background: #fff;
  padding: 32px;
}

.fake-dark-mode {
  filter: invert(100%);
  transition: all 0.5s;
}

.btn-fake-dark-mode {
  position: fixed;
  top: 0;
  right: 0;
  padding: 16px;
  line-height: 1;
  font-size: 26px;
  background-color: #ffe8cc;
  border: none;
}

section {
  max-width: 1140px;
  margin: 0 auto;
}

header {
  margin-bottom: 32px;
  font-weight: bold;

  justify-content: space-between;
  display: flex;
}

h1 {
  font-size: 26px;
  display: flex;
  gap: 8px;
  align-items: center;
}

h1 span {
  font-size: 140%;
  line-height: 1;
}

h2 {
  text-transform: uppercase;
  margin-bottom: 24px;
  color: #333;
}

h3 {
  text-transform: capitalize;
  margin-bottom: 16px;
  color: #333;
}

header div {
  display: flex;
  gap: 32px;
  align-items: center;
}

header button {
  font-size: 14px;
}

form {
  padding: 24px;
  background-color: #fff4e6;
  display: flex;
  gap: 24px;
  margin-bottom: 40px;
}

main {
  margin-bottom: 40px;
}

aside {
  margin-bottom: 40px;
  opacity: 75%;
}

input,
textarea,
button {
  font-size: 16px;
  padding: 8px 12px;
  border: 1px solid #ffe8cc;
  font-family: inherit;
  color: inherit;
}

input {
  width: 12rem;
}

*::placeholder {
  color: #999;
}

*:focus {
  outline: 2px solid #ffa94d;
  border-radius: 2px;
}

textarea {
  flex: 1;
  height: 50px;
}

button {
  border: 1px solid #ffa94d;
  background-color: #ffa94d;
  font-weight: 700;
  padding: 9px 24px;
  cursor: pointer;
}

h3 {
  text-transform: capitalize;
  margin-bottom: 16px;
  color: #333;
}

main ul {
  list-style: none;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  gap: 32px;
}

main li {
  border: 1px solid #ffe8cc;
  padding: 16px 20px;
}

main li:hover {
  background-color: #fff4e6;
}

aside ul {
  font-size: 90%;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

aside li {
  border: 1px solid #ffe8cc;
  padding: 4px 8px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

h2 + button {
  margin-bottom: 24px;
}

aside li button {
  padding: 4px 8px;
  font-size: 14px;
}