I'm new to reactjs. I'm trying to implement a toggleTheme action in every page of my project.
So, Instead of declaring it in every component, I thought of creating a toggle theme component itself and import it in all other components.
I don't know why but for some reason it is not working properly.
Here is my code...
toggletheme.js
import Brightness4Icon from '#material-ui/icons/Brightness4';
import Brightness7Icon from '#material-ui/icons/Brightness7';
import React, { useState } from 'react';
import './BasePage.css';
const ToggleTheme = (isLight, setLight) => {
[isLight, setLight] = useState("true");
const toggleTheme = () => {
setLight(!isLight);
}
console.log(isLight)
return (
<div className="themebtn" onClick={toggleTheme}>
{isLight ? <Brightness7Icon /> : <Brightness4Icon />}
</div>
)
}
export default ToggleTheme
Another component in which I want to import toggletheme component basepage.js
import ToggleTheme from './ToggleTheme'
import React, { useState } from 'react';
const Basepage = () => {
return (
<div className={isLight ? "light" : "dark"}>
<div>
<ToggleTheme />
</div>
</div>
)
}
export default Basepage
basepage.css
.light {
--background: #ffffff;
--background-color: #f1f1f1;
--blue: #1b98f5;
--foreground: #323234;
--shadow: 4px 4px 4px #aaa;
}
.dark {
--background: #323234;
--background-color: #202124;
--blue: #1b98f5;
--foreground: #f1f1f1;
--shadow: 4px 4px 4px #222;
}
I'm getting at isLight in my basepage.js
I would appreciate some help in rectifying it.
Thank you.
It is very simple, because you are using a string "true" to set the initial state of the variable in your file toggletheme.js and remember that a string with characters is always true, that is why in the ternary operator you asked if the variable was true or not, and it returns always true.
Just change this useState("true") to useState(true).
import Brightness4Icon from '#material-ui/icons/Brightness4';
import Brightness7Icon from '#material-ui/icons/Brightness7';
import React, { useState } from 'react';
import './BasePage.css';
const ToggleTheme = (isLight, setLight) => {
[isLight, setLight] = useState(true);
const toggleTheme = () => {
setLight(!isLight);
}
console.log(isLight)
return (
<div className="themebtn" onClick={toggleTheme}>
{isLight ? <Brightness7Icon /> : <Brightness4Icon />}
</div>
)
}
export default ToggleTheme
const ToggleTheme = ({isLight, setLight}) => {
const toggleTheme = () => {
setLight(!isLight);
}
console.log(isLight)
return (
<div className="themebtn" onClick={toggleTheme}>
{isLight ? <Brightness7Icon /> : <Brightness4Icon />}
</div>
)
}
const Basepage = () => {
const [isLight, setLight] = React.useState(true);
return (
<div className={isLight ? "light" : "dark"}>
<div>
<ToggleTheme isLight={isLight} setLight={setLight} />
</div>
</div>
)
}
I have majorly used chakra-ui and I have hated the fact that you can use true and false to set the theme, what if you choose to have a different theme down the road ?
import Brightness4Icon from '#material-ui/icons/Brightness4';
import Brightness7Icon from '#material-ui/icons/Brightness7';
import { useEffect, useState } from 'react';
const useTheme = () => {
let themes=['light','dark'];
let icons =[ <Brightness7Icon /> , <Brightness4Icon />]
const [icon,setIcon]=useState(<Brightness7Icon />);
let [theme, setTheme] = useState("light");
let changeTheme=()=>{
let index =themes.indexOf(theme)
if(index==themes.length-1)
{
setTheme(themes[0]);
setIcon(icons[0]);
}
else{
setTheme(themes[index+1]);
setIcon(icons[index+1]);
}
}
useEffect(()=>{
},[theme])
return ([theme,changeTheme,<div onClick={changeTheme}>{icon}</div>])
}
export default useTheme
import useTheme from './toggle'
import React from 'react';
const Basepage = () => {
let [theme,changeTheme,icon] = useTheme();
return (
<div className={theme}>
<div>
{icon}
</div>
</div>
)
}
export default Basepage
Related
I'm using Context API for a cryptocurrency tracker app. What I want is to create the state currency and setCurrency from the context named CryptoState which I created in CryptoContext.js and import to Header component so I can use them to do the function onChange. When I import state from CryptoState it has the error as in the title and causes the page emptied.
Here's my code:
CryptoContext.js
import { createContext, useContext, useState, useEffect } from 'react'
const Crypto = createContext()
const CryptoContext = ({ children }) => {
const [currency, setCurrency] = useState("VND")
const [symbol, setSymbol] = useState("₫")
useEffect(() => {
if (currency === "VND") setSymbol("₫")
else if (currency === "USD") setSymbol("$")
}, [currency])
return (
<Crypto.Provider value={{currency, symbol, setCurrency}}>{children}</Crypto.Provider>
)
}
export default CryptoContext
export const CryptoState = () => {
return useContext(Crypto)
}
Header.js
import {
AppBar,
Container,
Toolbar,
Typography,
Select,
MenuItem,
} from '#material-ui/core'
import { makeStyles, createTheme, ThemeProvider } from '#material-ui/core/styles'
import { Link } from 'react-router-dom'
import { CryptoState } from '../CryptoContext'
const useStyles = makeStyles(() => ({
title: {
flex: 1,
color: 'gold',
fontFamily: 'Montserrat',
fontWeight: 'bold',
cursor: 'pointer'
}
}))
const Header = () => {
const classes = useStyles()
const { currency, setCurrency } = CryptoState()
const darkTheme = createTheme({
palette: {
primary: {
main: '#fff',
},
type: 'dark'
}
})
return (
<ThemeProvider theme={darkTheme}>
<AppBar color="transparent" position="static">
<Container>
<Toolbar>
<Typography
className={classes.title}
variant='h6'
>
<Link to="/">
Crypto Hunter
</Link>
</Typography>
<Select
variant="outlined"
style={{
width: 100,
height: 40,
marginRight: 15,
}}
defaultValue={'VND'}
value={currency}
onChange={(e) => setCurrency(e.target.value)}
>
<MenuItem value={"USD"}>USD</MenuItem>
<MenuItem value={"VND"}>VND</MenuItem>
</Select>
</Toolbar>
</Container>
</AppBar>
</ThemeProvider>
)
}
export default Header
As soon as I import the state by this line: const { currency, setCurrency } = CryptoState() in Header.js, it causes error TypeError: _CryptoContext__WEBPACK_IMPORTED_MODULE_0__.CryptoState() is undefined and cause the page emptied.
Here's the captured image of the error: error's image
All answers are highly appreciated. Thank you 🙏
You are wrong now.
Try to rename CryptoContext to CryptoProvider.
Where does Header is render? in App.js?
Please find out where is contain and wrap the Provider of CryptoProvider:
<CryptoProvider>
<Header />
</CryptoProvider>
In Header.js component. You have to use the variable you set for createContext function. then, please use useContext instead of calling it directly
// CryptoContext.js
const cryptoContext = createContext(); // remember to export this var.
Then, in Header.js
import { useContext } from 'react';
import { cryptoContext } from './CryptoContext';
const { currency, setCurrency } = useContext(cryptoContext);
Hey Guys so i was having this error below
The main issue is just to make sure your naming convention for exporting your useContext must be in camelCase Naming convention and make sure your index.js is wrapped around with your Context provider.
so instead of this
export const UseStateContext = () => {
return useContext(StateContext);
};
DO THIS!!!
export const useStateContext = () => {
return useContext(StateContext);
};
import { useNavigate} from 'react-router-dom';
import React from 'react'
import CustomButton from '../custom-button/custom-button.component';
import './cart-dropdown.styles.scss';
import CartItem from '../cart-item/cart-item.component';
import {connect} from 'react-redux';
import { selectCartItems } from '../../redux/cart/cart.selectors';
import {createStructuredSelector} from 'reselect';
const CartDropDown = ({cartItems }) => {
return (
<div className="cart-dropdown">
<div className="cart-items">
{cartItems.length
? cartItems.map(cartItem =><CartItem key={cartItem.id} item={cartItem} />)
: <span className="empty-message">Your cart is empty</span>
}
<CustomButton onClick{() => window.history.push('/checkout')}>
GO TO CHECKOUT
</CustomButton>
</div>
</div>
)
}
const mapStateToProps = createStructuredSelector({
cartItems: selectCartItems
})
export default useNavigate(connect(mapStateToProps) (CartDropDown));
useNavigate isn't a Higher Order Component, it's a React hook, it must be used within a function component or custom React hook.
Move it into the function body and fix the navigation.
const CartDropDown = ({ cartItems }) => {
const navigate = useNavigate(); // <-- use hook in component
return (
<div className="cart-dropdown">
<div className="cart-items">
{cartItems.length
? cartItems.map(cartItem => (
<CartItem key={cartItem.id} item={cartItem} />
))
: <span className="empty-message">Your cart is empty</span>
}
<CustomButton
onClick{() => navigate('/checkout')} // <-- call navigate in handler
>
GO TO CHECKOUT
</CustomButton>
</div>
</div>
)
}
const mapStateToProps = createStructuredSelector({
cartItems: selectCartItems
})
export default connect(mapStateToProps)(CartDropDown);
A react hook must be inside the component. You get a navigate function from useNavigate(), and then you call that to navigate.
import { useNavigate } from 'react-router-dom'
import React from 'react'
import CustomButton from '../custom-button/custom-button.component'
import './cart-dropdown.styles.scss'
import CartItem from '../cart-item/cart-item.component'
import { connect } from 'react-redux'
import { selectCartItems } from '../../redux/cart/cart.selectors'
import { createStructuredSelector } from 'reselect'
const CartDropDown = ({ cartItems }) => {
const navigate = useNavigate() // <-- hooks must be INSIDE the component
const onCustomButtonClick = (event) => {
//window.history.push('/checkout') // <-- no
navigate('/checkout')
}
return (
<div className="cart-dropdown">
<div className="cart-items">
{
cartItems.length
? cartItems.map(cartItem => <CartItem key={cartItem.id} item={cartItem} />)
: <span className="empty-message" > Your cart is empty</span>
}
<CustomButton onClick={onCustomButtonClick}> GO TO CHECKOUT </CustomButton>
</div>
</div>)
}
const mapStateToProps = createStructuredSelector({
cartItems: selectCartItems
})
//export default useNavigate(connect(mapStateToProps)(CartDropDown))
export default CartDropDown
I can't render props from useContext because it returns undefined before desired object. I can't seem to find any solution to my problem.
This is the child element I'm trying to render:
const Reviews = ({reviews}) => {
return (
<div className={styles['review__card']}>
{reviews.map((review) => {
return(
<div className={styles['review__card__item']}>
<div className={styles['card__item__meta']}>
<span>{review.name}</span>
<span><StarRating rating={review.rating}/></span>
</div>
<div className={styles['card__item__p']}>
<p>{review.revew}</p>
</div>
</div>
)})}
</div>
)
}
export default Reviews
This is Parent Element:
import React, { useContext, useEffect } from 'react'
import { useParams } from 'react-router-dom'
import { RestaurantsContext } from '../context/RestaurantsContext';
import Wrapper from '../components/Wrapper'
import Header from '../components/Header'
import Reviews from '../components/Reviews'
import AddReview from '../components/AddReview'
import RestaurantFinder from '../apis/RestaurantFinder';
const RestaurantDetailPage = () => {
const {id} = useParams()
const {selectedRestaurant, setSelectedRestaurant} = useContext(RestaurantsContext)
useEffect(()=> {
const fetchDate = async () => {
try {
const response = await RestaurantFinder.get(`/${id}`)
setSelectedRestaurant(response.data.data)
}
catch (err) {console.log(err)
}
}
fetchDate()
}, [])
console.log(selectedRestaurant.reviews)
return (
<Wrapper>
<Header title={ 'RestaurantDetailPage' }/>
<div>{selectedRestaurant && (
<>
<Reviews reviews={selectedRestaurant.reviews}/>
<AddReview/>
</>
)}</div>
</Wrapper>
)
}
export default RestaurantDetailPage
Whenever I console.log(selectedRestaurant.reviews) it gives me undefined and then it gives me object query. I assume that .map() is getting an error because it is trying to render that first argument which is undefined
Here is Context js
import React, {useState, createContext} from 'react';
export const RestaurantsContext = createContext();
export const RestaurantsContextProvider = props => {
const [restaurants, setRestaurants] = useState([])
const [selectedRestaurant, setSelectedRestaurant] = useState([])
const addRestaurants = (restaurant) => {
setRestaurants([...restaurants, restaurant]);
}
return (
<RestaurantsContext.Provider value={{restaurants, setRestaurants, addRestaurants, selectedRestaurant, setSelectedRestaurant }}>
{props.children}
</RestaurantsContext.Provider>
)
}
I have found the issue and solved it though I'm not entirely sure how it works.
const [selectedRestaurant, setSelectedRestaurant] = useState([])
default useState value should be null using useState(null)
so i'm trying to implement a simple theme switch via react context, and i need to change the value of context(in ThemeProvider.jsx) provider according to a onChange event in another component(ThemeSwitcher.jsx).
ThemeProvider.jsx :
import React, {createContext} from "react";
import {THEME_TYPE} from "../constants";
export const ThemeContext = createContext(THEME_TYPE.LIGHT);
const ThemeProvider = ({ children }) => {
return <>
<ThemeContext.Provider value={//THEME_TYPE.LIGHT or THEME_TYPE.DARK)}>
{children}
</ThemeContext.Provider>
</>
};
export default ThemeProvider;
ThemeSwitcher.jsx :
import React, {useContext} from "react";
import { THEME_TYPE } from "../constants";
import {ThemeContext} from "../providers/ThemeProvider";
const ThemeSwitcher = () => {
const themeMode = useContext(ThemeContext);
const handleThemeChange = (e) => {
//value of context should change according to argument 'e'
};
return (
<div className="switch-container">
<label className="switch">
<input
data-testid="theme-changer"
type="checkbox"
checked={themeMode === THEME_TYPE.DARK}
onChange={handleThemeChange}
/>
<span className="slider round"></span>
</label>
<span className="text-color bold">Dark mode</span>
</div>
);
};
export default ThemeSwitcher;
App.jsx:
import React, {useContext} from "react";
import { Helmet } from "react-helmet";
import NameBox from "./components/NameBox";
import ThemeSwitcher from "./components/ThemeSwitcher";
import { THEME_TYPE } from "./constants";
import Styles from "./data/Styles";
import ThemeProvider from "./providers/ThemeProvider";
import {ThemeContext} from "./providers/ThemeProvider";
const StyleTag = () => {
const themeMode = useContext(ThemeContext);
return (
<Helmet>
<style>{Styles(themeMode)}</style>
</Helmet>
);
};
function App() {
return (
<ThemeProvider>
<StyleTag />
<NameBox />
<ThemeSwitcher />
</ThemeProvider>
);
}
export default App;
and styles.js if necessary:
import { THEME_TYPE } from "../constants";
const Theme = {
[THEME_TYPE.LIGHT]: {
background: "#fafafa",
text: "#rgba(0, 0, 0, 0.87)",
divider: "rgba(0, 0, 0, 0.12)",
},
[THEME_TYPE.DARK]: {
background: "#303030",
text: "#fff",
divider: "rgba(255, 255, 255, 0.12)",
},
};
const Styles = (theme) => `
body {background-color: ${Theme[theme].background};}
.text-color {color: ${Theme[theme].text};}
.box {border: 1px solid ${Theme[theme].divider}}
`;
export default Styles;
as you see the value of context should be changed according to input onChange event. i couldn't come up with proper solution for relating these two, so your help is appreciated.
I suggest adding useState() hook inside ThemeProvider component.
Here is the codesandbox: https://codesandbox.io/s/magical-franklin-cril0?file=/src/ThemeProvider.jsx
That is how the code looks like:
import React, { createContext, useState } from "react";
import { THEME_TYPE } from "./constants";
export const ThemeContext = createContext(THEME_TYPE.LIGHT);
const ThemeProvider = ({ children }) => {
const [themeType, setThemeType] = useState(THEME_TYPE.LIGHT);
const changeTheme = (value) => {
if (value) {
setThemeType(THEME_TYPE.DARK);
} else {
setThemeType(THEME_TYPE.LIGHT);
}
};
return (
<>
<ThemeContext.Provider value={{ themeType, changeTheme }}>
{children}
</ThemeContext.Provider>
</>
);
};
export default ThemeProvider;
And then you would use the context where needed like this:
const themeMode = useContext(ThemeContext)
themeMode.themeType // THEME_TYPE.LIGHT or THEME_TYPE.DARK
themeMode.changeTheme(value) // if value is true, it would change to dark mode, if false to light mode
I'm trying to build a section of my site that will dynamically pull in contact information from a REST API. At this point I'm using the sample API from https://jsonplaceholder.typicode.com.
I'm trying to use axios, specifically the useAxios and makeUseAxios hooks, to fetch the API, and store it in an app context that I can then use throughout the site, preferably with the useContext hook. Additionally, I'm needing to be able to update the API call based on user interactions, allowing them to select a location, have the API update, and store that in the app's context so that it all updates dynamically.
Essentially, I've gotten a basic useContext scenario working based on this tutorial, but I am struggling with how to store the JSON response in such a way that I can reference it in useContext. Here's the closest thing I've had going so far:
AppContext.tsx
import React, { createContext, ReactNode } from 'react';
import axios from 'axios';
import { makeUseAxios } from 'axios-hooks';
import { useCookie } from 'hooks/use-cookie';
export const AppContext = createContext();
export const DealerContextProvider = ({children}: any) => {
const useAxios = makeUseAxios({
axios: axios.create({ baseURL: 'https://jsonplaceholder.typicode.com/users/' }),
});
const LOCAL_STORAGE_KEY_DEALER = '_selectedDealerInformation';
const [cookie] = useCookie('one-day-location', '1');
const [dealerInfo] = useAxios(`${cookie}`);
return (
<AppContext.Provider value={[dealerInfo]}>
{children}
</AppContext.Provider>
);
};
And my header component, where I'm trying to access it:
import React, { ReactNode, useEffect, useState, useContext } from 'react';
import { AppContext } from 'components/app-context/AppContext';
import Logo from 'assets/svg/logo.svg';
import css from 'classnames';
import { Button } from 'components/button/Button';
import { Link } from 'components/link/Link';
import { NotificationBar } from '../notification-bar/NotificationBar';
import s from './Header.scss';
import { MenuIcon } from 'components/menu-icon/MenuIcon';
import { MainNav } from './navigation/MainNav';
interface HeaderProps {
navigationContent: ReactNode;
}
export const Header = ({ navigationContent }: HeaderProps) => {
const [scrolled, setScrolled] = useState(false);
const [open, setOpen] = useState(false);
const blogInfo = useContext(AppContext);
const buttonLabel = blogInfo ? `${blogInfo.name}` : 'Find a Dealer';
const buttonLink = blogInfo ? `tel:${blogInfo.name}` : '/find-a-dealer';
useEffect(() => {
const handleScroll = () => {
const isScrolled = window.scrollY > 10;
if (isScrolled !== scrolled) {
setScrolled(!scrolled);
}
};
document.addEventListener('scroll', handleScroll, { passive: true });
return () => {
document.removeEventListener('scroll', handleScroll);
};
}, [scrolled]);
return (
<>
<NotificationBar notificationContent={navigationContent} />
<header
className={scrolled ? css(s.header, s.header__scrolled) : s.header}
data-open={open ? 'true' : ''}
>
<nav className={s.header__navigation}>
<ul className={s.header__container}>
<li className={s.header__logo}>
<Link to="/" className={s.header__link}>
<Logo />
</Link>
</li>
<li className={s.header__primary}>
<MainNav navigationItems={navigationContent} />
</li>
<li className={s.header__utility}>
<Button href={buttonLink}>{buttonLabel}</Button>
</li>
<li className={s.header__burger}>
<MenuIcon onClick={() => setOpen(!open)} />
</li>
</ul>
</nav>
</header>
</>
);
};
What I'm needing is for the button in header__utility to dynamically display the name and phone number of the chosen dealer. I can clarify anything as needed, I'm newish to React and am still learning how to express all that I'm needing.
Thanks!
Alright, I did a ton of digging over the last 24 hours, and I was able to find a solution.
I've got my context, now named ApiContext for clarity.
ApiContext.tsx
import React, { createContext } from 'react';
import axios from 'axios';
import { makeUseAxios } from 'axios-hooks';
import { useCookie } from 'hooks/use-cookie';
const contextObject = {} as any;
export const context = createContext(contextObject);
const useAxios = makeUseAxios({
axios: axios.create({ baseURL: process.env.GATSBY_API_ENDPOINT }),
});
export const ApiContext = ({ children }: any) => {
const [cookie] = useCookie('one-day-location', '1');
const [{ data }] = useAxios(`${cookie}`);
const { Provider } = context;
return <Provider value={data}>{children}</Provider>;
};
Then, to use it across components, I wrap my AppLayout in <ApiContext>:
AppLayout.tsx
import React, { ReactNode } from 'react';
import { ApiContext } from 'contexts/ApiContext';
import { graphql, StaticQuery } from 'gatsby';
import { Devtools } from '../devtools/Devtools';
import { Footer } from '../footer/Footer';
import { Header } from '../header/Header';
import s from './AppLayout.scss';
interface AppLayoutProps {
children: ReactNode;
location: string;
}
const isDev = process.env.NODE_ENV === 'development';
// tslint:disable no-default-export
export default ({ children }: AppLayoutProps) => {
return (
<StaticQuery
query={`${NavQuery}`}
render={(data) => (
<>
<ApiContext>
<Header navigationContent={data.prismic.allNavigations.edges[0].node} />
<div className={s.layout}>
{children}
<Footer navigationItems={data.prismic.allNavigations.edges[0].node} />
{isDev && <Devtools />}
</div>
</ApiContext>
</>
)}
/>
);
};
const NavQuery = graphql`
query NavQuery {
prismic {
allNavigations {
edges {
node {
...NotificationBar
...NavigationItems
...FooterNavigationItems
}
}
}
}
}
`;
And in my Header component I can access the data with the useContext hook:
Header.tsx
import React, { ReactNode, useContext, useEffect, useState } from 'react';
import Logo from 'assets/svg/logo.svg';
import css from 'classnames';
import { Button } from 'components/button/Button';
import { Link } from 'components/link/Link';
import { MenuIcon } from 'components/menu-icon/MenuIcon';
import { context } from 'contexts/ApiContext';
import { NotificationBar } from '../notification-bar/NotificationBar';
import s from './Header.scss';
import { MainNav } from './navigation/MainNav';
interface HeaderProps {
navigationContent: ReactNode;
}
export const Header = ({ navigationContent }: HeaderProps) => {
const [scrolled, setScrolled] = useState(false);
const [open, setOpen] = useState(false);
const data = useContext(context);
console.log(data);
const buttonLabel = data ? data.name : 'Find a Dealer';
const buttonLink = data ? `tel:${data.phone}` : '/find-a-dealer';
useEffect(() => {
const handleScroll = () => {
const isScrolled = window.scrollY > 10;
if (isScrolled !== scrolled) {
setScrolled(!scrolled);
}
};
document.addEventListener('scroll', handleScroll, { passive: true });
return () => {
document.removeEventListener('scroll', handleScroll);
};
}, [scrolled]);
return (
<>
<NotificationBar notificationContent={navigationContent} />
<header
className={scrolled ? css(s.header, s.header__scrolled) : s.header}
data-open={open ? 'true' : ''}
>
<nav className={s.header__navigation}>
<ul className={s.header__container}>
<li className={s.header__logo}>
<Link to="/" className={s.header__link}>
<Logo />
</Link>
</li>
<li className={s.header__primary}>
<MainNav navigationItems={navigationContent} />
</li>
<li className={s.header__utility}>
<Button href={buttonLink}>{buttonLabel}</Button>
</li>
<li className={s.header__burger}>
<MenuIcon onClick={() => setOpen(!open)} />
</li>
</ul>
</nav>
</header>
</>
);
};
I'm still working on additional optimizations, specifically eliminating a flicker when loading in a page, and also updating the API on a button click, utilizing useCookie, which is a custom hook I've built out. Hopefully this gives some clarity to anyone else searching for this info, it took me hours and hours to determine a solution. Cheers.