I'm trying to find a way to switch between dark and light theme in my web app.
I want to add the switch in my AppBar component which is inside my header but I'm not sure how to get it to work on all of the web app and not just the header.
AppBar.js :
//imports ...
const AppBar = () =>{
const [theme, setTheme] = useState(false);
const changeTheme = () => {
setTheme(!theme);
};
//rest of the code....
<Box sx={{ flexGrow: 0 }}>
<Switch checked={theme} onChange={changeTheme} />
</Box>
};
export default AppBar;
here is the code from the Header.js
const Header = () => {
return (
<header>
<AppBar />
</header>
);
};
export default Header;
so the header is just rendering one component one could get rid of it if it was necessary.
and here is my App.js (routes)
//imports ...
//themes ...
const app = ()=>{
return (
<React.Fragment>
<ThemeProvider theme={theme ? lightTheme : darkTheme}>
<CssBaseline />
<Router>
<Header />
<Routes>
//Routes ...
</Routes>
<Footer />
</Router>
</ThemeProvider>
</React.Fragment>
);
}
export default App;
I'd really appreciate the help and Thanks in advance!
you can store the value of theme inside context or redux store and change it using a dispatch function once that value changes the whole component related to it will re render !, so your component will change the value inside context ! rather than having the value stuck inside one component.
Related
Here is my Context File:
import React from 'react';
import { createContext, useState } from 'react';
export const TabContext = createContext({
opened: false,
toggleOpened: () => {},
});
export const TabContextProvider = ({ children }) => {
const [opened, setOpened] = useState(false);
const toggleOpened = () => {
setOpened(!opened);
};
return (
<TabContext.Provider value={{ opened, toggleOpened }}>
{children}
</TabContext.Provider>
);
};
My Simplified App.js File: (Necessary files are imported)
const Tab = createBottomTabNavigator();
export default function App() {
const buttonCtx = useContext(TabContext);
return (
<>
<TabContextProvider>
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen
name='Action'
component={ActionScreen}
options={{
tabBarButton: () => (
<ActionButton
opened={buttonCtx.opened}
toggleOpened={buttonCtx.toggleOpened}
/>
),
}}
/>
</Tab.Navigator>
</NavigationContainer>
</TabContextProvider>
</>
);
}
And the Simplified ActionButton Component:
export default function ActionButton({ opened, toggleOpened }) {
return (
<View style={styles.container}>
<View style={styles.box}>
<TouchableWithoutFeedback
onPress={toggleOpened}
style={styles.actionButton}
>
/* With an Animated View inside */
</TouchableWithoutFeedback>
</View>
</View>
);
}
Basically, **toggleOpened **should switch the value of the variable **opened **between true and false. So the **AnimatedView **can work properly which solely depends on the value of opened.
Opened is readable in all of the components, no problem with that. But **toggleOpened **is not working at all. Any idea?
In order to use contexts properly, you need to have at least two components working together,one that renders the Provider and one descendant who then uses that context.
You are trying to provide and use the context at the same time,try to move the consumer component one position down to the hierarchy.
For example in your App.js you can create a consumer component to wrap your ActionButton,then pass the context to it as you did :
export default function App() {
return (
<>
<TabContextProvider>
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen
name="Action"
component={ActionScreen}
options={{
tabBarButton: () => <ActionButtonWrapper />
}}
/>
</Tab.Navigator>
</NavigationContainer>
</TabContextProvider>
</>
);
}
const ActionButtonWrapper = () => {
const { opened, toggleOpened } = useContext(TabContext);
return (
<>
<ActionButton
opened={opened}
toggleOpened={toggleOpened}
/>
</>
);
};
However,i would just use the context directly within ActionButton,after all,passing props down to children is what we want to avoid by using context,right?
I have created for you a snippet to see how we can properly use context
after getting all mixed up with state, i am now trying to restructure my app in a way that might be more reflective of best practices (not sure if this is the way, advice is welcome.)
so, i have my main page, which holds 3 states: viewer,buyside,sellside
there are also three different components, one for each of those states.
i want to be able to pass the props down from the main page, through those components, to their children (i've read this is the best approach??)
main page:
//we have 3 states for the website: viewer,buyside customer, sellside customer
const [visitorType, setVisitorType] = useState('viewer');
if (visitorType == 'viewer') {
return(
<div>
<Viewer visitortype='viewer' setvisitor={()=>setVisitorType()}/>
</div>
)}
else if (visitorType =='buyside') {
return(
<div>
<Buyside visitortype='buyside' setvisitor={()=>setVisitorType()}/>
</div>
)}
else if (visitorType =='sellside') {
return(
<div>
<Sellside visitortype='sellside' setvisitor={()=>setVisitorType()}/>
</div>
)}
};
what is the best way to pass down the main page props, so that i can bring them down to any grandchildren, along with the child props?
the viewer component -UPDATED-:
const MainView = (props) => {
return(
<>
<Navbar mainprops={{props}}/>
</>
)
};
export default MainView
i was previously just passing them individually, but realized it might be better to do so as one object...
UPDATE: point taken on the syntax, but i'm wondering how i can best pass the objects
nav component (grandchild)
const Navbar = (props) => {
const {mainprops} = props.mainprops;
if (mainprops.visitortype == 'viewer') {
return(
<>
<h1>viewer navbar</h1>
</>
)}
else if (mainprops.visitortype =='buyside') {
return(
<>
<h1>buyside navbar</h1>
</>
)}
else if (mainprops.visitortype =='sellside') {
return(
<>
<h1>sellside navbar</h1>
</>
)}
};
export default Navbar;
UPDATE 2 - this works, but not sure if it is the correct way, are these still considered object literals??
viewer component:
const MainView = (props) => {
const mainprops = {...props}
return(
<>
<Navbar mainprops={mainprops}/>
</>
)
};
export default MainView
navbar component
const Navbar = (props) => {
const mainprops = {...props.mainprops};
if (mainprops.visitortype == 'viewer') {
return(
<>
<h1>viewer navbar</h1>
</>
)}
else if (mainprops.visitortype =='buyside') {
return(
<>
<h1>buyside navbar</h1>
</>
)}
else if (mainprops.visitortype =='sellside') {
return(
<>
<h1>sellside navbar</h1>
</>
)}
};
export default Navbar;
if this is correct, then is this what #amir meant?
First there are certain rules for passing props:
You never ever pass literal object as props since it will not be the same every re-render and will cause the child component to re-render too (without any new info)
You don't need to do that
<Viewer visitortype='viewer' setvisitor={()=>setVisitorType()}/>
You can:
<Viewer visitortype='viewer' setvisitor={setVisitorType}/>
since it comes from useState react make sure the setVisitorType keeps the same reference
And now for you error, you almost correct you just did a js syntax error
you should write it like this:
const MainView = (props) => {
return(
<>
<Navbar mainobj={{
visitortype:props.visitortype,
setvisitor:props.setvisitor
}}
/>
</>
)
};
export default MainView
But again you never send literal object as props
I would keep it inside a ref or state (depend if the visitor state will be change)
i'm new to react(hooks typescript) world, here i have an icon when user clicks that it should go to '/menu' and when it is again clicked it should go to previous page, and if again clicked it should go to '/menu' so basically it should work like toggle.
am i doing this wrong :
import { useHistory } from "react-router-dom";
const [menu, setMenu] = useState(false);
const history = useHistory();
<Box onClick={() => setMenu(!menu)}>
{menu ? (
<Link to={history.goBack}>
<MenuIcon fontSize="large" />
</Link>
) : (
<Link to="/menu">
<MenuIcon fontSize="large" />
</Link>
)}
</Box>
If I'm not wrong, Link can not be used this way. It expects the route not the function. How about using a div and placing onClick handler where you can navigate back through history.goBack()?
The to property expect a string as value (path). if you don't know what the previous route is, I recommend you to create a context to store the previous route value:
const RouterContext = React.createContext();
const RouterProvider = ({children}) => {
const location = useLocation()
const [route, setRoute] = useState({
to: location.pathname,
from: location.pathname
});
useEffect(()=> {
setRoute((prev)=> ({to: location.pathname, from: prev.to}) )
}, [location]);
return <RouterContext.Provider value={route}>
{children}
</RouterContext.Provider>
}
then:
const route = useContext(RouterContext);
<Box onClick={() => setMenu(!menu)}>
<Link to={menu ? route.from : "/menu" }>
<MenuIcon fontSize="large" />
</Link>
</Box>
Don't forget to add RouterProvider under Router component:
<Router>
<RouterProvider>
...
</RouterProvider>
</Router>
Working example (About component has the go back option)
Can someone tell me if there is a way to pass the dark variable from Navbar component to the App component here is a little part from my Navbar component which contains the state:
function Navbar({search, handleSearch, region, handleChange, number}){
const [dark , setDark] = useState(false)
function handlThem(){
setDark(prevThem=> !prevThem )
}
return(
<div className="navbar" style={ dark ? {backgroundColor : "#333"} : null}>
)
}
I want to pass dark here in the App component to change and use it to change it's class or toggle to change the background like this style={ dark ? {backgroundColor : "#333"}
the App component :
function App() {
return (
<div className="App">
<Body />
</div>
);
}
This is a good use case for React Context. I'm providing an example using hooks API. You can create a context then use the values (state and state setter) in any of the components you wrap with the provider.
const ThemeContext = React.createContext();
function App() {
const [dark , setDark] = React.useState(false);
return (
<ThemeContext.Provider value={{ dark, setDark }}>
<Body />
</ThemeContext.Provider>
);
}
function Navbar() {
const value = React.useContext(ThemeContext);
return (
<div>Navbar<button onClick={() => value.setDark(true)}>Change to Dark</button></div>
);
}
function Body() {
const value = React.useContext(ThemeContext);
return (
<div style={ value.dark ? {backgroundColor : "#333"} : null}>
<Navbar />
<div>Rest of the body</div>
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root" />
Try this
App.js
function App() {
function getTheme(themeValue) {
console.log(themeValue);
}
return (
<div className="App">
<Body getTheme={getTheme}/>
</div>
);
}
Navbar.js
function Navbar({search, handleSearch, region, handleChange, number, getTheme}){
const [dark , setDark] = useState(false)
function handlThem(){
const theme = !dart;
setDark(prevThem=> theme )
getTheme(theme);
}
return(
<div className="navbar" style={ dark ? {backgroundColor : "#333"} : null}>
)
}
As I passed to body, you pass to Navbar component, you didn't post body component that's why passed to body component and accessed in Navbar. you can change as per your requirement.
I'm currently trying to find a way to display a custom component (like a Modal) to confirm route changes using the Prompt component.
The default behavior of the Promp component is to show a confirm dialog with a message, as you can see in this Example: React Router: Preventing Transitions.
Note: I am using the <BrowserRouter> component.
The router has a prop named getUserConfirmation, which you can use to customize the behavior of the <Prompt> component.
// this is the default behavior
function getConfirmation(message, callback) {
const allowTransition = window.confirm(message);
callback(allowTransition);
}
<BrowserRouter getUserConfirmation={getConfirmation} />;
What I'm trying to do:
Inside the parent component APP
I'm setting the confirm state to true, to display the <Confirm> component
And I'm trying to pass the callback from the getConfirmation function to the <Confirm> component to call it with true to allow transition, and with false to prevent it.
The callback would be called with true or false in the default behavior as you can see above.
function getConfirmation(message, callback) {
console.log("Inside getConfirmation function...");
setConfirmCallback(callback);
setConfirm(true);
// const allowTransition = window.confirm(message);
// callback(allowTransition);
}
This is what App.js is rendering:
return (
<Router getUserConfirmation={getConfirmation}>
<AllRoutes />
{confirm && (
<Confirm confirmCallback={confirmCallback} setConfirm={setConfirm} />
)}
</Router>
);
What seems to be the problem:
The confirm dialog seems to block the function at that point. So the callback variable/parameter is still in scope. So everything works OK.
When I remove the confirm dialog, that function runs all the way. And when I click on the confirm button inside the <Confirm> component, the callback no longer exists.
QUESTION
Does anybody know a way to achieve this behavior (preventing route changes using a custom component instead of a confirm dialog) using react-router-dom?
Link to CodeSandbox
Full code from CodeSandbox:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter as Router,
Route,
Switch,
Link,
Prompt
} from "react-router-dom";
import "./styles.css";
function App() {
console.log("Rendering App...");
const [confirm, setConfirm] = useState(false);
const [confirmCallback, setConfirmCallback] = useState(null);
function getConfirmation(message, callback) {
console.log("Inside getConfirmation function...");
setConfirmCallback(callback);
setConfirm(true);
// const allowTransition = window.confirm(message);
// callback(allowTransition);
}
return (
<Router getUserConfirmation={getConfirmation}>
<AllRoutes />
{confirm && (
<Confirm confirmCallback={confirmCallback} setConfirm={setConfirm} />
)}
</Router>
);
}
function Confirm(props) {
function allowTransition() {
props.setConfirm(false);
props.confirmCallback(true);
}
function blockTransition() {
props.setConfirm(false);
props.confirmCallback(false);
}
return (
<React.Fragment>
<div>Are you sure?</div>
<button onClick={allowTransition}>Yes</button>
<button onClick={blockTransition}>No way</button>
</React.Fragment>
);
}
function AllRoutes(props) {
console.log("Rendering AllRoutes...");
return (
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/comp1" component={Component1} />
</Switch>
);
}
function Home(props) {
console.log("Rendering Home...");
return (
<React.Fragment>
<div>This is Home</div>
<ul>
<li>
<Link to="/comp1">Component1</Link>
</li>
</ul>
</React.Fragment>
);
}
function Component1(props) {
console.log("Rendering Component1...");
const [isBlocking, setIsBlocking] = useState(true);
return (
<React.Fragment>
<Prompt
when={isBlocking}
message={location =>
`Are you sure you want to go to ${location.pathname}`
}
/>
<div>This is component 1</div>
<Link to="/">Home</Link>
</React.Fragment>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Inspired by this discussion and by this example, I was able to make my example working.
The problem was that when the <Confirm> was being created, the setConfirmCallback() call wasn't done yet. So the <Confirm> component wasn't able to use the callback from getUserConfirmation.
So I've changed this line:
FROM:
setConfirmCallback(callback);
TO:
setConfirmCallback(()=>callback);
And now it works!
CodeSandbox Link
Full CodeSandbox code:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter as Router,
Route,
Switch,
Link,
Prompt
} from "react-router-dom";
import "./styles.css";
function App() {
console.log("Rendering App...");
const [confirm, setConfirm] = useState(false);
const [confirmCallback, setConfirmCallback] = useState(null);
function getConfirmation(message, callback) {
console.log("Inside getConfirmation function...");
setConfirmCallback(() => callback);
setConfirm(true);
// const allowTransition = window.confirm(message);
// callback(allowTransition);
}
return (
<Router getUserConfirmation={getConfirmation}>
<AllRoutes />
{confirm && (
<Confirm confirmCallback={confirmCallback} setConfirm={setConfirm} />
)}
</Router>
);
}
function Confirm(props) {
console.log("Rendering Confirm...");
function allowTransition() {
props.setConfirm(false);
props.confirmCallback(true);
}
function blockTransition() {
props.setConfirm(false);
props.confirmCallback(false);
}
return (
<React.Fragment>
<div>Are you sure?</div>
<button onClick={allowTransition}>Yes</button>
<button onClick={blockTransition}>No way</button>
</React.Fragment>
);
}
function AllRoutes(props) {
console.log("Rendering AllRoutes...");
return (
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/comp1" component={Component1} />
</Switch>
);
}
function Home(props) {
console.log("Rendering Home...");
return (
<React.Fragment>
<div>This is Home</div>
<ul>
<li>
<Link to="/comp1">Component1</Link>
</li>
</ul>
</React.Fragment>
);
}
function Component1(props) {
console.log("Rendering Component1...");
const [isBlocking, setIsBlocking] = useState(true);
return (
<React.Fragment>
<Prompt
when={isBlocking}
message={location =>
`Are you sure you want to go to ${location.pathname}`
}
/>
<div>This is component 1</div>
<Link to="/">Home</Link>
</React.Fragment>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I found a simple workaround for my case. I could not share the whole component but snippets.
// this will initiate the dialogbox render and
// prevent the window from going back by returning false
const backButtonPressed = async () => {
leavePrompt(false);
return false;
}
// this will open the prompt dialog box
const leavePrompt = (endRoom) => {
setOpenPrompt({open: true, action: endRoom ? "endRoom" : "leaveQuitely"});
}
// render
<Dialog open={openPrompt.open} aria-labelledby="interim-user-dialog-title">
<DialogContent dividers>
<Typography variant="h6" gutterBottom>
Are you sure?
</Typography>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpenPrompt({...openPrompt, open: false})} color="primary">
Stay
</Button>
<Button onClick={() => history.push("/")} color="secondary">
Leave
</Button>
</DialogActions>
</Dialog>
// when allowedToGoBack state is true then call a method that will render the dialog box
<Prompt
when={true}
title={"Alert"}
message={() => allowedToGoBack ? backButtonPressed() && false : true}
/>