Conditional rendering on React routes - javascript

I have several routes where different components are being rendered depending on the path as the following in my App.js
<Route
path="/spaces"
render={({ match: { url } }) => (
<>
<Route path={[`${url}`, `${url}/members`, `${url}/about`, `${url}/admin`]} component={Spaces} exact />
</>
)}
/>
<Route
path="/profile"
render={({ match: { url } }) => (
<>
<Route path={[`${url}`, `${url}/comments`, `${url}/spaces`, `${url}/cloud`]} component={Spaces} exact />
</>
)}
/>
In my Space.js component I have created a conditional rendering depending on the url, so when the user is in for example /spaces/members or /spaces/members/ a component will be rendered etc ..
function Spaces({ match }) {
let path = `${match.url}`;
let Content;
let admin;
let profile;
if (path == '/spaces/members/' || path == '/spaces/members') {
Content = <SpaceMembers />;
} else if (path == '/spaces/' || path == '/spaces') {
Content = <SpaceContent />;
} else if (path == '/spaces/about' || path == '/spaces/about/') {
Content = <SpaceAbout />;
} else if (path == '/spaces/admin' || path == '/spaces/admin/') {
admin = true;
Content = <SpaceAdmin />;
} else if (path == '/profile' || path == '/profile/') {
profile = true;
typeContent = <ProfileContent />
} else if (path == '/profile/comments' || path == '/profile/comments') {
profile = true;
Content = <ProfileSpace />
}
return (
<div>
<div className='page-content' style={{ maxWidth: !admin ? '980px': '800px'}}>
<div className='profile-page tx-13'>
{(!profile || admin) ? <Cover admin={admin} profile={profile}/> : <CoverProfile admin={admin} profile={profile}/> }
{Content}
</div>
</div>
</div>
);
}
export default Spaces;
For now the conditional rendering is working perfectly. But as a newbie in React my question is, is this the right thing to do or is there any other better method ?
For now I'm afraid that I will need to add a custom /:id later to the paths and the conditions might not work.

I suggest you to create a config file where you declare all the routes available to you and after that, create a routes file and import all the paths from path.js and create a list containing objects having all the props of which are - exact,component,path. In path,s value, call the paths you've imported from paths.js. what it will do is, it creates a good structure for you to deal with routing in general

Related

Render a component in jsx if a certain condition is met

Really new to react, I have this part of navigation in my Navbar.js using react-router-dom useLocation hook, I am able to get the active path I have to views and I want to render custom text when a user arrives at the view with JSX something like
if(path==="login" || path==="signin"){
`<h1>welcome</h1> ${userName}!`
}
How can I check if condition A or B is met then render the appropriate text using jsx
You must explicitly tell what to return, in your example you do not return anything. So, You can do either :
if (path === "login" || path === "signin") {
return (
<>
<h1>welcome</h1> {userName}
</>
);
} else {
return null;
}
Or use operator && to conditionally render.
{
(path === "login" || path === "signin") && (
<>
<h1>welcome</h1> {username}
</>
);
}
Read more about conditional rendering [here]
You need to:
Return the value
Use JSX and not a string
Put everything in a container such as a React Fragment
Such:
if(path==="login" || path==="signin") {
return <>
<h1>Welcome</h1>
{userName}!
</>;
}
… but that logic should probably be handled by your routes rather than an if statement.
You can use the ternary operator for conditional rendering in JSX like
{path==="login" || path==="signin" ? <> <h1>welcome</h1> {userName} </> : null}
Simple :-)

JSX string interpolation into a component name

I have this code...
import React from 'react';
import Fade from 'react-reveal/Fade';
import { Novice } from '../../../Icons/Novice';
import { Apprentice } from '../../../Icons/Apprentice';
import { Explorer } from '../../../Icons/Explorer';
import { Advocate } from '../../../Icons/Advocate';
import { Profesional } from '../../../Icons/Profesional';
import { Ambassador } from '../../../Icons/Ambassador';
export const ProfileType = (props) => {
const {
label = 'Apprentice',
} = props;
return (
<Fade top delay={2100}>
<div className="profile-card__work-type">
{
/* {label === 'Novice' && <Novice />}
{label === 'Apprentice' && <Apprentice />}
{label === 'Explorer' && <Explorer />}
{label === 'Advocate' && <Advocate />}
{label === 'Profesional' && <Profesional />}
{label === 'Ambassador' && <Ambassador />} */
}
{ `< ${label} />` }
<span className="profile-card__work-type-text">
{' '}
{label}
</span>
</div>
</Fade>
);
};
I want to use the 'label' variable as the name of my component and I'm trying to use string interpolation to use it like this { `< ${label} />` } but it just prints out the string < Novice/> to screen.
How can use the label varible as the name of the component instead of the several lines of conditionals?
Best wishes,
Nicolas
Use an object to store your components...
const obj = {
Novice: <Novice />,
Apprentice: <Apprentice />,
Explorer: <Explorer />,
Advocate: <Advocate />,
Profesional: <Profesional />,
Ambassador: <Ambassador />
};
And then access the object using the label.
{obj[label]}
You can't just put < ${label} /> and expect it to be evaluated as a Component if your really want to do this you have to use the dangerouslySetInnerHTML prop on a <div/> or something and give it a valid html String. But as the name of the prop suggests its dangerous given the possibility of remote code execution attacks.
Another way of going about this and probably the best way, like the other Answer suggests is putting your Components already in an Array or as properties of an Object and accessing them through the index of the Component in the Array or the key of the Object property.

Nextjs Warning: Expected server HTML to contain a matching <div> in <div> when using conditional rendering

There are similar questions posed here and on Google, but none with answers that fit my scenario.
Basically, I want to display a different searchbar in the header, depending on what page I am on. This is nextjs.
But when reloading the page, I get the error in console:
Warning: Expected server HTML to contain a matching <div> in <div>
First thing I tried was
const currentPath = Router.router?.route;
return (
<div className="sub-bar">
{currentPath === '/products' && (
<Search />
)}
{currentPath === '/baseballcards' && (
<SearchBaseballCards />
)}
</div>
);
That generates the error when reloading the page, even if I comment either of them out.
Next thing I tried was the ternary route:
<div className="sub-bar">
{currentPath === '/baseballcards' ? <SearchBaseballCards /> : <Search />}
</div>
This actually worked but ternary is no good because I only want the search bar on the /products and /baseballcards pages.
Final thing I tried was:
const currentPath = Router.router?.route;
let searchbar;
if (currentPath === '/baseballcards') {
searchbar = <SearchBaseballCards />
}
else if (currentPath === '/products') {
searchbar = <Search />
}
else {
searchbar = null;
}
return (
<div className="sub-bar">
{searchbar}
</div>
);
This gives me the original error when reloading the page, so back to square one.
https://reactjs.org/docs/conditional-rendering.html
Rather than accessing the Router object directly with Router.router?.route, use the router instance returned by the useRouter hook. This will ensure that the rendering between server and client is consistent.
import { useRouter } from 'next/router';
const SearchBar = () => {
const router = useRouter();
const currentPath = router.asPath;
return (
<div className="sub-bar">
{currentPath === '/products' && (
<Search />
)}
{currentPath === '/baseballcards' && (
<SearchBaseballCards />
)}
</div>
);
};

React - Operator '<' cannot be applied to types 'void' and 'number'

I have a demo available on StackBlitz. This is very pseudo code and my actual code is a lot more complicated.
I have three components loaded into index.tsx. Each component has a conditional that checks the boolean above. My actual code checks data from a GraphQL call.
If the component is shown I want to store the text. I'm trying to do this with useState. So, if the About section is shown add 'About' to the nav state.
I'm getting the error:
Operator '<' cannot be applied to types 'void' and 'number'
Can anyone explain this error, is it possible to do this?
import React, { useState } from "react";
import { render } from "react-dom";
import About from "./About";
import Products from "./Products";
import Contact from "./Contact";
import "./style.css";
const App = () => {
const [nav, setNav] = useState([])
const about = true;
const products = true;
const contact = true;
return (
<div>
{about && (
setNav([...nav, 'About'])
<About />
)}
{products && (
setNav([...nav, 'Products'])
<Products />
)}
{contact && (
setNav([...nav, 'Contact'])
<Contact />)}
</div>
);
};
render(<App />, document.getElementById("root"));
I'm not sure if I understand your question correctly. If your code checks data from a GraphQL call, you should update the nav state from there (right after you received the response) and display the components based on that state, not the other way around.
You can use the useEffect hook to grab the response and update your state. I would also use an object instead of an array to store the nav state (it's easier to update its values).
function App() {
const [nav, setNav] = React.useState({
about: false,
contact: false,
products: false,
});
React.useEffect(() => {
// make the graphQL call here and based on the response update the state:
const graphqlResponse = {
about: true,
contact: false,
products: true,
// ... others
}
setNav({
about: graphqlResponse.about,
contact: graphqlResponse.contact,
products: graphqlResponse.products,
});
}, []);
return (
<div>
{nav.about && <About />}
{nav.products && <Products />}
{nav.contact && <Contact />}
<pre>{JSON.stringify({nav}, null, 2)}</pre>
</div>
);
};
function About() { return <h2>About</h2> }
function Contact() { return <h2>Contact</h2> }
function Products() { return <h2>Products</h2> }
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
This should work:
{ about && setNav(["About", ...nav]) && <About /> }
{ products && setNav(["Product", ...nav]) && <Products /> }
{ contact && setNav(["Contact", ...nav]) && <Contact /> }

Detect change in query param react-router-dom v4.x and re-render component

I am not really sure why its showing the default route once I did a query param change. Is there a better approach for this kind of issue? maybe I shouldn't be using query param? Open to get educated!
Version
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2",
Test Case
https://codepen.io/adamchenwei/pen/YeJBxY?editors=0011
Steps to reproduce
Click on Home -> Step 1
Expected Behavior
Go to Step 1 as well as Step 2 render correct dom
Actual Behavior
Empty, nothing renders on the page
// For this demo, we are using the UMD build of react-router-dom
const {
BrowserRouter,
Switch,
Route,
Link
} = ReactRouterDOM
const {
Component,
} = React;
const Router = BrowserRouter;
const Step1View = () => (
<div>
<h1> Step 1</h1>
</div>
)
const Step2View = () => (
<div>
<h1> Step 2</h1>
</div>
)
class Home extends Component {
constructor(props) {
super(props);
console.log('-!!!')
this.state = {
step: 1,
}
this.next = this.next.bind(this);
}
next(stepNumber=1) {
this.props.history.push({
pathname: `/adamchenwei/pen/YeJBxY?editors=0011/?step=${stepNumber}`,
});
const query = this.props.history.location.pathname;
console.log('---aaaaa');
console.log(query);
if (query === '/adamchenwei/pen/YeJBxY?editors=0011/?step=1') {
this.setState({
step: 1,
})
} else if (query === '/adamchenwei/pen/YeJBxY?editors=0011/?step=2') {
this.setState({
step: 2,
})
}
}
render() {
console.log('render!!!');
console.log(this);
const {
step
} = this.state;
console.log('---step');
console.log(step);
return(
<div>
<h1>Welcome to the Tornadoes Website!</h1>
<button onClick={()=> this.next(1)} > Step 1</button>
<button onClick={()=> this.next(2)} > Step 2</button>
{
step === 1 ? <h1>Step 1 here</h1> : null
}
{
step === 2 ? <h1>Step 2 here</h1> : null
}
</div>
);
}
}
// The Main component renders one of the three provided
// Routes (provided that one matches). Both the /roster
// and /schedule routes will match any pathname that starts
// with /roster or /schedule. The / route will only match
// when the pathname is exactly the string "/"
const Main = () => (
<main>
<Route exact path='/' component={Home}/>
</main>
)
// The Header creates links that can be used to navigate
// between routes.
const Header = () => (
<header>
<nav>
<ul>
<li><Link to='/'>Home</Link></li>
<li><Link to='/roster'>Roster</Link></li>
<li><Link to='/schedule'>Schedule</Link></li>
</ul>
</nav>
</header>
)
const App = () => (
<div>
<Header />
<Main />
</div>
)
// This demo uses a HashRouter instead of BrowserRouter
// because there is no server to match URLs
ReactDOM.render((
<Router>
<App />
</Router>
), document.getElementById('root'))
React router from v4 onwards no longer gives you the query params in its location object. The reason being
There are a number of popular packages that do query string parsing/stringifying slightly differently, and each of these differences might be the "correct" way for some users and "incorrect" for others. If React Router picked the "right" one, it would only be right for some people. Then, it would need to add a way for other users to substitute in their preferred query parsing package. There is no internal use of the search string by React Router that requires it to parse the key-value pairs, so it doesn't have a need to pick which one of these should be "right".
Having included that, It would just make more sense to just parse location.search in your view components that are expecting a query object.
You can do this generically by overriding the withRouter from react-router like
customWithRouter.js
import { compose, withPropsOnChange } from 'recompose';
import { withRouter } from 'react-router';
import queryString from 'query-string';
const propsWithQuery = withPropsOnChange(
['location', 'match'],
({ location, match }) => {
return {
location: {
...location,
query: queryString.parse(location.search)
},
match
};
}
);
export default compose(withRouter, propsWithQuery)
and wherever you need query string, you could simply use it like
import withRouter from 'path/to/customWithRouter.js';
class Home extends Component {
constructor(props) {
super(props);
console.log('-!!!')
this.state = {
step: 1,
}
this.next = this.next.bind(this);
}
next(stepNumber=1) {
this.props.history.push({
pathname: `/adamchenwei/pen/YeJBxY?editors=0011&step=${stepNumber}`,
});
}
componentDidUpdate(prevProps) { // using componentDidUpdate because componentWillReceiveProps will be renamed to UNSAFE_componentWillReceiveProps from v16.3.0 and later removed
const {query: { step } } = this.props.history.location;
if(!_.isEqual(this.props.history.location.query, prevProps.history.location.query)) {
this.setState({
step
})
}
}
render() {
console.log('render!!!');
console.log(this);
const {
step
} = this.state;
console.log('---step');
console.log(step);
return(
<div>
<h1>Welcome to the Tornadoes Website!</h1>
<button onClick={()=> this.next(1)} > Step 1</button>
<button onClick={()=> this.next(2)} > Step 2</button>
{
step === 1 ? <h1>Step 1 here</h1> : null
}
{
step === 2 ? <h1>Step 2 here</h1> : null
}
</div>
);
}
}
const HomeWithQuery = withRouter(Home);
// A lose explanation. URL = localhost:3000/myRoute/2
<Router history={history}>
<Route path="/myRoute/:id" component={App} />
</Router>
class App extends Component {
render() {
const { id } = this.props.match.params
return (
<div>
{id === 2 ? <div>id is 2</div> : <div>id not 2</div>}
</div>
)
}
}

Categories

Resources