Pathname conditional rendering in React - javascript

How can I conditionally add a component to the render on certain pathname?
Using const pathname = this.props.location.pathname I identify the current pathname
Then I get the slugs of the pages I want to identify using
const specialPages = get(this.props, 'data.allContentfulSpecialPages.edges')
I then organise the returned data by
const special = specialPages.map(({ node: page })=> (
`/parent/${page.slug}`
))
and this returns the page slugs as
["/parent/page1", "/parent/page2", "/parent/page3", "/parent/page4", "/parent/page5", "/parent/page6"]
All seems good to now but when I try to add add
let PageHeader;
if (pathname !== special) {
PageHeader =
<Header/>;
} else {
PageHeader = null
}
it doesn't do remove the <Header/> for pathname identified in special
Have I not correctly defined each in the array?
Edit - I have just noticed the issue but unsure of the fix.
const special is returning as /parent/page1/parent/page2/parent/page3/parent/page4/parent/page5/parent/page6
When adding to console.log(special) I receive
0:"/parent/page1"
1:"/parent/page2"
2:"/parent/page3"
3:"/parent/page4"
4:"/parent/page5"
5:"/parent/page6"
length:6
__proto__: Array(0)
So I believe I need to map these differently.

If I'm understanding correctly, you want to conditionally add a component to the render based on pathname. A common pattern I've seen for this is to use the and operator with the condition you're looking to check and the component to render if need be. Something like this.
{{ pathname !== special && <Header/> }}
This will not display anything if the condition is not true and will display Header component if condition is true.

Related

Get Component name in React After Webpack Minify()

I'm new to react, and just pushed our app to a live environment. I've just encountered this error which stackoverflow doesnt seem to have a direct answer for, so i'm creating a new question for it.
I'm using ReactJS and NextJS 12. In my _app.js I have an array inside render():
let pagesThatDontNeedHeader = [
'RegisterNow',
'Signin',
'LoginError',
'CreateNewPassword',
];
let componentName = Component.name
if(componentName === 'WithRouterWrapper') {
componentName = Component.displayName
}
Then below when I render the page:
{pagesThatDontNeedHeader.includes(componentName) ? null : <Header />}
So now pages like Signin, Register and Create new password dont show the header, and just the form. Now, in production the names have all been minified and I have no way to compare and figure out whether the component needs a header or not.
What is the correct answer for this?

Impossible to get part of URL with react router?

How can I always get the same part of the URL in react?
example:
http://localhost:3000/supplier/924511e8-9056-4c1e-9976-625bf042924e
I only want "supplier", but this can be anything else. So it's possible for it to be:
http://localhost:3000/product/924511e8-9056-4c1e-9976-625bf042924e
Then I want "product"
But it can also be just http://localhost:3000/supplier/ also in this case I only want the supplier. And this can be anything.
How do I do this? If I've already tried it with pathname.slice(0, pathname.indexOf("/") but this doesn't seem to work.
So I only want the string after the http://localhost:3000/want this/ no matter if there is anything after it or not.
You can use the split method as below:
const url = 'http://localhost:3000/supplier/'
const want_this = url.split('/')[3]
Just use useParams from react router dom
import {useParams} from "react-router-dom";
function Child() {
// We can use the `useParams` hook here to access
// the dynamic pieces of the URL.
let { id } = useParams();
return (
<div>
<h3>ID: {id}</h3>
</div>
);
}

Conditional display of component based on Route matching

I am looking to conditionally render a component based on the route (using React Router), and the component should return null if it matches any path pre-defined in an array or some sort of similar data structure, where I do not have to be reliant on a <Switch>/<Route> setup. Currently here is what I have but it is clearly inefficient and not robust at all.
const Component = (props) => {
const path = props.location.pathname;
const paths_to_hide = ["/path/to/something", "/path/to/A", "/path/to/B"];
if (paths_to_hide.indexOf(path) != -1) return null;
return (
<div>test</div>
);
}
For example, if I want to match the following paths:
/path/to/something
/path/to/something/<any path that follows after this>
/path/<random string>/fixed
/newPath/<random string>
Note that this list is not just limited to 4 items, which is why I'm trying to stray away from having inline <Route> matching as I'm looking for a more scalable approach which I can save in a config file and have imported as an array or some similar data structure.
Currently my implementation will only be able to identify the first item, and there is no way to match the subsequent items, using the indexOf() function. What would be the best way to accomplish this? Any help is appreciated, thank you!
So upon reading the React Router docs further, I found this. This is definitely the most ideal solution and I overlooked this initially.
const Component = (props) => {
const path = props.location.pathname;
const paths_to_hide = ["/path/to/something", "/path/to/A", "/path/to/B"];
return (
<Switch>
<Route path={paths_to_hide}>
</Route>
<Route>
<div>test</div>
</Route>
</Switch>
);
}
So now I can create complex paths and don't have to loop through an array to match them, as it's taken care of by the Route component, and this is ideal because now I can import this array from a config file instead.

Required Props and existence checks

I see that my code breaks even though prop list is required.
So, should I check for the existence of list before mapping it as I'm doing below?
class Cart extends React.Component {
render() {
const { list } = this.props
return { list && list.map(e => <div> {e} </div>) }
}
}
Cart.propTypes = {
list: PropTypes.array.isRequired
}
UPDATE:
I see suggestions advising to add a default value.
Does it make sense though to both have isRequired and default value set?
Isn't it implied that if a value is required then it should always exists?
But the component seems to mount even though some required props are not satisfied.
So I guess setting default value makes sense, but so isRequire is only a flag for the developer, nothing more, Correct?
Yes, I think you should.
Other developers can still explicitly pass null to the list prop.
<Cart list={null}/>
Or ... a more real-life example:
// getListFromServer() <-- can return null
<Cart list={getListFromServer()}/>
You should use PropTypes which is imported from prop-types:
import PropTypes from 'prop-types'
Cart.propTypes = {
list: PropTypes.array.isRequired
}
So, should I check for the existence of list before mapping it as I'm doing below?
return { list && list.map(e => {e} ) }
Yes, you should check it. Because until your component is being rendered, the list may be undefined or null. And using map on undefined or null will throw you an error. When your component gets list data then your use of map will be correct usage.
It would even be better to check its length:
return { list && list.length && list.map(e => <div> {e} </div>) }
I would also suggest you to use defaultProps:
Cart.defaultProps = {
list: [] // or list: ['my','default','props']
}
If you use the default props, then you don't need to worry about checking it before using map. This will ensure you to map on array.
But what if user pass the props other than array?
Even in this case, user is notified through PropTypes.array.isRequired. So checking list before using map in this case is not necessary.
I think that depends on your way of programming, It's very subjective.
Some people prefer to have the responsibility on the caller to provide the right value, some prefer to be more defensive and check for all the possible values.
I prefer to have the caller provide the right value, otherwise, why have propTypes in the first place, it almost becomes useless.
Now if you can't control how your component will be called, then Yes check that the right value is passed.
I would do null checks when doing some side effects, like doing an Ajax call where I can't really control the result.
At the end, you need to do types/value checks in your program, the question is where do you do it, everywhere or where it matters.
Could you post the code where you are passing the list to the cart component.
If nothing works you can always try this
Cart.defaultProps = {
list: []
}
Although I would suggest to fix the underlying problem of why the code is crashing, could you provide an error log as well.
Yes you should check whether it has an array or not because your Cart component wants list as an array always and list should not be empty array and only then do map or do that check in parent component itself before passing List props to Cart so that you no need to check again in Cart component you can directly do map
class Cart extends React.Component {
render() {
const { list } = this.props; //should be inside render
return (list && list.length>0 && list.map(e => <div> {e} </div>)
}
}
Better keep your list as empty array in your parent component like for eg: this.state={list:[]} so that you no need to check whether it is undefined or null. You can just check the length of the array and do map

react - render dynamic component insider render function of other component

I am trying to develop a web app using react and i have a issue.
my component get a 'exists component name' and I try to render this new component inside render function of the current component.
my current component render function
render(){
let Xxx = null;
if( this.props.onHex ){
console.log( this.props.onHex );
Xxx = <this.props.onHex />
}
return(
<div className="myClass">
<div className="anotherClass">
{Xxx}
</div>
</div>
);
}
}
it not works for me, the console log returns the name of the new component "Unit". when I replace the Xxx = <this.props.onHex /> with this Xxx = <Unit /> it works and render the Unit's render function.
it looks like react not recognise <Unit/> as component.
what I am doing wrong please advise.
my Unit code:
export default class Unit extends Component{
render(){
return(
<div>test</div>
);
}
}
UPDATE:
when I use const XxxName = Unit; Xxx = <XxxName />; it works for me but I want to be able to render the component from string ( I got this string from json ).
I guess I can create all my possible components at this situation inside a file load them into array or something and get them by string, but it's not something that can live with I have a lot of components maybe if I will some how load them from separate folder ( individual file for each component ) it will be half solution. but I still looking how to load component from string.
jsFiddle with another similar issue http://jsfiddle.net/dhjxu5oL/
UPDATE 2:
I am not found elegant way to reach my goal (I don't sure if it exists) for now I am using method for each dynamic component for hope that someone will advise me with more elegant solution. check it: React / JSX Dynamic Component Name
newExampleComponent() {
return <ExampleComponent />;
}
newComponent(type) {
return this["new" + type + "Component"]();
}
let Xxx = null;
if( this.props.onHex ){
const XxxName = this.props.onHex;
Xxx = <XxxName />;
}
Check this jsfiddle for example
UPDATE:
According to React official docs
You cannot use a general expression as the React element type. If you
do want to use a general expression to indicate the type of the
element, just assign it to a capitalized variable first. This often
comes up when you want to render a different component based on a
prop:
So you need to assign this.props.onHex to a CAPITALIZED variable first then you should be able to use it.
UPDATE again
Seems you want to pass a string, not a reference to the component. There is a dirty way to do that
const xxx = this.props.onHex || "";
const XxxComp = eval(xxx);
...
return (<XxxComp />);
I created this codepen for testing

Categories