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
Related
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!
return (
<>
Some jsx..
<Route
path={modal.info ? `/fullInfo/${this.state.modal.id}`:`/preview/${this.state.modal.id}`}
element={({match}) => {
return (
<ModalWindow
modalVisible={Boolean(match)}
onCloseWindow={this.onCloseWindow}
modalContent={modal}
/>
)
}}
/>
</>
)
If I do that I get an error like: Route tag must be wrapped by Routes tag. I did this feature in a old version of react-router-dom but when I try to do it in the new one there is err..
In your App.js file, wrap the entire app with <BrowserRouter>
const AppWithRouter = () => <BrowserRouter><App /></BrowserRouter>
export default AppWithRouter
And then wrap all your routes in the new <Routes> tag (replaces switch):
<Routes>
<Route path="..." element={...} />
</Routes>
The Routes component effectively replaced the Switch component from v5, and it's required to wrap any Route components. Additionally, the Route components no longer take component, and render and children prop functions, the routed components must use the element prop that takes a ReactElement, a.k.a. JSX.
Wrap the Route component in a Routes component and since all routes are always exactly matched, just render the ModalWindow with the modalVisible prop set to true.
return (
<>
...Some jsx...
<Routes>
<Route
path={modal.info
? `/fullInfo/${this.state.modal.id}`
:`/preview/${this.state.modal.id}`
}
element={(
<ModalWindow
modalVisible
onCloseWindow={this.onCloseWindow}
modalContent={modal}
/>
)}
/>
</Routes>
</>
)
Setup
I have an App component rendering following routes:
<Route path="/items/:id" component={ItemDetail} />
<Route path="/items" component={AllItems} />
In my AllItems component I render a list of all items and the option to create a new item or update an existing one. Doing either one of those actions opens a popup. To do this I render following routes in AllItems:
<Route path="/items/add" component={AddItemModal} />
<Route path="/items/edit" component={EditItemModal} />
Note: It's important that these modals are actually linked to these routes, I can't change that. Neither can I render those routes outside of AllItems as I need to pass soms props to the modals.
Problem
When I go to a route like /items/1: ItemDetail renders (as expected).
When I go to /items/add: ItemDetail renders with add as :id.
I need it to render AddItemModal here as defined in AllItems.
What I tried:
I tried adding exact to the /items/:id route and I also tried adding it to /items/add & /items/edit. Neither of those solutions worked. Either only ItemDetail rendered, or only the modals.
I tried defining /items before /items/:id to hopefully give higher priority to the nested routes. ItemDetail never rendered in this case.
Is there a solution to this so I can prioritise items/add & items/edit over items/:id
Try nesting the routes under /items
<Route
path="/items"
render={() => (
<>
<Route path="" component={AllItems} exact />
<Route path="/add" component={AddItemModal} />
<Route path="/edit" component={EditItemModal} />
<Route path="/:id" component={ItemDetail} />
</>
)}
/>
If you want to have an independent views for ItemDetail and AllItems but at the same time have /items/add and /items/:id/edit (took a little liberty with the url, you need and id to edit an item right?) as modals over AllItems so the structure of the routes would be something like this:
AllItemsView (/items)
AddItemModal (/items/new)
EditItemModal (/items/:id/edit)
ItemDetailView (/items/:id)
You need a little tweak of Tnc Andrei response:
<Route
path="/items"
render={({ match: {url, isExact}, location: {pathname} }) => {
let pathnameArray = pathname.split("/")
let lastChunk = pathnameArray[pathnameArray.length - 1]
if (isExact || lastChunk === "new" || lastChunk === "edit") {
return (
<>
<Route path={`${url}/`} component={CompetitionsView} />
<Switch>
<Route path={`${url}/new`} component={CompetitionFormModal} />
<Route path={`${url}/:competitionId/edit`} component={CompetitionFormModal} />
</Switch>
</>
)
}
return (
<>
<Route path={`${url}/:competitionId`} component={CompetitionView} />
</>
)
}}
/>
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".
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>
)
}