I've got three themes in my project and I want to handle the theme switching process with React Context API, but I get this "Maximum update depth exceeded" error. how can I handle this?
here is my data layer:
import React from "react";
// Data Layer
export const StateContext = React.createContext();
// Provider
export const StateProvider = ({ reducer, initialState, children }) => (
<StateContext.Provider value={React.useReducer(reducer, initialState)}>
{children}
</StateContext.Provider>
);
export const useStateValue = () => React.useContext(StateContext);
here is my reducer:
export const initialState = {
theme: "",
};
export const reducer = (state, action) => {
if (action.type === "SET_THEME") {
return {
...state,
theme: action.userTheme,
};
} else {
return state;
}
};
here is index.js:
import { StateProvider } from "./context/StateProvider";
import { initialState, reducer } from "./context/reducer";
ReactDOM.render(
<React.StrictMode>
<StateProvider initialState={initialState} reducer={reducer}>
<App />
</StateProvider>
</React.StrictMode>,
document.getElementById("root")
);
The user switch the themes inside the navbar component, here is my navbar:
import { useStateValue } from "../context/StateProvider";
export const Nav = () => {
const [state, dispatch] = useStateValue();
const setTheme = (theme) => {
dispatch({
type: "SET_THEME",
userTheme: theme,
});
};
return (
<>
<div onClick={setTheme("theme-orange")}>
<a id="orange" href="javascript:void(0)">
orange
</a>
</div>
<div onClick={setTheme("theme-green")}>
<a id="green" href="javascript:void(0)">
green
</a>
</div>
<div onClick={setTheme("theme-blue")}>
<a id="blue" href="javascript:void(0)">
blue
</a>
</div>
</>
)
}
and finally here is the App.js:
import { useStateValue } from "./context/StateProvider";
function App() {
const [{ theme }] = useStateValue();
return (
<div className={theme}> // Theme applied from context
<Nav />
<Home />
<About />
<Portfolio />
<Contact />
</div>
);
}
You are performing setState on render on Nav component. Try:
<div onClick={() => setTheme("theme-orange")}>
<a id="orange" href="javascript:void(0)">
orange
</a>
</div>
Related
I am following the WhatsApp clone on YouTube, I did exactly what they were doing but I don't know why I'm getting this error. I was
I read a lot of blogs, but I couldn't resolve it.
In app, it gives this error and couldn't dismiss.
./src/App.js Line 10: 'dispatch' is assigned a value but never used no-unused-vars
In login, it gives this error.
./src/Login.js Line 9: Unexpected empty object pattern no-empty-pattern
<!-- begin snippet: js hide: false console: true babel: false -->
import React from "react";
import "./Login.css";
import { Button } from "#mui/material";
import { auth, provider } from "./firebase";
import { useStateValue } from "./StateProvider";
import { actionTypes } from "./reducer";
function Login() {
const [value, dispatch] = useStateValue({});
// const [value, dispatch] = useStateValue({})
// const [{ type, user }, dispatch] = useStateValue();
const signIn = () => {
auth
.signInWithPopup(provider)
.then((result) => {
dispatch({
type: actionTypes.SET_USER,
user: result.user,
});
})
.catch((error) => alert(error.message));
};
return (
<div className="login">
<div className="login__container">
<img
src="https://www.freepnglogos.com/uploads/whatsapp-logo-png-hd-2.png"
alt=""
/>
<div className="login__text">
<h1>Sign in to WhatsApp</h1>
</div>
<Button onClick={signIn}>Sign In with Google</Button>
</div>
</div>
);
}
export default Login;
import React from "react";
import "./App.css";
import Sidebar from "./Sidebar";
import Chat from "./Chat";
import Login from "./Login";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { useStateValue } from "./StateProvider";
function App() {
const [{ user }, dispatch] = useStateValue();
return (
<div className="app">
{!user ? (
<Login />
) : (
<div className="app__body">
<Router>
<Sidebar />
<Routes>
<Route path="/rooms/:roomId" element={<Chat />} />
<Route path="/" element={<Chat />} />
</Routes>
</Router>
</div>
)}
</div>
);
}
export default App;
import React, { createContext, useContext, useReducer } from "react";
export const StateContext = createContext();
export const StateProvider = ({ reducer, initialState, children }) => (
<StateContext.Provider value={useReducer(reducer, initialState)}>
{children}
</StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);
export const initialState = {
user: null,
};
export const actionTypes = {
SET_USER: "SET_USER",
};
const reducer = (state, action) => {
console.log(action);
switch (action.type) {
case actionTypes.SET_USER:
return {
...state,
user: action.user,
};
default:
return state;
}
};
export default reducer;
In my current project I'm using React Context to save component references so that the header component can access them to scroll to them. I managed to successfully make it work the first time with contactRef. But when I tried to add more states to the context, they just would not register.
Console logging the context in Header.js gives me;
contactRef: {current: div.contact}
dispatch: ƒ ()
findings: undefined
locationRef: undefined
[[Prototype]]: Object
I've attached the segments involved with this, but I've narrowed down the issue to be with the INITIAL_STATE in ComponentContext.js. Adding more states does not seem to work, every time only contactRef seems to be initialised.
ComponentContext.js
import { createContext, useReducer } from "react";
const INITIAL_STATE = {
contactRef: null,
locationRef: null,
findings: true,
};
export const ComponentContext = createContext(INITIAL_STATE);
const componentReducer = (state, action) => {
switch (action.type) {
case "contact":
return { contactRef: action.ref };
case "location":
return { locationRef: action.ref };
default:
return state;
}
};
export const ComponentProvider = (props) => {
const [state, dispatch] = useReducer(componentReducer, INITIAL_STATE);
return (
<ComponentContext.Provider
value={{
contactRef: state.contactRef,
locationRef: state.locationRef,
findings: state.findings,
dispatch,
}}
>
{props.children}
</ComponentContext.Provider>
);
};
Contact.js
import React, { useContext, useEffect, useRef } from "react";
import "./index.scss";
import { contactInfo } from "../../data/contactInfo";
import ContactPhoneIcon from "#mui/icons-material/ContactPhone";
import EmailIcon from "#mui/icons-material/Email";
import { ComponentContext } from "../../context/ComponentContext";
const Contact = () => {
const componentContext = useContext(ComponentContext);
const contactRef = useRef();
useEffect(() => {
componentContext.dispatch({ type: "contact", ref: contactRef });
}, []);
return (
<div className="contact" ref={contactRef}>
<div className="contact-accent"></div>
<div className="contact-body">
<div className="contact-left">
<h1 className="contact-title">Hit Me up!</h1>
<div className="contact-info">
<div className="contact-info-item">
<ContactPhoneIcon className="contact-info-icon" />
{contactInfo.phone}
</div>
<div className="contact-info-item">
<EmailIcon className="contact-info-icon" />
{contactInfo.email}
</div>
</div>
</div>
<div className="contact-right">
<p className="contact-description">
<b>I'm great with kids</b> <i>Sejarah</i> has been my passion since
high school and I'd love to show that to your kids; that history is
not just a boring compulsory subject for SPM.
</p>
</div>
</div>
</div>
);
};
export default Contact;
Header.js
import "./index.scss";
import React, { useContext } from "react";
import { ComponentContext } from "../../context/ComponentContext";
const Header = () => {
const componentContext = useContext(ComponentContext);
return (
<div className="header">
<div className="header-logo"></div>
<div className="header-sections">
<div className="header-section-item">Introduction</div>
<div className="header-section-item">About My Classes</div>
<div
className="header-section-item"
onClick={() => {
componentContext.contactRef.current.scrollIntoView({
behavior: "smooth",
});
}}
>
Contact Me
</div>
<div
className="header-section-item"
onClick={() => {
// componentContext.state.locationRef.current.scrollIntoView({
// behavior: "smooth",
// });
console.log(componentContext);
}}
>
Location
</div>
</div>
</div>
);
};
export default Header;
I am having issues managing the state of my navbar using useContext. Atm my app renders the menu items as soon as the menu toggle. I want this event to happen only onClick, also the button does not log the console.log message, it works only when I click directly on the link item ex:home.
So I have 2 questions.
How do I manage my navbar state to show how to hide the menu items without having to create a new component for it?
How do I fix my click event for it be triggered on either the menu button itself or/and menu items?
Below you will code snippets for App.js, Layout.js, ThemeContext.js, useTheme.js, useToggle.js, ToggleContext.js and the Navbar where the toggle context is used.
I would really appreciate some help here guys, I am junior and really kind of stuck here.
Thanks in advance to you all.
App.js
//import { data } from '../../SkillData';
import Header from './Header';
import Navbar from './Navbar';
import Skills from './Skills';
import Layout from './Layout';
function App () {
return (
<Layout startingTheme="light" startingToggle={"show"}>
<div>
<Navbar />
<Header />
<Skills />
</div>
</Layout>
);
}
export default App;
Layout.js
import React, { useContext } from "react";
import { ThemeContext, ThemeProvider } from "../contexts/ThemeContext";
import { ToggleContext, ToggleProvider } from "../contexts/ToggleContext";
function Layout ({startingTheme, startingToggle, children}) {
return (
<>
<ThemeProvider startingTheme={startingTheme} >
<ToggleProvider startingToggle={startingToggle}>
<LayoutNoToggleProvider>
</LayoutNoToggleProvider>
</ToggleProvider>
<LayoutNoThemeProvider >{children}</LayoutNoThemeProvider>
</ThemeProvider>
</>
);
}
function LayoutNoToggleProvider ({children}) {
const toggle = useContext(ToggleContext);
return (
<div className={
toggle === false ? "navbar navbar-collapsed" : "navbar navbar-collapse show"
}>
{children}
</div>
)
}
function LayoutNoThemeProvider ({ children }) {
const {theme} = useContext(ThemeContext);
return (
<div className={
theme === "light" ?
"container-fluid bg-white" :
"container-fluid bg-dark"
}>
{children}
</div>
);
}
export default Layout;
ThemeContext
import React, { createContext} from "react";
import useTheme from "../hooks/useTheme";
export const ThemeContext = createContext();
function ThemeProvider ({children, startingTheme}) {
const { theme, setTheme } = useTheme(startingTheme);
return (
<ThemeContext.Provider value={
{theme, setTheme}
}>
{children}
</ThemeContext.Provider>
);
}
export { ThemeProvider };
useTheme.js
import { useState } from "react";
function useTheme (startingTheme ="light") {
const [theme, setTheme] = useState(startingTheme);
function validateTheme (themeValue) {
if (themeValue === "dark") {
setTheme("dark");
} else {
setTheme("light");
}
}
return {
theme,
setTheme: validateTheme,
}
}
export default useTheme;
ToggleContext.js
import React, { createContext } from "react";
import useToggle from "../hooks/useToggle";
export const ToggleContext = createContext();
function ToggleProvider({ children, startingToggle }) {
const { toggle, setToggle } = useToggle(startingToggle);
return (
<ToggleContext.Provider value={{ toggle, setToggle }}>
{children}
</ToggleContext.Provider>
);
}
export { ToggleProvider };
useToggle.js
import { useState } from "react";
function useToggle (startingToggle = false) {
const [toggle, setToggle] = useState(startingToggle);
function validateShowSidebar (showSidebarValue) {
if (showSidebarValue === "show") {
setToggle("show");
} else {
setToggle("");
}
}
return {
toggle,
setToggle: validateShowSidebar,
}
}
export default useToggle;
Navbar.js
import Image from "next/image";
import styles from "../../styles/Home.module.scss"
import Logo from "../../public/Knowledge Memo.svg"
import { useContext } from "react";
import { ThemeContext } from "../contexts/ThemeContext";
import { ToggleContext } from "../contexts/ToggleContext";
import Link from 'next/link';
import { useState } from "react";
const navbarData = [
{ id: "1",
title: "home",
ref: "#home"
},
{ id:"2",
title: "Skills",
ref: "#skills"
},
{ id:"3",
title: "The List",
ref: "#theList"
},
{ id: "4",
title: "Team",
ref: "#team"
},
{ id: "5",
title: "Contact",
ref: "#contact"
},
];
function Navbar() {
const theme = useContext(ThemeContext);
const toggle = useContext(ToggleContext);
return (
<>
<nav className={
theme === "light" ?
"navbar navbar-expand-lg navbar-dark fixed-top":
"navbar navbar-expand-lg navbar-dark bg-dark fixed-top id= mainNav"}>
<div className="container d-flex flex justify-content-between">
<a className="navbar-brand h-50" href="#page-top">
<div className="navbar-brand">
<Image
src={Logo}
alt="..."
fill="#fff"
objectFit="contain"
className="h-50"
/>
</div>
</a>
<button
onClick={ () => toggle === !toggle, console.log("clicked")}
className="navbar-toggler collapsed"
type="button"
data-bs-toggle="collapsed"
data-bs-target="#navbarResponsive"
aria-controls="navbarResponsive"
aria-expanded="false"
aria-label="Toggle navigation"
>
Menu
<i className="fa fa-bars ms-1 navbar-toggler" aria-hidden="true"></i>
</button>
{toggle ?
<div className="collapsed navbar-collapse mt-2 id=navbarResponsive">
<ul className="navbar-nav text-uppercase ms-auto py-4 py-lg-0">
{navbarData.map((link,idx) => {
return (
<li key={link.id}>
<Link href={`/${link.ref}`} className="nav-item" data-index={idx} passHref>
<a className="nav-link">
{link.title}
</a>
</Link>
</li>
);
})}
</ul>
</div>
: <div className="collapse navbar-collapse show mt-2 id=navbarResponsive">
<ul className="navbar-nav show text-uppercase ms-auto py-4 py-lg-0">
{navbarData.map((link,idx) => {
return (
<li key={link.id}>
<Link href={`/${link.ref}`} className="nav-item" data-index={idx} passHref>
<a className="nav-link">
{link.title}
</a>
</Link>
</li>
);
})}
</ul>
</div>}
</div>
</nav>
</>
);
}
export default Navbar;
You can try out this implemetation with reducers to handle for you the state change with localstorage. It is not an exact implemetation of your's but you can see the flow
In the AppContext.jsx
The AppContext holds the global state of the application so that it's easier working with a single context provider and dispatching actons to specific reducers to handle state change without providing many providers.
The combinedReducers handle reducer methods to a given state component
import { useReducer, createContext, useEffect } from "react";
import userReducer from "./reducers/userReducer";
import themeReducer from "./reducers/themeReducer";
export const APP_NAME = "test_app";
//Check the localstorage or set a default state
const initialState = JSON.parse(localStorage.getItem(APP_NAME))
? JSON.parse(localStorage.getItem(APP_NAME))
: {
user: {
username: "",
email: "",
isAdmin: false,
},
theme: { dark: false },
};
//Create your global context
const AppContext = createContext(initialState);
//Create combined reducers
const combinedReducers = ({ user, theme }, action) => ({
user: userReducer(user, action),
theme: themeReducer(theme, action),
});
const AppState = ({ children }) => {
//Making it to provider state
const [state, dispatch] = useReducer(combinedReducers, initialState);
useEffect(() => {
localStorage.setItem(APP_NAME, JSON.stringify(state));
}, [state]);
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
};
export default AppState;
export { AppContext, AppState };
The above implementation works like redux but you destructure the given state to a specific reducer to handle the state change
In this I have used localstorage to keep a persistent state because with context API on page reload the state goes. Use the useEffect hook from react and add the state in the dependency array to ensure your state is in sync
In the UserReducer.jsx
const userReducer = (state, action) => {
const { type, payload } = action;
switch (type) {
case "LOGIN":
return { ...state, ...payload };
case "LOGOUT":
return {};
default:
return state;
}
};
export default userReducer;
In the ThemeReducer.jsx
const themeReducer = (state, action) => {
const { type, payload } = action;
switch (type) {
case "DARK":
return { ...payload };
default:
return state;
}
};
export default themeReducer;
Wrapping the whole app with a single provider in the index.jsx
import reactDom from "react-dom"
import React from "react"
import App from "./App"
import "./index.css"
import AppState from "./state/AppState"
reactDom.render(
<React.StrictMode>
<AppState >
<App />
</AppState>
</React.StrictMode>,
document.getElementById("root")
)
Accessing the context from App.jsx
import { useContext } from "react";
import { AppContext } from "./state/AppState";
const App = () => {
const { state, dispatch } = useContext(AppContext);
const handleLogin = () => {
dispatch({
type: "LOGIN",
payload: {
username: "Mike",
email: "mike#gmail.com",
isAdmin: false,
},
});
};
const handleLogout = () => {
dispatch({
type: "LOGOUT",
payload: {},
});
};
return (
<div className="main-container">
<div className="container">
<p>Username: {state.user.username ? state.user.username : "Unknown"}</p>
<p>Email: {state.user.email ? state.user.email : "Unknown"}</p>
</div>
<button onClick={handleLogin}>Login</button>
<button onClick={handleLogout} style={{ background: "red" }}>
Login
</button>
</div>
);
};
export default App;
Here is my code LINK if you want to see the structure Github
I'm currently struggling with React Context. I'd like to pass functions allowing the show / hide cart logic in the context, instead of using props between components.
I dont understand why when clicking on the button in HeaderCartButton component, it doesn't trigger the **onClick={ctx.onShowCart}** that is in my context, even though when I console log the cartCtx.state it is properly updated, which should then add the component in the App.js
//App.js
import { useContext } from "react";
import Header from "./components/Layout/Header";
import Meals from "./components/Meals/Meals";
import Cart from "./components/Cart/Cart";
import CartProvider from "./store/CartProvider";
import CartContext from "./store/cart-context";
function App() {
const ctx = useContext(CartContext);
return (
<CartProvider>
{ctx.state && <Cart />}
<Header />
<main>
<Meals />
</main>
</CartProvider>
);
}
export default App;
//cart-context.js
import React from "react";
const CartContext = React.createContext({
state: false,
onShowCart: () => {},
onHideCart: () => {},
items: [],
totalAmount: 0,
addItem: (item) => {},
removeItem: (id) => {},
});
export default CartContext;
//CartProvider.js
import CartContext from "./cart-context";
import { useState } from "react";
const CartProvider = (props) => {
const [cartIsShown, setCartIsShown] = useState(false);
const showCartHandler = () => {
setCartIsShown(true);
};
const hideCartHandler = () => {
setCartIsShown(false);
};
const handleAddItem = (item) => {};
const handleRemoveItem = (id) => {};
const cartCtx = {
state: cartIsShown,
onShowCart: showCartHandler,
onHideCart: hideCartHandler,
items: [],
totalAmount: 0,
addItem: handleAddItem,
removeItem: handleRemoveItem,
};
return (
<CartContext.Provider value={cartCtx}>
{props.children}
</CartContext.Provider>
);
};
export default CartProvider;
//Header.js
import { Fragment } from "react";
import HeaderCartButton from "./HeaderCartButton";
import mealsImage from "../../assets/meals.jpg";
import classes from "./Header.module.css";
const Header = (props) => {
return (
<Fragment>
<header className={classes.header}>
<h1>ReactMeals</h1>
<HeaderCartButton />
</header>
<div className={classes["main-image"]}>
<img src={mealsImage} alt="A table full of delicious food!" />
</div>
</Fragment>
);
};
export default Header;
//HeaderCartButton.js
import CartIcon from "../Cart/CartIcon";
import { useContext } from "react";
import classes from "./HeaderCartButton.module.css";
import CartContext from "../../store/cart-context";
const HeaderCartButton = (props) => {
const ctx = useContext(CartContext);
const numberOfCartItems = ctx.items.reduce((accumulator, item) => {
return accumulator + item.amount;
}, 0);
return (
<button className={classes.button} onClick={ctx.onShowCart}>
<span className={classes.icon}>
<CartIcon />
</span>
<span>Your Cart</span>
<span className={classes.badge}>{numberOfCartItems}</span>
</button>
);
};
export default HeaderCartButton;
Thanks for your help
If you look at your App component, you are using CartContext outside the provider.
function App() {
const ctx = useContext(CartContext);
return (
<CartProvider>
{ctx.state && <Cart />}
<Header />
<main>
<Meals />
</main>
</CartProvider>
);
}
You should modify it so that it is similar to the following, where you are using the context inside the provider.
const Main = () => {
return <CartProvider><App /></CartProvider>
}
function App() {
const ctx = useContext(CartContext);
return (
<>
{ctx.state && <Cart />}
<Header />
<main>
<Meals />
</main>
</>
);
}
import React, { Fragment, useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import { getPosts } from '../redux/actions/#posts';
import PostItem from '../components/posts/PostItem';
import CommentForm from '../components/posts/CommentForm';
import Comment from '../components/posts/Comment';
import '../styles/posts/postComponent.scss';
const Posts = ({
getPosts,
posts: { posts, isLoading },
isAuthenticated,
user
}) => {
useEffect(() => {
getPosts();
}, []);
const [show, toggleShow] = useState(false);
console.log(show);
const postsList = isLoading ? (
<div>posts are loading</div>
) : (
posts.map(post => {
return (
<div className='post'>
<PostItem
key={post._id}
auth={isAuthenticated}
user={user}
id={post._id}
title={post.title}
body={post.text}
author={post.name}
avatar={post.avatar}
date={post.date}
likes={post.likes}
comments={post.comments.map(comment => comment)}
toggleShow={toggleShow}
show={show}
/>
<CommentForm id={post._id} />
{post.comments.map(
comment =>
show && (
<Comment
key={comment._id}
comment={comment}
auth={isAuthenticated}
admin={user}
show={show}
/>
)
)}
</div>
);
})
);
return (
<Fragment>
<Link to='/add-post'>add Post</Link>
<div>{postsList}</div>
</Fragment>
);
};
Posts.propTypes = {
getPosts: PropTypes.func.isRequired,
posts: PropTypes.object.isRequired,
isAuthenticated: PropTypes.bool.isRequired
};
const mapStateToProps = state => {
// console.log(state.posts.posts.map(post => post.likes));
// console.log(state);
return {
posts: state.posts,
isAuthenticated: state.auth.isAuthenticated,
user: state.auth.user
};
};
export default connect(mapStateToProps, { getPosts })(Posts);
import React, { Fragment } from 'react';
import '../../styles/posts/postComponent.scss';
const Comment = ({
comment: { user, avatar, name, date, text },
admin,
auth
}) => {
return (
<Fragment>
<div className='c-container'>
<div className='c-img-text'>
<img className='c-img' height={'40px'} src={avatar} />
<div className='c-nt'>
<a href='#' className='c-n'>
{name}
</a>
<span className='c-t'> {text}</span>
<i className='c-d'>{date}</i>
</div>
{auth && admin
? admin._id === user && <div className='c-toggle'>...</div>
: ''}
</div>
</div>
</Fragment>
);
};
export default Comment;
I have a list of posts stored in redux, and mapped through it to create components. Now each component has a some body and comments.
I want to show the comments only after onClick event .
Below is the code I have come up with , and on Click it is toggling all the comments of all the Components.How can I toggle comments of an individual Component.