React - Toggle theme from another component - javascript

I am working on a project on react but I have ran into an issue.
The issue I am having is that I need to be able to toggle my "dark" and "light" theme from a icon that is in a different component. This icon exists in my BottomNavigation but the function for switching the theme exists in my app.js.
Did some research on my own and found that I need to "lift the state up". Issue is that I need to lift it twice as my files look like this:
./Components
./Home.js
- (components gets added here)
./Navigation
./BottomNavigation.js
app.js
(/home is added here)
My app.js looks like:
function App() {
const [theme, setTheme] = useState("light");
const themeToggler = () => {
theme === "light" ? setTheme("dark") : setTheme("light");
};
return (
<ThemeProvider theme={theme === "light" ? lightTheme : darkTheme}>
<GlobalStyles />
<Router>
<Route exact path="/">
<Home />
</Route>
<Route exact path="/account">
<Account />
</Route>
</Router>
</ThemeProvider>
);
}
My Home.js looks like:
const Home = (props) => {
const [showingState, setIsShowing] = useState(false);
return (
<div>
<TopNavigation isShowing={(showingState) => setIsShowing(showingState)} />
<BottomNavigation />
<ImageSlider />
<Grid />
{showingState && (
<CurrencyPopup
isShowing={(showingState) => setIsShowing(showingState)}
/>
)}
<BestSeller />
<CollectionPromo />
<Instagram />
<Footer />
</div>
);
};
My BottomNavigation.js looks like (only took the part with the icon):
<div className={classes.options_container}>
<IconApp className={classes.icon_container}>
<span className={classes.cart_sum}>$0.00</span>
<Cart className={classes.icon} />
</IconApp>
<IconApp className={classes.icon_container}>
<Heart className={classes.icon} />
</IconApp>
<IconApp className={classes.icon_container}>
<Visibility
onClick={() => props.setTheme("")} //This icon shall switch the theme
className={classes.icon}
/>
</IconApp>
<IconApp className={classes.icon_container}>
<a href="/account">
<User className={classes.icon} />
</a>
</IconApp>
</div>
If you have any ideas or need something more from the code, let me know!

For best practice, you have a few options:
Use React Context.
Use a state management library like Redux and MobX.
You definitely don't want to life state up in your case, because the two components are too far away from each other in the hierarchy.

Lifting state up would be solution that does not require any additional knowledge.
However, its excessive use (over three or more component) is a bad practice, since it makes code dirty
Using RecoilJS would be easiest, since its api is nearly identical to that of useState, and there is less work for you to do
Sticking to vanilla ReactJS, try React Context

Related

Functions are not valid as react child Error while using Navigate in react router dom

I am getting the error ("Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.") when I am trying to use condition which includes Navigate to "/path" (look at the statement in the code).
return (
<>
<Router>
<Navbar bg="dark" variant="dark">
<Container>
<Navbar.Brand to="/">MoviesReviews</Navbar.Brand>
<Nav className="me-auto">
<Link to="/picks">Picks</Link>
<Link to="/critics">Critics</Link>
</Nav>
<Form className="d-flex">
<FormControl
type="search"
placeholder="Search"
className="me-2"
aria-label="Search"
id = "SearchBox"
/>
{redirect === true? (() => (<Navigate to = "/search"/>)) : null}
<Button variant="outline-success" onClick = {() => pressSearch()}>Search</Button>
</Form>
</Container>
</Navbar>
<Routes>
<Route path = "/" element = {<Reviews/>}/>
<Route exact path = "/picks" element = {<Reviews/>}/>
<Route exact path = "/critics" element = {<Critics/>}/>
<Route exact path = "/search" element = {<SearchReviews search = {searchString}/>}/>
</Routes>
</Router>
</>
);
If you look at the Route exact path for "/search", you see that I am actually returning a <Component/>. I am pretty sure the problem is in the line which contains the condition ({redirect === true? (() => (<Navigate to = "/search"/>)) : null}).
{redirect === true? (() => (<Navigate to = "/search"/>)) : null} with the above statement you :
in false case you are returning null.
in true case you are trying to render a function.
() => (<Navigate to = "/search"/>)
Hence it is not valid as a react child.
You have to do : {redirect === true ? <Navigate to="/search"/> : null} as mentioned in comments.
Functions are not valid JSX, they can't be rendered.
If you are conditionally rendering React components there are a couple syntaxes you can use, neither involves using a function. It's considered bad practice to compare boolean variables against true/false, just use the variable's value for the condition. If you just need to test the truthy/falsey-ness of a value then use !! to coerce it to a strict boolean value.
Using a ternary: condition ? <Component1 /> : null
{redirect ? <Navigate to="/search" replace /> : null}
This is useful if you need to conditionally render one of two different components, or to return null as a component result.
Using logical AND (&&): condition && <Component1 />
{redirect && <Navigate to="/search" replace />}
This is useful if you only need to conditionally render a single component.
If you are conditionally rendering the entire component render result then you should use the first in order to return null as valid JSX.
Suggestion
It would be better to issue an imperative redirect instead of setting any redirect state. This has the benefit of not requiring an additional React render cycle to effect the change.
In order for this component to use any react-router-dom hooks, the Router will need to be moved higher in the ReactTree, i.e. to the parent component or higher of this component.
Example:
import { useNavigate } from 'react-router-dom';
...
const Component = () => {
const navigate = useNavigate();
...
// logic to replace setting any `redirect` state
if (<condition to redirect>) {
navigate("/search", { replace: true });
}
...
return (
<>
<Navbar bg="dark" variant="dark">
<Container>
<Navbar.Brand to="/">MoviesReviews</Navbar.Brand>
<Nav className="me-auto">
<Link to="/picks">Picks</Link>
<Link to="/critics">Critics</Link>
</Nav>
<Form className="d-flex">
<FormControl
type="search"
placeholder="Search"
className="me-2"
aria-label="Search"
id = "SearchBox"
/>
<Button
variant="outline-success"
onClick={pressSearch}
>
Search
</Button>
</Form>
</Container>
</Navbar>
<Routes>
<Route path="/" element={<Reviews />} />
<Route path="/picks" element={<Reviews />} />
<Route path="/critics" element={<Critics />} />
<Route path="/search" element={<SearchReviews search={searchString} />} />
</Routes>
</>
);
};

How to use a hook in a different react component?

I created a hook to toggle the visibility of a NavBar in my webpage (this is done in NavBar.jsx), I need to toggle the navbar elsewhere in my code, namely under Journey.jsx, can I pass these as params?
How should I approach this?
Here are the essential/boiled-down excerpts of my code if they can help....
App.jsx:
function App () {
return (
<Router hashType="hashbang">
<div className="App">
<NavBar />
<Switch>
<Route exact path="/l" component={() => <Lorem/>} />
<Route exact path="/j" component={() => <Journey/>} />
<Route exact path="/i" component={() => <Ipsum/>} />
</Switch>
</div>
</Router>
);
}
NavBar.jsx:
function NavBar () {
//I need access to these in Journey.jsx
const [sidebar, setSidebar] = useState(false);
const showSidebar = () => setSidebar(!sidebar);
return(
//elaborations about the menu where I use these functions/varariables
);
}
Journey.jsx:
function Journey () {
return (
//some unimportant divs
<button onClick={****I need access to showSidebar here****} ></button>
);
}
The way my NavBar is configured is so that the hamburger icon that toggles it is visible and usable from everywhere (including the journey page), but I want this specific button only on the journey page and nowhere else because, according to the theme of the website (by my team of designers) its supposed to be an aesthetic functionality
What can I do to make this happen? because I've tried re-creating the hook into App.jsx and try passing each func/var as props and then in Journey referencing it as props.sidebar etc. but that doesnt work either....
if the solution is to just pass as parameters, how exactly do I do that because I tried and it said something like it wasnt declared.
Any alternatives?
Thank you,
either you lift the state up to their closest common ancestor (app.js) and create a handleClick method in App.js (do the state change in App.js) and pass down the method with navBar current state as porps to Journey and NavBar.
or you use a Context check: https://reactjs.org/docs/context.html and https://reactjs.org/docs/hooks-reference.html#usecontext for further info.
The easiest way to share state is lifting state up to their common parent component, and then pass the state and some methods which change state by props, like this:
function App() {
// lift state up
const [sidebar, setSidebar] = useState(false);
const showSidebar = () => setSidebar(!sidebar);
return (
<Router hashType="hashbang">
<div className="App">
{/* pass state by props */}
<NavBar sidebar={sidebar} showSidebar={showSidebar} />
<Switch>
<Route exact path="/l" component={() => <Lorem />} />
{/* pass state by props */}
<Route exact path="/j" component={() => <Journey sidebar={sidebar} showSidebar={showSidebar} />} />
<Route exact path="/i" component={() => <Ipsum />} />
</Switch>
</div>
</Router>
);
}
function NavBar ({sidebar,showSidebar}) {
return(
//elaborations about the menu where I use these functions/varariables
);
}
function Journey ({sidebar,showSidebar}) {
return (
// use state from props
<button onClick={showSidebar} ></button>
);
}
Also you can use context to pass state deeply.
You can read the latest react docs beta, which describe very detailed:
React Doc Beta: Sharing State Between Components
React Doc Beta: Passing Data Deeply with Context
I hope it helpful for you!

Menubar is not refreshing after sign in or sign out in React

My Menubar is not automatically refreshing when I sign in or sign out, but when I refresh the page manually it is refreshing. I tried so many solutions, but still can't solve this.
Please help me to find a solution to this problem.
My code:
// index page
ReactDOM.render(<Provider store={store}>
<Routes />
</Provider>, document.getElementById('root'));
My Routes.js file
return (
<BrowserRouter>
<Menu /> //this is my menubar
<Switch>
<BuyerRoutes path = "/myprofile" exact component={ProfileUpdate} />
<BuyerRoutes path = "/dashboard" exact component={BuyerHome} />
<SellerRoutes path = "/seller" exact component={SellerHome} />
<SellerRoutes path = "/addproduct" exact component={AddProduct} />
</Switch>
</BrowserRouter>
);
my auth.js file
<Route
{...rest}
render = {props =>
isAuthenticate() && isAuthenticate().user.usertype == 1 ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname:"/login",
state: {from: props.location}
}}
/>
)
}
/>
My menu.js file
{user && user.usertype===0 ?
<Fragment>
<Link className="dropdown-item" to="/seller" >Home</Link>
<Link className="dropdown-item" to="/addproduct" >Add Product </Link>
</Fragment>:
<Fragment>
<Link className="dropdown-item" to="/myprofile" > My Profile</Link>
<Link className="dropdown-item" to="/dashboard" > My Orders</Link>
</Fragment>
}
Thanks in advance.
Your Menu component usage under <BrowserRouter> doesn't involve any dynamic props and state. In React, to re-render components dynamically, you should pass dynamic contents via props, whose changes will be reflected by re-rendering. That's why your <Menu> component is not changing at all while your router is changing.
You need to update the store state after sign in and sign out. For an example dispatch an action to delete user login state from the store.

Don't render component on specific route

I have a React-based web application that utilizes React Router to map pages to different URLs:
export const Container = () => (
<div>
<SideNav/>
<div>
<Switch>
<Route path="/login" component={LoginView} />
<Route path="/route1" component={RouteOne} />
<Route path="/route2" component={RouteTwo} />
</Switch>
</div>
</div>
)
When any route gets hit, the sidebar gets rendered as well as the appropriate view. However, I am trying to build the layout such that for certain routes (such as "login"), the SideNav doesn't get rendered and the component (in this case, LoginView) is the only thing that gets rendered. In other words, LoginView should take over the div and be the only child of the top div.
Is there anyway this can be done?
According to react-router docs:
path: string Any valid URL path that path-to-regexp understands.
path-to-regexp understand a string, array of strings, or a regular expression.
Array of routes:
State which routes will render the SideNav as well (Working Example):
<Route path={['/route1', '/route2']} component={SideNav} />
RegExp:
Another option is to show the SideNav only if the path doesn't contain a certain word (working example):
<Route path={/^(?!.*login).*$/} component={SideNav} />
And in your code:
export const Container = () => (
<div>
<Route path={['/route1', '/route2']} component={SideNav} />
<div>
<Switch>
<Route path="/login" component={LoginView} />
<Route path="/route1" component={RouteOne} />
<Route path="/route2" component={RouteTwo} />
</Switch>
</div>
</div>
)
Another approach (I am not sure about this but I faced the same problem and this is how I fixed it, but I admit it's less cleaner than what Ori Drori proposed):
In your SideNav component :
import React from "react";
import {useLocation} from "react-router"
export const SideNav = (props) => {
const location = useLocation();
const show = !location.pathname.includes("login");
return (
<>
{show && (<YourLoginComponentCode /> }
</>
)
}

How to pass props between React Routes 4

I can't find the way how to send object from one React Route Component to another.
For example I have container router like this:
const App = () => (
<Router>
<div>
<Route exact path="/" component={Home} />
<Route path="/sections/:id"
component={Section} />
</div>
</Router>
)
with Home component like this:
const Home = () => {
const Sections = tilesData.map( (section, index) => (
<div>
<img src={section.img} height="200" /><br/>
<Link to={`/sections/'${section.id}`} >Details for {section.author}</Link>
<hr/>
</div>
))
return(
<div>
{Sections}
</div>
)
}
and I don't understand =\ how to pass selected object when next route clicked with <Link>. Here is example component:
const Section = (props) => {
return(
<div>
Section {props.title}
<img src={props.img} />
</div>
)
}
Here is code example: https://codesandbox.io/s/Mv037AE3
In react-router v4, we usually do the following to pass in a ProductPage component:
<Route path='/' component={ProductPage} />
When you want to use props, you can prepare a function that returns your component with the desired props. Here I'm preparing to pass in a toggleSidebarOn prop:
const MyProductPage = (props) => {
return (
<ProductPage
toggleSidebarOn={this.toggleSidebarOn.bind(this)}
{...props}
/>
);
}
Then you use the render prop of the <Route /> instead of its component prop. Do not use both and do not use the component prop as this leads to undesired remounting and might break your app (e.g. for me CSS transitions for the sidebar animation stopped working properly).
Using the render prop for the MyProductPage we can then pass in our toggleSidebarOn prop to its target route:
<Router>
<div>
<Switch>
<Route exact path="/products" render={MyProductPage} />
<Route exact path="/perspectives" component={PerspectivePage}/>
<Route component={NotFound}/>
</Switch>
</div>
</Router>
Hope this helps!

Categories