How to use a hook in a different react component? - javascript

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!

Related

Params matching return empty React-router

I am facing an apparently simple problem but it is getting complicating , even with some well written existing topics.
I have a global route
export const createPricingPath = `${catalogRoutes.priceLists.path}/creation/:pricingId?/:step(products|parameters|customers-groups)`;
export const toCreatePricingPath = pathToRegexp.compile(createPricingPath);
As you can see I have a step parameters and an optional one called pricingId.
I have a parent route leading to my top level parent component
<ProtectedRoute
exact
AUTHORIZED_ROLES={[USER_ROLES.PRICING]}
path={createPricingPath}
render={props => <PricingCreationPanelContainer {...props} />}
/>
The parent top root component is the following one :
<SlidePaneProvider route={catalogRoutes.priceLists.path}>
{({ handleCancel }) => (
<Fragment>
<PricingCreationPanelHeader handleCancel={handleCancel} {...this.props} />
<SlidePaneBody>
<PricingContent handleCancel={handleCancel} {...this.props} />
</SlidePaneBody>
</Fragment>
)}
</SlidePaneProvider>
Inside this component we got somme "commun" elements like a PricingCreationHeader and SlidesComponents ( body ect...), and I have a PricingContent which is the children containing my sub-routes and children components.
It looks like this:
const PricingContent = ({ handleCancel, match: { params } }) => {
return (
<Switch>
<ProtectedRoute
exact
AUTHORIZED_ROLES={[USER_ROLES.PRICING]}
path={toCreatePricingPath({ step: 'parameters' })}
render={props => <PricingCreation {...props} />}
/>
<ProtectedRoute
exact
AUTHORIZED_ROLES={[USER_ROLES.PRICING]}
path={toCreatePricingPath({ step: 'products', pricingId: params.pricingId })}
render={props => <PricingProducts {...props} />}
/>
<ProtectedRoute
exact
AUTHORIZED_ROLES={[USER_ROLES.PRICING]}
path={toCreatePricingPath({ step: 'customers-groups', pricingId: params.pricingId })}
render={props => <PricingClients handleCancel={handleCancel} {...props} />}
/>
</Switch>
)
};
The problem is, I can't figure out why I am not able to get the match.params.pricingId and match.params.step props inside every child component (PricingClients and PricingProducts ). PricingCreation doesnt need pricingId by the way, only the other two.
I do have the impression that everything is clearly well written.
The child component should be able to get all the params since the root parent component does.
Maybe it is an architecture problem so every suggestion is welcome !
I went through many existing topics and was not able to find a proper solution.
Thank you very much for helping me !
As far as I can see here, path for child routes is specified incorrectly. I dont think that making path dynamically is a good idea. It is enough to specify shortened version of parent path and match.pricingId will be available in child components:
const PricingContent = ({ handleCancel }) => {
return (
<Switch>
<ProtectedRoute
path={ `${base_path }/creation/:pricingId?/parameters`}
// other props stay the same
/>
<ProtectedRoute
path={ `${base_path}/creation/:pricingId?/products`}
// other props stay the same
/>
<ProtectedRoute
path={ `${base_path}/creation/:pricingId?/customer-groups`}
// other props stay the same
/>
</Switch>
)
};
According to code above base_path is catalogRoutes.priceLists.path.
I think there is no need in :step route param as we have a separate component for every route.
In case we still need it, it's possible to do smt like this: .../:step(parameters);
Optional :pricingId? can cause mounting of incorrect component, for example:
base_url/products/parameters
In this case router is considering "products" as :pricingId route param which is not good=)
I often try to avoid optional params in the middle of a path
My apologies if I missed or misunderstood something
Thank you

How do I pass props down from react-router-doms's render?

I am aware that to pass down props to my Routes I need to use the render. For example:
const BaseRouter = (props) => (
<div>
{console.log(props)} // this displays everything I need, including props.username
<Route exact path='/room/' render={(props) => <Room {...props} />} />
</div>
);
The problem is that inside my Room component, the only props passed down are history, location, and match (these come straight from react-router-dom).
I have also tried manually passing the props needed down:
<Route exact path='/room/' render={(props) => <Room {...props} username={props.username} />} />
But inside Room, I still get undefined for props.username. Any ideas?
You need to give a different name to you variables. Doing render={(props) => <Room {...props} /> will always pass the router props, that is why props.username is undefined.
const BaseRouter = (props) => (
<div>
<Route exact path='/room/' render={(routeProps) => <Room {...routeProps} {...props} />} />
</div>
);
Route component takes children as well.
const BaseRouter = (props) => (
<div>
{console.log(props)} // this displays everything I need, including props.username
<Route exact path='/room/'>
<Room {...props}/>
</Route>
</div>
);
In Room component you can use useHistory, useLocation and useRouteMatch hooks. If Room is class based component then withRouter HOC is also useful.

Nested routes and Switch - how to pass props.match.params.id?

I have been trying to understand nested routes and switch in the React v4 Router.
Consider the main router looks like this (simplified):
<Switch>
<Route path="/" component={LoginPage} exact={true} />
<Route path="/dashboard/edit/:id" component={DashboardPage} />
<Route path="/dashboard" component={DashboardPage} />
</Switch>
The "dashboard" component renders the sub-route:
render(){
return (
<div className="note">
<Route to='/edit/:id' render={(props) =>
<div>
<NoteList {...props} />
<EditNotePage {...props} />
</div>
} />
</div>
)
}
The "EditNotePage" component can access the param by:
const mapStateToProps = (state, props) => ({
note: state.notes.find((note) => note.id === props.match.params.id
});
Is this the correct approach?
It seems a little redundant to specify "/dashboard/edit/:id" twice ( ? )
Once in main router and the again in the dashboard component.
However, if I do not match the route in the main router "Switch" the "props.match.params.id" is not accessible since "props.match" will only point to "/dashboard" .
Have I missed something crucial regarding how the React v4 Router works? :)
Kind regards
Kermit
Nope, didn't miss anything. That's how react router v4 works. You define full routes. The trick you can use is that you can grab the current path and prepend it to your "nested path".

How to pass props to nested routes in React Router 4?

I have a component tree like this:
-App
--UserList
---UserItem
---UserItem
---UserItem
--User
I'm not being able to pass user data from UserItem to User. This is what I have:
App.js
export default class App extends Component {
state = { users: [] }
componentDidMount() {// fetch and setState}
render() {
return (
<BrowserRouter>
<Route
exact
path="/"
render={() => <UserList users={this.state.users} />}
/>
</BrowserRouter>
)
}
}
UserList.js
export default function({ users }) {
return (
<div>
{users.map(user => (
<UserItem user={user} key={`${user.id}`} />
))}
</div>
)
}
This is where the problem is: I want to pass the data from the parent component to the child User component, instead of having to fetch the user data from the API again.
UserItem.js
export default function({ user }) {
return (
<div>
<Link to="/user">{user.name}</Link>
<Route path={`/user/${user.name}`} render={() => <User user={user} />} />
</div>
)
}
I'm not sure what you're trying to implement here. Your app renders the UserList when then route is /. The UserList renders a UserItem component for each user in the array. Each UserItem simply renders a route specific to every user, which will render the User component if that route is triggered.
But if I'm not mistaken, the UserList will not be rendered if the route is anything but /, so if someone accesses user/..., the inner routes won't actually exist.
Essentially, this app will not render anything.
If you remove the exact keyword from the route in App, I think you'll get the result you are looking for. In this case, opening /user/<USER_NAME> will render the User element for that user.
Your question is regarding passing props into a component through a route, and the mechanism you've used is correct.
<Route path={...} render={() => <User user={user} />} />
This is actually right. See the code linked below. On changing the route to /user/User1, you'll see the name of "User1" rendered in the app.
See the working code here: https://codesandbox.io/s/18w3393767
You should use this.props.users in the UserItem component
i'm not sure but could you pass props like below, here i pass props to render and then to User Component
<Route path={`/user/${user.name}`} render={(props) => <User user={user} {...props} />} />
export default function({ users }) {
return (
<div>
{ this.props.users.map(user => (
//mistake here this.props.users.map not users.map
<UserItem user={user} key={`${user.id}`} />
))}
</div>
)
}

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

Categories