This question focuses on the Header component: So in my app. I have a something that looks a bit like this with a few more components and plenty more routes:
return (
<SiteLayoutContext.Provider value={siteConfiguration} >
<div className="topContainer">
<BrowserRouter>
<div className="header">
<Header/>
</div>
<div className="mainWrapperContainer">
<div className="contentWrapper">
<Route exact path='/login' component={Login}/>
<Route exact path='/home' component={Home}/>
<Route exact path='/about' component={About}/>
<Route exact path='/collection/:collectionName' component={CollectionLandingPage}/>
<Route exact path='collection/:collectionAlias/id/:itemId' component={ItemView}/>
</div>
<Footer/>
</div>
</BrowserRouter>
</div>
</SiteLayoutContext.Provider>
)
What I'd like to accomplish:
I can currently set a logo for the header. This is great, but I'd like the user to be able to select a custom logo, one that will replace the global default, if they are within a collection.
To define 'within' a collection, the route will start with /collection/ so in the example below, both the ItemView and the CollectionLandingPage should have this custom logo.
I need to be able to tell my header that it's currently within a collection, and I want to limit renders and redraws to improve performance. I have an api endpoint that I fetch to grab collection info, which would tell me if a custom logo has been set, and what the href should be.
What I've tried: My original thought was to rip the header out of the main return() shown above, and instead place it within every component, that way I can use React's useLocation() hook to check the the url, and only fetch the collection's logo if1) we're within a collection and 2) a custom logo has been set.
My issue with this method is that I now have to enter the Header within every single component, instead of how it is currently implemented.
My second thought is to wrap it all within some kind of context, consume it in every component, and update it based on the url, which would then fetch the relevant info, but this just feels off. I suppose I could fetch the information for the collection and update a new CollectionInfoContext every time I hit /collection/:collectionName which would only update when the collectionName changes?
I'm kind of just looking for suggestions on how to implement this because well... I feel like there's an option I haven't listed that's probably much easier and more reasonable... as is the stackoverflow standard.
Disclaimer: I'm a react context api novice
If you're not using Redux or another state management solution, a context is definitely a reasonable way to share data across your application, UI themes are actually one of the recommended use cases in the React docs.
The only issue I could see is that changing the state for only certain components would require a way of resetting the context for other places where you just want a standard logo. But reading the URL name could solve that by simply storing the path name and using it inside of Header to render the custom logo if the path includes collections. If Header can read the path name all on its own, you may not even need a context if that's the only condition that's dynamic everywhere.
Related
I'm new to React, and have a n00b question and Googling hasn't turned up any good answers, maybe someone here knows:
With React Router or any kind of declarative routing, how do you "hide" private URLs that you don't want every user to know about? Or, if you just want to prevent someone from being able to easily enumerate all valid URLs?
Code splitting doesn't solve this (really) because the user is told what to request to get the rest of the information: I suppose you could put that URL behind authentication or some kind of restriction to prevent public access to it, but won't that cause the JS fetching the code-split import to receive an error, causing that error to bubble up and cause unintended failure behavior?
Edited to add:
The crux of my issue is wanting to essentially do <Route path="/:path"> where :path matches the entire URL path (e.g., could be foo/bar/baz) instead of just the first "part" (e.g., foo) for a URL like https://example.com/foo/bar/baz for example.
I could use <Route path="*"> and do my own URL parsing, but what if I don't want that route to actually handle the request? How do I signal "nope, I don't want this request, fall-through to the next matching Route"?
You can check if the user is authorized to see these routes and then show or hide them.
<Route path="/cities" component={City} />
<Route path="/courses" component={Courses} />
{user.hasPermission && (
<Route path="/secret" component={VerySecretPage} />
)}
If you want to go further, you can create a custom guarded route as described in this blog post.
I am having a Server Side Rendered React app where I use HashRouter for react routing(v5). My Routes look like this
<HashRouter basename="/">
<Layout {...config} />
</HashRouter>
<Switch>
<Route exact={true} path="/" component={LPComp} />
<Route exact={true} path={this.props.siteBanner.Red} component={bannerPage} />
</Switch>
When I hit localhost:8888/parent/ and once it get loaded and if I hit localhost:8888/parent/banner in the same window the bannerPage component renders fine.
But I hit localhost:8888/parent/banner directly(consider in a new tab), then the component is not rendered properly.
Any ideas why this is happening?
Also to add on when I hit localhost:8888/parent/banner I can see LPComp(default route) also being loaded and then it disappears suddenly and the bannerPage component renders improperly.
Thanks in Advance
The behavior is making sense, since your routes is based on a prop/state this.props.siteBanner.Red. So the first thing is to put a console.log once you enter this component.
console.log(this.props.siteBanner.Red)
In your first case, you reach this component from its parent, this way the props mostly likely is resolved.
In your second case, you reach it directly, of course also from its parent, but most likely there's no time for the props to get resolved quickly. Couple of possibilities
useEffect is to update this variable
mouse click is required to get a value
setTimeout is used to defer
callback is used to get this variable
You can say there's 50ms delay in getting this prop resolved, but you need to dig out why yourself. Dynamic route is more advanced thing, it's easy to have permanent route.
Thank you all for your timely response, I was able to solve that issue by setting location=(req.url) from the server side configured static router. Going through this example I got the bigger picture I was missing.
https://medium.com/javascript-in-plain-english/server-side-rendering-in-react-redux-ab0af31c9c4b
I have a filter system for my products in ReactJS which basically has the following:
http://localhost:3000/category/women/subcategory/tops/sorting/null/price/null/size/null/color/null/brands/null/merchants/null
The Route is as follows:
<Router>
<Route path="/category/:cat/subcategory/:subCat/sorting/:sorting/price/:price/size/:size/color/:color/brands/:brands/merchants/:merchants" component={Products} />
</Router>
The Problem is that I want to show filters in the URL in when they have a value other than null. Current my component works but I have to display every single filter in the URL with a null value by default, this is causing my URL to be extremely long. The only way I thought possible was to do a permutation combination of all the possible URLs in the filter and direct them all to { Products } which is extremely silly. There must be something in the Router component that I'm missing?
You need to use optional params in this case.
As and example if you want to accept both sorting/ascending/price and sorting/price you can write your path as follows assuming you use react router v4.
<Router>
<Route path="sorting/:sort?/price" component={Products} />
</Router>
You can read more about this here: React Router with optional path parameter
I want to have a 'dynamic route' for a section of my app, which was done like this:
<Route path="path(/:id)" component={Component} />
So far this works, but in Component I would like to access the value of id because it changes a bunch of things depending on which it is. How can I do that?
In your component, you would access this via props.
this.props.params.id
Here is the guide from react-router that goes into more detail as well.
https://github.com/reactjs/react-router-tutorial/tree/master/lessons/06-params
I have four components: Panel, PanelHeader, PanelBody, PanelFooter they are composed as following:
<Panel>
<PanelHeader>
some title
</PanelHeader>
<PanelBody>
<div>
main content
</div>
</PanelBody>
<PanelFooter>
footer content
</PanelFooter>
</Panel>
I also have multiple routes, where I want to see that Panel, but with different content.
An obvious solution is to create components for each of routes and each of those components will have their own Panel.
But in my case I want to add some morphing animation between routes (change the height of panel, slide body, fade footer etc.). To achieve this I need to render all the contents to exactly the same Panel, PanelHeader, PanelBody, PanelFooter instances, so the solution above won't work.
So I need to split my route component into three components for each PanelXXX and the problem is that those components need to react on events of each other.
There are the following points so far:
I can't create another one mediator component to control those three partial components, because I will not be able to access its API till it will be rendered (so I can't implement getter methods e.g. getHeader()).
I can connect them all to redux, but it seams too complicated.
Another one solution is to create non-React object to share the state, but in this case I will need to clean up after this object to overcome memory leaks. The connection with redux will also be tricky. This approach looks not so clean to me.
I can also cheat with transparent background, so that I can have one Panel but multiple PanelHeaders etc. But in this case I will need to spread the knowledge about animation to all the child components.
Does anybody knows any other approach to do that in React? Or should I choose one from the points above?
I think that the scenario you are describing and the questions you pose are the exact rationalizations for using Flux. Redux is one option. We used a plain vanilla Flux implementation using the original Facebook library and we have had great success. It has a steep learning curve, but once you get your head around it, issues like the one you are facing just fit right in to the infrastructure.
Sounds like you could benefit from using this.props.children.
Router
<Router history={createBrowserHistory()}>
<Route path="/" component={App}>
<IndexRoute component={DefaultView} />
<Route path="foopath" component={FooView} />
<Route path="barpath" component={BarView} />
</Route>
</Router>
App.js
...
render: function() {
return (
<Panel>
{this.props.children}
</Panel>
);
}
...
DefaultView.js
render: function() {
<div>
<PanelHeader>
FooHeader
</PanelHeader>
<PanelBody>
FooContent
</PanelBody>
<PanelFooter>
FooFooter
</PanelFooter>
</div>
}
You can read more about React's children here.