So i have started using useContext and having trouble resolving a task with it.
I have a state outside my provider function but i dont want to use prop drilling even first level for my button component. I tried to import it from provider function but failed because it is outside this function. Any solutions ?
import { useContext, useEffect, useState } from "react";
import { faker } from "@faker-js/faker";
import { PostProvider, PostContext } from "./PostContext";
function createRandomPost() {
return {
title: `${faker.hacker.adjective()} ${faker.hacker.noun()}`,
body: faker.hacker.phrase(),
};
}
function App() {
const [isFakeDark, setIsFakeDark] = useState(false);
// 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]
);
// PROBLEM HERE I WANNA HAVE <Button /> without props
return (
// 2) Provide value to child components
<PostProvider>
<section>
<Button setIsFakeDark={setIsFakeDark} isFakeDark={isFakeDark} />
<Header />
<Main />
<Archive />
<Footer />
</section>
</PostProvider>
);
}
function Button({ setIsFakeDark, isFakeDark }) {
return (
<button
onClick={() => setIsFakeDark((isFakeDark) => !isFakeDark)}
className="btn-fake-dark-mode"
>
{isFakeDark ? "βοΈ" : "π"}
</button>
);
}
function Header() {
// 3) Consuming context value
const { onClearPosts } = useContext(PostContext);
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 } = useContext(PostContext);
return (
<input
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search posts..."
/>
);
}
function Results() {
const { posts } = useContext(PostContext);
return <p>π {posts.length} atomic posts found</p>;
}
function Main() {
return (
<main>
<FormAddPost />
<Posts />
</main>
);
}
function Posts() {
return (
<section>
<List />
</section>
);
}
function FormAddPost() {
const { onAddPost } = useContext(PostContext);
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 } = useContext(PostContext);
return (
<ul>
{posts.map((post, i) => (
<li key={i}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</li>
))}
</ul>
);
}
function Archive() {
const { onAddPost } = useContext(PostContext);
// 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: 100 }, () => 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>© by The Atomic Blog βοΈ</footer>;
}
export default App;
Second FILE
import { faker } from "@faker-js/faker";
import { createContext, useState } from "react";
function createRandomPost() {
return {
title: `${faker.hacker.adjective()} ${faker.hacker.noun()}`,
body: faker.hacker.phrase(),
};
}
// 1) Create new Context
const PostContext = createContext();
function PostProvider({ children }) {
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([]);
}
return (
<PostContext.Provider
value={{
posts: searchedPosts,
onAddPost: handleAddPost,
onClearPosts: handleClearPosts,
searchQuery,
setSearchQuery,
setIsFakeDark,
isFakeDark,
}}
>
{children}
</PostContext.Provider>
);
}
export { PostProvider, PostContext };
2
Answers
i just moved useEffect to Provider file :)
If I’m understanding correctly, this is about the values
setIsFakeDark
andisFakeDark
and you want to avoid passing them as props toPostProvider
.I would put the theme in its own "
ThemeContext
", and leave thePostContext
for providing and managing posts only.