I have a dropdown BasketContents component (as per this question), which fades in and out when a button is clicked. I also want it to close when a the route is changed by clicking a Link tag. I.e. if it's already open on the Checkout page, if I then click the Home page link I want it to automatically close without clicking the basket's close button.
My Routes.js is the root of the app:
render() {
return (
<Router history={hashHistory}>
<Route path="/" component={App}>
<IndexRoute component={Home} />
<Route path="/home" component={Home} />
<Route path="/checkout" component={Checkout} />
</Route>
</Router>
);
}
App.js looks in part like this:
render() {
const childrenWithProps = React.Children.map(
this.props.children,
child => {
return (
React.cloneElement(child, {
basket: this.state.basket
}
)
)
}
);
return (
<div className="main">
<HeaderSection basket={this.state.basket} />
<div className="content">
{childrenWithProps}
</div>
</div>
)
}
The Basket.js component contains the button:
constructor() {
super();
this.state = { open: false }
this.handleDropDown = this.handleDropDown.bind(this);
}
handleDropDown() {
this.setState({ open: !this.state.open })
}
render() {
return(
<div className="basket">
<button className="basketBtn" onClick={this.handleDropDown}>Toggle</button>
<BasketContents
contents={this.props.contents}
open={this.state.open}
/>
</div>
)
}
I did try adding onEnter to my routes but understandably that doesn't do much since it doesn't have access to the Basket's open state. I would ideally like to set the open state of Basket.js to false when changing route (i.e. when clicking on a Link component). Is this possible?
I found a solution that doesn't rely on context. The first thing I did, following the React docs, was to lift my basket state up from the Basket component to the App component. This made it easier to manipulate it from a central location.
Then, following a suggestion here, I listened for changes to the browserHistory in the componentWillMount hook in App:
componentWillMount() {
browserHistory.listen( () => {
this.setState({ basketOpen: false });
});
}
The result is that when the page changes the basket closes.
Related
I want to create routing for overlay modal with parent page in background. Just like in instagram.com , When you click create icon it takes u to "/create/select" while the parent page is still in background.
I tried to implement this in my own page, but it didn't seem to work, it renders the whole new page instead of parent page.
function Routes(){
return(
<Router>
<Switch>
<Route path = "/login" component = {LoginPage} />
<Route path = "/signup/name" component = {NamePage} />
<Route path = "/signup" component = { SignupPage } />
<MainContainer>
<Route path = "/" exact component = {HomePage} />
<Route path = "/explore" component = {Overlay} />
</MainContainer>
</Switch>
<Route path = "/create" children = {<Overlay />}/>
</Router>
)
}
function MainContainer(props: any){
return(
<userContext.Provider value = {currentUser}>
<div id = "main-container">
<NavComponent />
<div className = "main-container-right">
<HeaderComponent />
<main id = "container">
{props.children}
</main>
</div>
</div>
</userContext.Provider>
)
}
It works when I remove exact from the "/" route, but I want the router switch page in "/explore". Is there anything I am doing wrong here?
If it's overlaying the background (parent) component, do you really need to change the route path? If so, my answer may be incomplete.
For overlaying, just put the overlayed component inside the parent's render function. However, you can use the parent's state to decide whether the child/overlay gets rendered.
For example:
render() {
return (
{this.state.overlay === true && <Overlay />}
)
}
This uses a property in the state called overlay - which should likely be false by default. Then just use a function in the parent component to toggle this on/off. You can pass this toggle in to the overlay function in order to close out the overlay.
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} />} />
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>
}
}
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>
);
}
}
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>
)
}