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
Related
I have a React project using Tailwind CSS and I want the sidebar to take the full height below the logo and have the links to the left with a specific width, but it is not working
profile.jsx
import { useContext, useState } from "react";
import { useLocation } from "react-router-dom";
import { UserContext } from "../App";
import ProfileView from "./profileView"
function Profile() {
const location = useLocation();
const msg = location.state?.mes;
const [success, setSuccess] = useState(msg === undefined ? "" : msg);
const [cancel, setCancel] = useState(msg === undefined ? "" : "X");
const [name, setName] = useState(
msg === undefined
? "h-0"
: "h-10 flex justify-around items-center bg-green-200 text-black"
);
const { state, dispatch } = useContext(UserContext);
function handleClick() {
setSuccess("");
setCancel("");
setName("h-0");
}
return (
<>
<div className={name}>
{success}
<button onClick={handleClick}>{cancel}</button>
</div>
{state.logStatus ? (
<div className="h-full">
<ProfileView />
</div>
) : (
<div className="h-96 bg-red-200 flex justify-center items-center text-3xl font-bold">
<div>You need to login in order to view your profile!</div>
</div>
)}
</>
);
}
export default Profile;
profileView.jsx
import { Component, useContext, useEffect, useState } from "react";
import { UserContext } from "../App";
import AdminProfile from "./adminProfile";
import StudentProfile from "./studentProfile";
import TeacherProfile from "./teacherProfile";
function ProfileView() {
const { state, dispatch } = useContext(UserContext);
return (
<div className="h-full">
{state.identity.id === "admin" ? (
<AdminProfile />
) : state.identity.id === "teacher" ? (
<TeacherProfile />
) : (
<StudentProfile />
)}
</div>
);
}
export default ProfileView;
studentProfile.jsx
import { SiGoogleclassroom } from "react-icons/si";
import { FaHouseUser } from "react-icons/fa";
import { MdGrade } from "react-icons/md";
import { MdManageAccounts } from "react-icons/md";
import { Link } from "react-router-dom";
const side = [
{ title: "Class", icon: <SiGoogleclassroom />, link: "/class" },
{ title: "Dormitory", icon: <FaHouseUser />, link: "/dormitory" },
{ title: "Grade", icon: <MdGrade />, link: "/grade" },
{ title: "Account", icon: <MdManageAccounts />, link: "/account" },
];
function StudentProfile() {
return (
<div className="bg-[#2f4050] text-white box-border w-1/4 h-full">
{side.map((val, index) => {
return (
<Link to={val.link} key={index}>
<div>{val.icon}</div>
<div>{val.title}</div>
</Link>
);
})}
</div>
);
}
export default StudentProfile;
The section is not taking the full height because you haven't defined a height for the parent of the following component (which is react fragment)
<div className="h-full">
<ProfileView />
</div>
So giving a value for the height of the above component would get your job done.
Note: Since a fragment is the parent of the above component, you have to replace it with a JSX element.
<div className="h-[100vh]">
<div className="h-full">
<ProfileView />
</div>
</div>
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'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>
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.
I am a beginner in React js and I'm trying to implement Context Provider in React js. But while I'm not getting the perfect output.
I am storing contact info in context.js which will act as Context Provider and in App.js I imported it in App.js then in Contacts.js I Consumed the Consumer and got the value but still, I'm getting the blank page and I'm not sure why I cannot bind the contact component in Context provider
Context.js
import React, {Component} from 'react';
const Context = React.createContext();
export class Provider extends Component {
state = {
contacts: [{
id: 1,
name: "dasd B",
email: "asdas#gmail.com",
phone: "dsadas"
}
};
render() {
debugger
return (
<Context.Provider value={this.state}>
{this.props.childern}
</Context.Provider>
);
}
}
export const Consumer = Context.Consumer;
App.js
import React, { Component } from 'react';
import Header from './components/Header';
import Contacts from './components/Contacts'
import 'bootstrap/dist/css/bootstrap.min.css';
import { Provider } from './Context'
class App extends Component {
render() {
return (
<Provider>
<div className="App">
<div className="container">
<Contacts />
</div>
</div>
</Provider>
);
}
}
export default App;
Contacts.js
import React, { Component } from 'react'
import Contact from './Contact';
import { Consumer } from '../Context';
class Contacts extends Component {
deleteContact = id => {
const { contacts } = this.state;
const newContacts = contacts.filter(contact => contact.id!== id);
this.setState({
contacts: newContacts
});
};
render() {
debugger
return(
<Consumer>
{value => {
const { contacts } = value;
return (
<React.Fragment >
{contacts.map(contact => (
<Contact
key = {contact.id}
contact={contact}
deleteClickHandler = {this.deleteContact.bind(this, contact.id)}>
</Contact>
))}
</React.Fragment>
);
}}
</Consumer>
);
}
}
export default Contacts;
Contact.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
class Contact extends Component {
state = {
showContactinfo : false
};
onDeleteClick = () => {
this.props.deleteClickHandler();
}
onEditClick() {
}
render() {
const { name, email, phone } =this.props.contact;
const { showContactinfo } = this.state;
return (
<div className="card card-body mb-3">
<h4>{name}
{showContactinfo ? (
<div className="float-right">
<i
onClick= {this.onEditClick}
style={{cursor: 'pointer', fontSize: 'medium'}}
className="fas fa-edit mr-3"></i>
<i
onClick= {this.onDeleteClick}
style={{cursor: 'pointer', fontSize: 'medium'}}
className="fa fa-trash-alt"></i>
</div>):
<i className="fa fa-sort-down float-right"
style={{cursor: 'pointer'}}
onClick={() =>
this.setState({ showContactinfo: !this.state.showContactinfo})}></i>}
</h4>
{showContactinfo ? (
<ul className="list-group">
<li className="list-group-item">Email: {email}</li>
<li className="list-group-item">Phone: {phone}</li>
</ul>) : null}
</div>
)
}
}
Contact.propTypes = {
contact: PropTypes.object.isRequired,
deleteClickHandler: PropTypes.func.isRequired
}
export default Contact;