So I currently have a state to toggle dark / light mode on a website with lots of nested components. I have a root App.js:
function App() {
return (
<DarkModeProvider>
<div className="App">
<HomePage />
</div>
</DarkModeProvider>
);
}
The DarkModeProvider is my react context, and in the next component I have a layout where I have my navigation and routing, that is wrapped in the ThemeProvider:
const HomePage = () => {
const { isDarkTheme } = useContext(DarkModeContext);
return (
<ThemeProvider theme={isDarkTheme ? createTheme(darkTheme) :
createTheme(lightTheme)}>
<DrawerProvider>
<Router>
<Box sx={{ display: "flex" }}>
<Box>
<HomePageInner />
</Box>
<Routes>
<Route path="/inventory" element={<Inventory />} />
<Route path="/orders" element={<Orders />} />
<Route path="/vendors" element={<Vendors />} />
</Routes>
</Box>
</Router>
</DrawerProvider>
</ThemeProvider>
);
};
It works fine, however, I'd like to access the theme context in my "app" class that's in the root App component. If I wrap the DarkModeProvider with the ThemeProvider, I don't have access to the state of the dark / light mode, if I wrap the ThemeProvider with the DarkModeProvider, I lose access to the isDarkTheme state from my context.
Is there a better practice to format this? What I really want is to have a css / style sheet in the source folder as the same level as the app component. I'm unsure how to access my theme provider when it's not in my app component. OR how to have my dark mode state accessible while wrapped inside of the theme provider (or vice versa).
For example my App.CSS:
body {
background-color: theme.primary.palette.main;
/* I would like the body to follow my MUI theme. */
}
a {
color: inherit;
text-decoration: none;
}
Dark Mode Provider:
import { createContext, useState } from "react";
const DarkModeContext = createContext();
export const DarkModeProvider = ({ children }) => {
const [isDarkTheme, setIsDarkTheme] = useState(false);
const changeTheme = () => {
setIsDarkTheme(!isDarkTheme);
};
return (
<DarkModeContext.Provider
value={{
isDarkTheme,
changeTheme,
}}
>
{children}
</DarkModeContext.Provider>
);
};
export default DarkModeContext;
You can move the ThemeProvider component inside App.js file and have a state there for isDarkTheme which you can then use both for DarkModeProvider and ThemeProvider
function App() {
const [isDarkTheme, setIsDarkTheme] = useState(false);
const changeTheme = () => {
setIsDarkTheme(!isDarkTheme);
};
return (
<DarkModeProvider value={{ isDarkTheme, changeTheme }}>
<ThemeProvider theme={isDarkTheme ? createTheme(darkTheme) : createTheme(lightTheme)}>
<div className="App">
<HomePage />
</div>
</ThemeProvider>
</DarkModeProvider>
);
}
Dark Mode Provider:
import { createContext } from "react";
const DarkModeContext = createContext();
export const DarkModeProvider = ({ children, value }) => {
return (
<DarkModeContext.Provider value={value}>
{children}
</DarkModeContext.Provider>
);
};
export default DarkModeContext;
Related
I'm working on overhauling the state manage of an application I'm working on, and attempting to set it up with a context provider. I only have limited number of states to track, and it's a relatively small application so context seemed appropriate, and was a recommended solution. I've read the docs on react, but some of it isn't super clear since it's written using class components. I did some reading on stackoverflow as well for similiar implementations, but the code was not consitent between answers so I've just hacked together my best version of making sense of the tool.
I think I've maybe goofed something relatively obvious but I'm not sure what yet. Any insight appreciated. My intuition is that I've just architected something incorrectly, but I'm not sure what.
The current application currently consoles:
Cannot access 'cards' before initialization
at App (App.jsx:30:1)
Here is App.jsx:
import { useState, useEffect, useContext } from "react";
import "./styles/App.css";
import FileHandler from "./Components/fileHandler";
import ImagePreviewer from "./Components/ImagePreviewer";
import { Header } from "./Components/Header";
import { Comparison } from "./Components/Comparison";
import { Table } from "./Components/Table";
import { backgroundQuery } from "./Components/backgroundQuery"
import { Footer } from "./Components/Footer";
import { DBContainer } from "./Components/DatabaseFunctions/DBContainer";
import { CardsContext } from "./Components/AppContext"
function App({user}) {
const context = useContext(CardsContext);
const [cards, setCards] = context[cards]
const [previewCard, setPreviewCard] = context[previewCard]
const [comparisonCards, setComparisonCards] = context[comparisonCards]
const [userDBCards, setUserDBCards] = context[userDBCards];
const [cardInput, setCardInput] = context[cardInput]
const [background, setBackground] = useState([]);
useEffect(() => {
backgroundQuery()
.then(data => {
setBackground(data.image_uris.art_crop);
})
.catch(error =>
console.log(error.message));
}, [setBackground]);
//insert user token into database
return (
<div id="master-div" className=".container" style={{
backgroundImage: `url(${background})`,
}}>
<Header
background={background}
setBackground={setBackground}
cards={cards}
user={user}
/>
<FileHandler
cardInput={cardInput}
setCardInput={setCardInput}
previewCard={previewCard}
setPreviewCard={setPreviewCard}
cards={cards}
setCards={setCards}
setComparisonCards={setComparisonCards}
user={user}
/>
<div className="main-container">
<Comparison
user={user}
userDBCards={userDBCards}
cards={cards}
setCards={setCards}
comparisonCards={comparisonCards}
setComparisonCards={setComparisonCards}
/>
</div>
<div className="row .container">
<div className="col-3 .container" id="preview-container">
<ImagePreviewer previewCard={previewCard} cards={cards} />
</div>
<div className="col-6 .container" id="tableContainer">
<DBContainer
user={user}
cards={cards}
setCards={setCards}
userDBCards={userDBCards}
setUserDBCards={setUserDBCards}
setComparisonCards={setComparisonCards}
/>
<Table
setComparisonCards={setComparisonCards}
setPreviewCard={setPreviewCard}
cards={cards}
setCards={setCards} />
</div>
</div>
<Footer />
</div>
);
}
export default App;
Below I have the code for my AppContext component which houses all of my context.
import { createContext, useState } from "react";
export const CardsContext = createContext();
export const BackgroundContext = createContext();
const AppContextProvider = ({children}) => {
const [cardInput, setCardInput] = useState([]);
const [cards, setCards] = useState([]);
const [previewCard, setPreviewCard] = useState([]);
const [comparisonCards, setComparisonCards] = useState([]);
const [userDBCards, setUserDBCards] = useState([]);
const [background, setBackground] = useState([]);
return(
<CardsContext.Provider value={{
cards: [cards, setCards],
cardInput: [cardInput, setCardInput],
previewCard: [previewCard, setPreviewCard],
comparisonCards: [comparisonCards, setComparisonCards],
userDBCards: [userDBCards, setUserDBCards]
}}>
<BackgroundContext.Provider value={[background, setBackground]}>
{children}
</BackgroundContext.Provider>
</CardsContext.Provider>
)
}
export default AppContextProvider
This is wrapped around all of my routes in my index here:
ReactDOM.render(
<BrowserRouter>
<Auth0ProviderWithRedirectCallback
domain={process.env.REACT_APP_DOMAIN}
clientId={process.env.REACT_APP_CLIENT_ID}
redirectUri={window.location.origin}
>
<AppContextProvider>
<Routes>
<Route path="/" element={<ProtectedRoute component={App}/>} />
<Route path="/profile/:id" element={<ProtectedRoute component={ProfilePage}/>} />
<Route path='/auth' element={<Auth />} />
{/* The below route should fire for upload list, rendering a page of thier cards. */}
<Route path='/profile/:id/:list' element={<ListPage />} />
</Routes>
</AppContextProvider>
</Auth0ProviderWithRedirectCallback>
</BrowserRouter>,
document.getElementById('root')
);
You should wrap your context[cards] in quotes like so context["cards"]. Now the app tries to get the context with the value of cards which is not defined yet.
Same for the other context[...]
I'm trying to get my Navbar from antd to re-render using react useContext, where by default 'isAuth' is 'false', and upon user log in, it will be set to 'true'. From my understanding, through useContext, when 'isAuth' is updated, my Navbar should update as well, but it doesn't. Is my understanding in useContext flawed?
Here are the sample codes:
App.js
function App() {
return (
<BrowserRouter>
<UserContextProvider>
<Navbar />
<UserContextProvider>
</BrowserRouter>
);
}
Navbar.js
export default function Navbar(props) {
const userContext = useUserContext();
return(
<Menu theme="dark" mode="horizontal">
<Menu.Item key="1" style={{ float: "right" }}>
{userContext ? (
<Menu.Item
key="2"
onClick={() => nav("/account")}
>
Account
</Menu.Item>
) : (
<Menu.Item
key="3"
onClick={() => nav("/signup")}
>
Login/Signup
</Menu.Item>
)}
</Menu.Item>
</Menu>
);
}
UserContext.js
const UserContext = createContext();
const UpdateUserContext = createContext();
export function useUserContext() {
return useContext(UserContext);
}
export default function UserContextProvider({ children }) {
const [isAuth, setIsAuth] = useState(false);
return (
<UserContext.Provider value={isAuth}>
{children}
</UserContext.Provider>
);
}
Try this approach. Set provider value prop as an object:
return (
<UserContext.Provider value={{isAuth}}>
{children}
</UserContext.Provider>
);
And get isAuth state destructuring your context:
const {isAuth} = useUserContext();
I'm struggling to figure out how to pass the search term from ChildOne to ChildTwo (which is nested in a page). I hope all the code I provided down below will make it clear. I tried to lift up the state to the App.js component but it didn't work or maybe I didn't do it correctly. I would appreciate any help. Thanks in advance :)
Child 1:
const ChildOne = () => {
const [searhTerm, setSearchTerm] = useState("");
return(
<InputContainer>
<input
type="text"
placeholder="Find a recipe"
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value);
}}
/>
<SearchIcon />
</InputContainer>
)
}
Child 2:
const ChildTwo = () => {
// I want to pass the searchTerm to be used in a fetch request in this component
const apiURL = `'url' + {searchTerm}`;
return(
...
)
}
App.js
function App(){
return(
<>
<ChildOne/>
<Switch>
<Route path="/" exact component={Home}/>
<Switch/>
</>
)
}
Home.js:
const Home = () => {
return (
<>
<ChildTwo />
</>
);
};
there is several way to do that...
I suggest you use Context Api.
if you don't want to use Context Api or State management
see this example
enter link description here
import { useState } from "react";
import {
Route,
Switch,
BrowserRouter as Router,
RouterProps
} from "react-router-dom";
import ChildOne from "./ChildOne";
import Home from "./Home";
function App() {
const [value, setValue] = useState("");
return (
<>
<ChildOne setValue={setValue} />
<Router>
<Switch>
<Route path="/" exact>
<Home value={value} />
</Route>
</Switch>
</Router>
</>
);
}
export default App;
hello
I am trying to make a menu toggle, where I have a variable with false as initial value, using react createContext and useContext hook, I set the initial state as true
// useMenu Context
import React, { useContext, useState } from 'react'
export const useToggle = (initialState) => {
const [isToggled, setToggle] = useState(initialState)
const toggle = () => setToggle((prevState) => !prevState)
// return [isToggled, toggle];
return { isToggled, setToggle, toggle }
}
const initialState = {
isMenuOpen: true,
toggle: () => {},
}
export const MenuContext = React.createContext(initialState)
const MenuProvider = ({ children }) => {
const { isToggled, setToggle, toggle } = useToggle(false)
const closeMenu = () => setToggle(false)
return (
<MenuContext.Provider
value={{
isMenuOpen: isToggled,
toggleMenu: toggle,
closeMenu,
}}>
{children}
</MenuContext.Provider>
)
}
export default MenuProvider
export const useMenu = () => {
return useContext(MenuContext)
}
so If true it will show the Menu if false it will show the Div where there a div
App.js
const { isMenuOpen } = useMenu()
//the providder
<MenuProvider>
<Header mode={theme} modeFunc={toggleTheme}/>
{isMenuOpen ? (
<Menu />
) : (
<Switch>
<Route path='/writing' component={Writings} />
<Route path='/meta' component={Meta} />
<Route path='/contact' component={Contact} />
<Route path='/project' component={Project} />
<Route exact path='/' component={Home} />
<Route path='*' component={NotFound} />
</Switch>
)}
<Footer />{' '}
</MenuProvider>
and when I add an onclick event the NavLink button of the menu to close it it does not work
Menu
const { closeMenu } = useMenu()
// return statement
{paths.map((item, i) => {
return (
<MenuItem
key={i}
link={item.location}
svg={item.icon}
path={item.name}
command={item.command}
onClick={closeMenu}
/>
)
})}
where did I go wrong
Issue
I suspect the issue is in App where you've a useMenu hook outside the MenuProvider used in App. This useMenu hook is using a MenuContext context but in the absence of a provider it instead uses the default initial context value.
const initialState = {
isMenuOpen: true,
toggle: () => {},
};
export const MenuContext = React.createContext(initialState);
export const useMenu = () => {
return useContext(MenuContext)
};
React.createContext
const MyContext = React.createContext(defaultValue);
Creates a Context object. When React renders a component that
subscribes to this Context object it will read the current context
value from the closest matching Provider above it in the tree.
The defaultValue argument is only used when a component does not
have a matching Provider above it in the tree. This default value can
be helpful for testing components in isolation without wrapping them.
Solution
Since I doubt you want to run/provide more than one menu provider I believe the solution is to move MenuProvider out of and wrap App to provide the context you are updating by nested components.
App.jsx
const { isMenuOpen } = useMenu();
...
<>
<Header mode={theme} modeFunc={toggleTheme}/>
{isMenuOpen ? (
<Menu />
) : (
<Switch>
<Route path='/writing' component={Writings} />
<Route path='/meta' component={Meta} />
<Route path='/contact' component={Contact} />
<Route path='/project' component={Project} />
<Route exact path='/' component={Home} />
<Route path='*' component={NotFound} />
</Switch>
)}
<Footer />
</>
index.jsx (?)
import App from './App.jsx';
...
//the provider
<MenuProvider>
<App />
</MenuProvider>
I am building a multi-language app. I am using react-intl. So far, so good. I made a state of the language with context api, so I can switch it easily. However I get this error when I try to use the state in App.js: TypeError: Object is not iterable (cannot read property Symbol(Symbol.iterator)).
Here is my context file:
import React, {useState, createContext} from 'react'
export const LanguageContext = createContext();
export const LanguageProvider = (props) => {
const [language, setLanguage] = useState('')
return (
<LanguageContext.Provider value = {[language,setLanguage]}>
{props.children}
</LanguageContext.Provider>
)
}
And here is the App.js:
function App() {
const [language, setLanguage] = useContext(LanguageContext)
return (
<LanguageProvider>
//i tried using locale={language}
<I18nProvider locale={LOCALES.language}>
<CartProvider>
<TableProvider>
<div className="App">
<Router>
<Header />
<Switch>
<Route path='/Cart' component={Cart} />
<Route path='/:group/:subGroup/:item' component={Item} />
<Route path='/:group/:subGroup' component={Items} />
<Route path='/' exact component={Home} />
<Route path='/:group' component={Groups} />
</Switch>
</Router>
</div>
</TableProvider>
</CartProvider>
</I18nProvider>
</LanguageProvider>
);
}
export default App
Here is the locale file that Im using to pass to the I18nProvider :
export const LOCALES = {
ENGLISH : 'en',
FRENCH: 'fr'
}
And where I change the context value(another component, not App.js):
const [language, setLanguage] = useContext(LanguageContext)
following line is cut from jsx:
onClick={() => setLanguage('en')}
I thinks the problem might be because I am trying to access the context before the App.js return statement, where the provider wraps the children but even if this is the case, I still don't know what might fix it. Any help would be appreciated!
I thinks the problem might be because I am trying to access the context before the App.js return statement
You're right this is the problem.
Depending on where you want to use useContext you could create an extra component that is a child of LanguageProvider. Then inside this child you are able to use useContext.
To give a simplified example:
const App = () => {
const [language, setLanguage] = useContext(LanguageContext);
useEffect(() => {
setLanguage('en');
}, []);
return <p>{language}</p>;
};
export default function AppWrapper() {
return (
<LanguageProvider>
<App />
</LanguageProvider>
);
}
I had the same problem trying to apply an authentication flow with react-navigation v5. I tried to follow the documentation as it is:
Authentication flows with react-navigation v5 But when trying to mix it with Context I run into the same issue
As in the previous Answer, I solve it in the same way.
Here it's an example where it has 3 possible Screens Stacks:
I create a component where I'll be using the context
const RootStack = () => {
const { state } = useContext(AuthContext);
return (
<Stack.Navigator>
{false ? (
<Stack.Screen name="Splash" component={SplashScreen} />
) : state.token === null ? (
<Stack.Screen
name="Authentication"
component={AuthenticationStack}
options={{
title: 'Sign in',
headerShown: false,
animationTypeForReplace: false ? 'pop' : 'push',
}}
/>
) : (
<Stack.Screen name="Home" component={AppStack} />
)}
</Stack.Navigator>
);
};
And then I insert the component inside the provider:
export default ({ navigation }) => {
return (
<AuthProvider>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="RootStack"
component={RootStack}
options={{
headerShown: false,
}}
/>
</Stack.Navigator>
</NavigationContainer>
</AuthProvider>
);
};