how to change value of react Context from another component React - javascript

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

Related

How to add Dynamic RTL support for NEXT.js Material UI Emotion

I am using nextjs v12.2, #mui/material v5.9, next-i18next v11.3
I am trying to implement dynamic Rtl/Ltr support based on changing language
I used this example for integration of Next.JS with Material UI v5 and this document to change the direction of MUI components
The problem is "each time when I switch between language generated class for mui component Like below (screenshot) for all mui component, in this example created a repeated class for mui input component"
My current implementation is as follows:
_document
import * as React from "react";
import Document, { Html, Head, Main, NextScript } from "next/document";
import createEmotionServer from "#emotion/server/create-instance";
import theme from "theme";
import createEmotionCache from "theme/createEmotionCache";
import i18nextConfig from "next-i18next.config";
export default class MyDocument extends Document {
render() {
const { locale } = this.props.__NEXT_DATA__;
return (
<Html lang={locale}>
<Head>
{/* PWA primary color */}
<meta name="theme-color" content={theme.palette.primary.main} />
<meta name="emotion-insertion-point" content="" />
{this.props.emotionStyleTags}
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
MyDocument.getInitialProps = async ctx => {
const originalRenderPage = ctx.renderPage;
const cache = createEmotionCache();
const { extractCriticalToChunks } = createEmotionServer(cache);
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: App =>
function EnhanceApp(props) {
return <App emotionCache={cache} {...props} />;
},
});
const initialProps = await Document.getInitialProps(ctx);
const emotionStyles = extractCriticalToChunks(initialProps.html);
const emotionStyleTags = emotionStyles.styles.map(style => (
<style
data-emotion={`${style.key} ${style.ids.join(" ")}`}
key={style.key}
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: style.css }}
/>
));
return {
...initialProps,
emotionStyleTags,
};
};
_app
import Head from "next/head";
import { appWithTranslation, useTranslation } from "next-i18next";
import { ThemeProvider } from "#mui/material/styles";
import { CacheProvider } from "#emotion/react";
import CssBaseline from "#mui/material/CssBaseline";
import createEmotionCache from "theme/createEmotionCache";
import theme from "theme";
import "styles/globals.scss";
import { useEffect } from "react";
// Client-side cache, shared for the whole session of the user in the browser.
const clientSideEmotionCache = dir => createEmotionCache(dir);
function MyApp(props) {
const { i18n } = useTranslation();
const {
Component,
emotionCache = clientSideEmotionCache(i18n.dir()),
pageProps,
} = props;
useEffect(() => {
document.body.dir = i18n.dir();
}, [i18n]);
return (
<CacheProvider value={emotionCache}>
<ThemeProvider theme={{ ...theme, direction: i18n.dir() }}>
<Head>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
</Head>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</CacheProvider>
);
}
export default appWithTranslation(MyApp);
createEmotionCache.js
import createCache from "#emotion/cache";
import { prefixer } from "stylis";
import rtlPlugin from "stylis-plugin-rtl";
const isBrowser = typeof document !== "undefined";
// On the client side, Create a meta tag at the top of the <head> and set it as insertionPoint.
// This assures that MUI styles are loaded first.
// It allows developers to easily override MUI styles with other styling solutions, like CSS modules.
export default function createEmotionCache(direction) {
let insertionPoint;
if (isBrowser) {
const emotionInsertionPoint = document.querySelector(
'meta[name="emotion-insertion-point"]',
);
insertionPoint = emotionInsertionPoint ?? undefined;
}
return createCache({
key: direction === "rtl" ? "mui-style-rtl" : "mui-style-ltr",
stylisPlugins: direction === "rtl" ? [prefixer, rtlPlugin] : [],
insertionPoint,
});
// return createCache({ key: "mui-style-ltr", insertionPoint });
}
theme.js
import { createTheme } from '#mui/material/styles';
// Create a theme instance.
const theme = createTheme({
palette: {
primary: {
main: '#556cd6',
},
secondary: {
main: '#19857b',
},
},
typography: {
fontFamily: "'Open Sans', Roboto, sans-serif",
},
});
export default theme;
index.js
import Head from "next/head";
import { useRouter } from "next/router";
import LocaleSwitcher from "../components/LocaleSwitcher";
import { useTranslation, Trans } from "next-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import Box from "#mui/material/Box";
import { TextField } from "#mui/material";
export default function Home(props) {
const router = useRouter();
const { locale, locales, defaultLocale } = router;
const { t, i18n } = useTranslation("home");
return (
<div>
<h1>{t("title")}</h1>
<p>
{t("Current")}: {locale}
</p>
<p>
{t("Default")}: {defaultLocale}
</p>
<p>
{t("Configured")}: {JSON.stringify(locales)}
</p>
<LocaleSwitcher />
<Box m={2}>
<TextField label={t("Email")} />
</Box>
</div>
);
}
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, ["home"])),
},
};
}
LocaleSwitcher.js
import Link from "next/link";
import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
export default function LocaleSwitcher({ title }) {
const router = useRouter();
const { locales, locale: activeLocale } = router;
const otherLocales = locales.filter(locale => locale !== activeLocale);
const { t } = useTranslation("home");
return (
<div>
<p>{t("Locale")}:</p>
<ul>
{otherLocales.map(locale => {
const { pathname, query, asPath } = router;
return (
<li key={locale}>
<Link href={{ pathname, query }} as={asPath} locale={locale}>
<a>
{locale === "en"
? "English"
: locale === "fa"
? "Farsi"
: null}
</a>
</Link>
</li>
);
})}
</ul>
</div>
);
}
_app.js
import Head from "next/head";
import { appWithTranslation, useTranslation } from "next-i18next";
import { ThemeProvider } from "#mui/material/styles";
import { CacheProvider } from "#emotion/react";
import CssBaseline from "#mui/material/CssBaseline";
import theme from "theme";
import "styles/globals.scss";
import { useEffect } from "react";
import createCache from "#emotion/cache";
import { prefixer } from "stylis";
import rtlPlugin from "stylis-plugin-rtl";
const isBrowser = typeof document !== "undefined";
let insertionPoint;
if (isBrowser) {
const emotionInsertionPoint = document.querySelector(
'meta[name="emotion-insertion-point"]',
);
insertionPoint = emotionInsertionPoint ?? undefined;
}
const cacheRtl = createCache({
key: "mui-style-rtl",
stylisPlugins: [prefixer, rtlPlugin],
insertionPoint,
});
const cacheLtr = createCache({
key: "mui-style-ltr",
insertionPoint,
});
function MyApp(props) {
const { i18n } = useTranslation();
const { Component, emotionCache, pageProps } = props;
useEffect(() => {
document.body.dir = i18n.dir();
}, [i18n]);
return (
<>
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</Head>
<CacheProvider value={i18n.dir() === "rtl" ? cacheRtl : cacheLtr}>
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</CacheProvider>
</>
);
}
export default appWithTranslation(MyApp);
#MarijaNajdova explained the problem at https://github.com/mui/material-ui/issues/33892

ReactJS giving error Uncaught TypeError: __WEBPACK_IMPORTED_MODULE_0__... is undefined when implementing Context API

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);
};

How to use const of one component in another component

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

I'm new in React.js and have this error: "Warning: React.createElement: type is invalid"

I'm doing a course of React Js, in the terminal not have errors but i have a error in the console of the navigator.
This is the error:
Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of ToggleLikeMutation.
at ToggleLikeMutation (webpack://curso-platzi-react-avanzado/./src/container/ToggleLikeMutation.js?:16:23)
at article
at styled.article (webpack://curso-platzi-react-avanzado/./node_modules/styled-components/dist/styled-components.browser.esm.js?:29:19307)
at PhotoCard (webpack://curso-platzi-react-avanzado/./src/components/PhotoCard/index.js?:31:17)
at ul
at ListOfPhotoCards (webpack://curso-platzi-react-avanzado/./src/components/ListOfPhotoCards/index.js?:22:25)
at div
at App
at ApolloProvider (webpack://curso-platzi-react-avanzado/./node_modules/#apollo/client/react/context/ApolloProvider.js?:12:21)
Here's my documents:
FavButton.js
import React from 'react'
import { MdFavoriteBorder, MdFavorite } from 'react-icons/md'
import { Button } from './styles'
export const FavButton = ({ liked, likes, onClick }) => {
const Icon = liked ? MdFavorite : MdFavoriteBorder
return (
<Button onClick={onClick}>
<Icon size='32px' /> {likes} likes!
</Button>
)
}
ToggleLikeMutation.js
import React from 'react'
import { Mutation, gql } from '#apollo/client'
const LIKE_PHOTO = gql`
mutation likeAnonymusPhoto($input: LikePhoto!) {
likeAnonymousPhoto(input: $input) {
id,
liked,
likes
}
}
`
export const ToggleLikeMutation = ({ children }) => {
return (
<Mutation mutation={LIKE_PHOTO}>
{children}
</Mutation>
)
}
PhotoCard.js
import React, { Fragment } from 'react'
import { Article, ImgWrapper, Img } from './styles'
import { useLocalStorage } from '../../hooks/useLocalStorage'
import { useNearScreen } from '../../hooks/useNearScreen'
import { FavButton } from '../FavButton/index.js'
import { ToggleLikeMutation } from '../../container/ToggleLikeMutation'
const DEFAULT_IMAGE = 'https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60'
export const PhotoCard = ({
id, likes = 0,
src = DEFAULT_IMAGE
}) => {
const [show, element] = useNearScreen()
const key = `like-${id}`
const [liked, setLiked] = useLocalStorage(key, false)
return (
<Article ref={element}>
{
show &&
<>
<a href={`/?detail=${id}`}>
<ImgWrapper>
<Img src={src} />
</ImgWrapper>
</a>
<ToggleLikeMutation>
{
(toggleLike) => {
const handleFavClick = () => {
!liked && toggleLike({
variables: {
input: { id }
}
})
setLiked(!liked)
}
return (
<FavButton
liked={liked} likes={likes} onClick={handleFavClick}
/>
)
}
}
</ToggleLikeMutation>
</>
}
</Article>
)
}
App.js
import React, { Fragment } from 'react'
import { ListOfCategories } from './components/ListOfCategories'
import { GlobalStyle } from './styles/GlobalStyles'
import { ListOfPhotoCards } from './components/ListOfPhotoCards'
import { Logo } from './components/Logo'
import { PhotoCardWithQuery } from './container/PhotoCardWithQuery'
export const App = () => {
const urlParams = new window.URLSearchParams(window.location.search)
const detailId = urlParams.get('detail')
return (
<div>
<GlobalStyle />
<Logo />
{
detailId
? <PhotoCardWithQuery id={detailId} />
: <>
<ListOfCategories />
<ListOfPhotoCards categoryId={1} />
</>
}
</div>
)
}
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { ApolloClient, HttpLink, InMemoryCache } from 'apollo-boost'
import { ApolloProvider } from '#apollo/client'
import { App } from './App'
const cache = new InMemoryCache()
const link = new HttpLink({
uri: 'https://petgram-jv-2011.vercel.app/graphql'
})
const client = new ApolloClient({
cache,
link
})
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('app'))

Storing Axios response in AppContext

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.

Categories

Resources