Rendering conditional route, when mapping components - javascript

I have an application, where I map some sample user components. I add some props, and I want to make a conditional nav link, that renders just a simple "profile components that show the name.
So far I have made a conditional nav, link inside the component, and the props get send correctly, and it displays the paragraph under my User component, but I want to make it redirect, so it only shows the Profile component.
Is there a way so it only shows that component. I tried with the switch but I realized, that it only renders the first, route, so everything else, will still be shown...
render() {
let persons= this.state.persons.map((item, index) =>{
return(
<Router>
<User key={index} name={item.name} img={item.img} id={item.id} />
</Router>
)
})
//user component
render(){
console.log(this.props.name)
return(
<Switch>
<div >
<img src={this.props.img} alt="profile" style={{float: 'left'}}>
</img>
<p style={{textAlign: 'center'}}>{this.props.name}</p>
<p>It's here={this.props.loggedInProp}</p>
<Route path="/:username" exact component={ Profile} />
<NavLink to={`/${this.props.name}`}>Click me</NavLink>
</div>
</Switch>
//Profile component
const Profile= ({match}) =>{
return(
<div>
<p>Hello {match.params.username}</p>
</div>
)
}

<Route
exact
path="/profile/view/:username"
render={props => <ProfileView {...props} />}
/>
inside of ProfileView component you could then use this.props.match.params.username to filter your collection of data and display only it's details.
ProfileView component
import React, { Component } from 'react';
export class ProfileView extends Component {
constructor(){
super()
this.state = {
allUsers[{ user1 ... }, {user2 ...}, ...],
selectedUser: {}
}
}
componentDidMount(){
// fetch('/get/users/from/somewhere').then(users => {
// this.setState({allUsers: users}) // Usually you would just pull your one user from the route and then fetch it's single details from a database
// })
this.setState({selectedUser: allUsers.filter(user => user.username === this.props.match.params.username)})
}
render() {
return (
<div>
<em>Do stuff with your this.state.selectedUser... things here</em>
</div>
);
}
}

Related

how to pass data between routes in react

I have a signup page signup.js after successful sign in I need to take those data to another route view sign in details page if I render sign in details then I am viewing it in the same page how to take it to another route ??If I do through window.name after refresh I couldn't view the data[my page layout]
1st page
class Validation extends Component {
constructor(props) {
super(props);
this.validate = this.validate.bind(this);
}
validate(detail,number) {
//i need to take this number to view page
});
}
render() {
return (
<div>
<SignupForm onAddDetail={this.validate} />
</div>);
}
}
export default Validation;
2nd page
class DetailsComponent extends Component {
render() {
let DetailNodes = this.props.details.map(detail =>
(
<Register key={detail.id}
emailId={detail.emailId} password={detail.password}
firstName={detail.firstName} lastName={detail.lastName}
location={detail.location} mobileNumber={detail.mobileNumber}>
</Register>
)
);
return (
<div> <br></br><br></br><br></br><br></br>
{DetailNodes[number-1]}//I need that number from 1st page and carried here
<br/>
</div>);
}
}
route.js
my route page
<Route path="/signup" component={Validation}/>
<Route path="/view" component={DetailsComponent} />
As option you can do it like this:
add field to state
success: true | false
and if signup is successfull
setState({success: true})
and in signup component add ternary
import {Redirect} from 'react-router-dom';
!success ? <SignUp /> : <Redirect to={{
pathname: '/view',
state: {data: signUpCredentials}
}}
/>
and in View component you can access it in
{props.location.state}
You can use the useHistory() hook of React router.
For eg:
const history = useHistory();
...
history.push('/');
You can pass props from Route render. You can do this in multiple ways. If your data is available at router level. Like below. Or setup up a Store for shared data
const myProps = {x,y,z}
<Route path="/signup" render={()=> <Validation myNewProp='someprop' />} />
<Route path="/view" render={()=> <DetailsComponent {...myProps} />} />

How to render React Route component in an entirely new, blank page

I'm trying to render a print page using React Router. So I have two components:
export default class PurchaseOrder extends React.Component{
....
render(){
const {orderDate, client} = this.state.order;
//omitted for brevity
return(
<BrowserRoute>
<Button
component={Link}
to="/order/print"
target="_blank"
>
Print
</Button>
<Route
path="/order/print"
render={props => (
<OrderPrint
{...props}
orderDate={orderDate}
client={client}
/>
)}
/>
</BrowserRoute>
}
}
And the OrderPrint:
export default function OrderPrint(props) {
return (
<div>props.orderDate</div>
<div>props.client.name</div>
);
}
As you can see, I'm trying to present the printable version of the purchase order with a click of a button. The OrderPrint component gets rendered, but it's rendered right below the button. I could put the Route inside my root component, which is App, that way making sure that I get only the contents of the OrderPrint component rendered like this:
class App extends Component {
render() {
return (
<div className="App">
<Router>
<Route exact path="/" component={PurchaseOrder} />
<Route exact path="/order/print" component={OrderPrint} />
</Router>
</div>
);
}
}
But in that case, I won't be able to pass the necessary props to it. So in this particular case, how to replace entire page content with the contents of OrderPrint component and still be able to pass the necessary input to it?
Update
As #Akalanka Weerasooriya mentioned in comments, I could have the entire state kept in the App component. But one thing stopped me from doing this: This means I'll practically always have to use the render prop of the Route component, instead of the component prop. Ok, that's not a problem, but if it's the way to go, then why does React Router documentation almost always use the
<Route path="/about" component={About} />
pattern as the standard way of using it? So to recap it, if I go the Single Source of Truth way and store all my state in one place, then doesn't it mean that I will always use
<Route path="/about" render={props=>(<div>props.someProp</div>)} />
I don't say there's a problem with it, it's just mentioning it in the documentation only after component={SomeComponent} pattern confuses me.
Not sure why you need a different route for a print page, but anyway if you want it on a new empty page, you can take advantage of the ReactDOM.createPortal feature.
You can create a new page and or even a new window using window.open while keeping the flow of react data in sync.
Here is a running example of a portal on a new window with live state updates from the component that triggered this window using a portal:
running example, i'm sharing an external snippet and not using stack-snippets here because window.open returns null in the contexts of stack-snippets
Source code:
class WindowPortal extends React.PureComponent {
containerEl = document.createElement("div");
externalWindow = null;
componentDidMount() {
const { width = 450, height = 250, left = 150, top = 150 } = this.props;
const windowFetures = `width=${width},height=${height},left=${left},top=${top}`;
this.externalWindow = window.open("", "", windowFetures);
this.externalWindow.document.body.appendChild(this.containerEl);
}
componentWillUnmount() {
this.externalWindow.close();
}
render() {
return ReactDOM.createPortal(this.props.children, this.containerEl);
}
}
class App extends React.PureComponent {
state = {
counter: 0,
showWindowPortal: false
};
componentDidMount() {
window.setInterval(() => {
this.setState(state => ({
counter: state.counter + 1
}));
}, 1000);
}
toggleWindowPortal = () => {
this.setState(state => ({
...state,
showWindowPortal: !state.showWindowPortal
}));
};
closeWindowPortal = () => {
this.setState({ showWindowPortal: false });
};
render() {
return (
<div>
<h1>Counter: {this.state.counter}</h1>
<button onClick={this.toggleWindowPortal}>
{this.state.showWindowPortal ? "Close the" : "Open a"} Portal
</button>
{this.state.showWindowPortal && (
<WindowPortal closeWindowPortal={this.closeWindowPortal}>
<h2>We are in a portal on a new window</h2>
<h3>{`This is the current state: ${this.state.counter}`}</h3>
<p>different window but sharing the state!!</p>
<button onClick={() => this.closeWindowPortal()}>Close me!</button>
</WindowPortal>
)}
</div>
);
}
}
here you have a PrivateRoute which is a custom route which holds a header and header is rendered in PrivateRoute routes only so when you try to navigate to new route like path="/order/print" then you won't get header which has button in it.
function Header(props) {
return (
<div>
<Button
component={Link}
to="/order/print"
target="_blank">
Print</Button>
{props.children}
</div>
)
}
const PrivateRoute = ({ component: Component, layout: Layout, ...rest }) => {
return <Route {...rest} render={props => {
return <Layout>
<Component {...props} />
</Layout>
}} />
}
export default class PurchaseOrder extends React.Component{
render(){
const {orderDate, client} = this.state.order;
//omitted for brevity
return(
<BrowserRouter>
<div>
<PrivateRoute exact path="/" layout={Header} component={Landing} />
<Route
path="/order/print"
render={props => (
<OrderPrint
{...props}
orderDate={orderDate}
client={client}
/>
)}
/>
</div>
</BrowserRouter>
}
}

Firebase auth resets when I refresh a different Route path

App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
user: {},
}
}
render() {
return (
<BrowserRouter>
<div style={{background:'floralwhite'}}>
<Route path='/' component={Home} />
<Route path='/calendar' component={Calendar} />
<Route path='/config' component={UserConfig} />
<Route path='/login' component={Login} />
</div>
</BrowserRouter>
);
}
}
I am using react-router-dom Route to create different paths in my web app. And I use these paths in my navbar that renders like this:
render() {
return(
<div className="navbar">
<div className="logo-container">
<img src={Logo}></img>
</div>
<div className="left-link-container">
</div>
<div className="right-link-container">
<span style={{color:'white'}} onClick={this.handleLogout}>Sign Out</span>
<Link style={{ textDecoration: 'none' }} to="/config"><span style={{color:'white', marginRight:'2em'}}>Configuration</span></Link>
<Link style={{ textDecoration: 'none' }} to="/calendar"><span style={{color:'white', marginRight:'2em'}}>Calendar</span></Link>
</div>
</div>
);
}
So, clicking the Calender would lead me to the /calendar path. There is no problem and everything works as intended. However, when I refresh on the /calendar page, it somehow logs off from my firebase authentication.
My authentication happens in my Home component as follows:
class Home extends Component {
constructor(props) {
super(props);
this.state = {
user: {},
}
}
componentDidMount() {
this.authListener();
}
authListener() {
fire.auth().onAuthStateChanged((user) => {
console.log(user);
if (user) {
this.setState({ user });
localStorage.setItem('user', user.uid);
} else {
this.setState({ user: null });
localStorage.removeItem('user')
}
});
}
render() {
return (
<div className="App">
{this.state.user ? (
<div>
<Navbar />
</div>) :
(<Login />)}
</div>
)
};
I do conditionally render the component and lead the user to the Login page when it's not logged on. It auto logs the user off when I refresh from other paths defined in my App.js. I need to fix this because I used fire.auth().currentUser.uid to get the uid of the user in other components with a different path.
Any help?
Check the following pages.
I think to use a React HOOKS is better.
use a React HOOKS
Firebase + React HOOKS Authentication Tutorial
https://www.youtube.com/watch?v=K_wZCW6wXIo
use a higher-order component
A Firebase in React Tutorial for Beginners [2019]
https://www.robinwieruch.de/complete-firebase-authentication-react-tutorial/#firebase-authentication

React Router: state variable updated with setState not being passed down to Route correctly

So, I have the following problem:
I want to allow users to set the language of my portfolio. To do so, I provide them with two links in the initial <Language /> component, which set the state of <App /> and then lead the user to the home screen - <Home />. The problem is, the updated this.state.language is not passed down to <Home />; instead, the initial value it had is passed down.
My code:
// <App /> component
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
language: "none"
}
}
changeLanguage = event => {
event.preventDefault()
this.setState({ language: event.target.id }, () => {
window.location.href = "/home"
})
}
render() {
return (
<BrowserRouter>
<div id="app">
<Route
exact
path="/"
render={() => (
<Language changeLanguage={this.changeLanguage} />
)}
/>
<Route
path="/home"
component={() => {
return <Home language={this.state.language} />
}}
/>
<Route
path="/about"
render={() => {
return <About language={this.state.language} />
}}
/>
<Route
path="/projects"
render={() => {
return <Projects language={this.state.language} />
}}
/>
<Route
path="/contact"
render={() => {
return <Contact language={this.state.language} />
}}
/>
</div>
</BrowserRouter>
)
}
}
// <Language /> component
const Language = props => {
return (
<div id="language">
<h1>Choose your language.</h1>
<div className="lang-options">
<a
href="/home"
className="lang-link"
id="pt_BR"
onClick={props.changeLanguage}
title="Português Brasileiro"
>
<img
src="https://s3-sa-east-1.amazonaws.com/myhostedfiles.raulf/Images/svg-icons/brazil-flag.svg"
alt="A bandeira brasileira. Clique para ter acesso ao site em português."
/>
</a>
<a
href="/home"
className="lang-link"
id="en_US"
onClick={props.changeLanguage}
title="American English"
>
<img
src="https://s3-sa-east-1.amazonaws.com/myhostedfiles.raulf/Images/svg-icons/usa-flag.svg"
alt="The american flag. Click to access the site in english."
/>
</a>
</div>
<h1>Escolha seu idioma.</h1>
</div>
)
}
// <Home /> component
const Home = (props) => {
console.log(props.language)
return (
<div id="home">
<div className="bg-filter" />
<Navbar />
<TypedIntro />
<LinkBox />
</div>
)
}
When the <Home /> component is loaded, console.log(props.language) logs none to the console, which is the initial value this.state.language is set to. Can anybody explain to me why won't it update, and how to fix it?
Setting window.location.href is going to refresh the page. That will lose all your state. Change your <a> tags to use the react-router <Link to="/home"> tag instead.
Here's a CodeSandbox to help illustrate what is going on:
Have you tried exploring this as a solution? You would have a file where you define your keys to each language and pass around an i18n object to access each key. This is an anti-pattern as you are trying to mutate state with multiple components.
You were having issues because you were using windows.location.href it will refresh the app. And after that you will get a new instance of the app and you will lose the state of the app. And it was the reason you were getting initial state in console.
Change windows.location.href to this.props.history.push() but to do so you need to wrap App component with withRouter HOC from 'react-router-dom'. I have changed your code in some places check that out. And try to use Link tag instead of a tag.
import { BrowserRouter , Route, withRouter} from "react-router-dom";
.....
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
language: "none"
}
}
changeLanguage = event => {
event.preventDefault()
// react-router passes "history" as props
this.setState({ language: event.target.id }, () => this.props.history.push('/home'))
}
render() {
return (
<div id="app">
<Route
exact
path="/"
render={() => (
<Language changeLanguage={this.changeLanguage} />
)}
/>
<Route
path="/home"
component={() => {
return <Home language={this.state.language} />
}}
/>
</div>
)
}
}
// wrapping App component with "withRouter" HOC
const RouterApp = withRouter(App)
// <NewApp /> Component
// you need to do this because component wrapped inside
// "withRouter" HOC must be inside "Router" component
const NewApp = () => {
return <BrowserRouter>
<RouterApp />
</BrowserRouter>
}
// <Language/> Component
const Language = props => {
return (
<div id="language">
<h1>Choose your language.</h1>
<div className="lang-options">
<p
className="lang-link"
id="pt_BR"
onClick={props.changeLanguage}
title="Português Brasileiro"
>
Português
</p>
<p
className="lang-link"
id="en_US"
onClick={props.changeLanguage}
title="American English"
>
American
</p>
</div>
<h1>Escolha seu idioma.</h1>
</div>
)
}
// <Home Component/>
const Home = (props) => {
console.log(props.language)
return (
<div id="home">
Home Component
<p>Language: {props.language}</p>
</div>
)
}
// render NewApp component
ReactDOM.render(<NewApp />, document.getElementById('root'));
You should use <Link to='/home'> instead of window.location.href = "/home".
This will reload the page and reload the component. Also it disables react's capability as a SPA.

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

Categories