Passing states to this.props.children using Nested Routes - javascript

I agree there are multiple questions asked for the same. I did a lot of research for a few hours, however, I couldnt resolve this easy looking error. According to How to pass props to {this.props.children} post, i understand the easy use of React.cloneElement and React.Children.
Following is my Parent Class:
class AboutPage extends React.Component {
constructor(props, context){
super(props, context);
this.state = {
details: "details"
}
}
render() {
const childrenWithProps = React.Children.map(this.props.children,
(child) => {
React.cloneElement(child, {
details: this.state.details
})
}
);
return (
<div className="jumbotron">
<h1>About</h1>
<p>This application uses React, Redux, React Router and other libs for our EducationOne Project</p>
<Link to="/about/Owner">
<Button color="primary">test</Button>
</Link>
{childrenWithProps}
</div>
);
}
}
AboutPage.PropTypes = {
children: PropTypes.object.isRequired
};
Following is my child component:
const Owner = (props) => {
return (
<div>Owner details: {props.details}</div>
);
};
Instead of importing the child or parent, i nested the route in my routes.js to create a child for aboutPage:
export default (
<Route path="/" component={App}>
<IndexRoute component={Login} />
<Route path="home" component={HomePage}/>
<Route path="about" component={AboutPage}>
<Route path="omkar" components={Omkar} />
</Route>
<Route path="courses" component={CoursesPage}>
{/*<IndexRoute components={CourseDetailsAndList}/>*/}
</Route>
</Route>
);
However, I dont see any error or any message in the console, nor the child component loaded when i click the button to load the child component.
Any help will be really appreciated.

The problem is in your map function. The callback arrow function has block body with brackets and so you need to explicitly return your cloned element with the return keyword.
In a concise body, only an expression is needed, and an implicit
return is attached. In a block body, you must use an explicit return
statement.
const childrenWithProps = React.Children.map(this.props.children, child => {
return React.cloneElement(child, {
details: this.state.details
});
});

Related

Navbar can't be shown [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
In my project after login, the Navbar has to change.
class App extends React.Component {
render(){
return(
<BrowserRouter>
<div className="App">
{firebase.auth().onAuthStateChanged(user =>{
if(user){
return(
<UserNavBar/>
)
}else{
return(
<CommonNavBar/>
)
}
})}
<Route path="/Home" component={Home}/>
<Route path="/Photos" component={Photos}/>
<Route path="/RegisterPage" component={RegisterPage}/>
<Route path="/LoginPage" component={LoginPage}/>
<Route path="/Account" component={Account}/>
</div>
</BrowserRouter>
)
}
}
export default App
But nothing is returned, I tried to debug it many times:
The system detects that the user is login
If I console log after the if it's work
Make sure whenever you are doing Async operations in a component which is in your case a class component, do the same in componetDidMount(). then change the state of your component by using setState.
you can see the lifecycle of the react here: https://reactjs.org/docs/state-and-lifecycle.html
for eg:
class App extends React.Component {
constructor(props){
super(props);
this.state = {
user: null
}
}
componentDidMount() {
firebase.auth().onAuthStateChanged(user => user && this.setState({user}) )
}
render(){
const { user } = this.state;
return(
<BrowserRouter>
<div className="App">
{user ? <UserNavBar/> : <CommonNavBar/>}
<Route path="/Home" component={Home}/>
<Route path="/Photos" component={Photos}/>
<Route path="/RegisterPage" component={RegisterPage}/>
<Route path="/LoginPage" component={LoginPage}/>
<Route path="/Account" component={Account}/>
</div>
</BrowserRouter>
)}
}
export default App
or you can use hooks instead of class component.
I believe the problem is that you misunderstand the Async code (Promises) and template rendering concept. You can't just return some component out of Promise and expect it to appear in the result. Here is a bit different approach that you can use for start:
import React, { useState, useEffect } from 'react';
const NavBar = ({ user, isLoading }) => {
const [isLoading, setIsLoading] = useState(true);
const [user, setUser] = useState(null);
useEffect(() => {
firebase.auth().onAuthStateChanged(user =>{
setUser(user);
setIsLoading(false);
});
}, []);
if (isLoading) {
return <div>Some navigation placeholder without content while info is loading....</div>
}
return user ? <UserNavBar/> : <CommonNavBar/>;
}
const App = () => {
return(
<div className="App">
<NavBar />
<BrowserRouter>
<Route path="/Home" component={Home}/>
<Route path="/Photos" component={Photos}/>
<Route path="/RegisterPage" component={RegisterPage}/>
<Route path="/LoginPage" component={LoginPage}/>
<Route path="/Account" component={Account}/>
</BrowserRouter>
</div>
)
};
export default App
Important things here:
In onAuthStateChanged we don't return some component, but we set the state data with user information. Setting state would re-render component and depends on current existing user data on hands, component would render the result.
While data is loading, it's a good practice to show some placeholder (empty nav) to avoid too much glitching
We've extracted Navigation data and rendering logic to separate component - to not pollute common App component with very specific code.
Code looks a bit differently because of Hooks API which are more "fancy and popular" these days in React community.
In case of questions, feel free to add comments, I'd be glad to help.

How to retain and pass the new state values to another router component in ReactJS

I have some state values using in the App.js as below
constructor(){
super()
this.state = {
text: "hello world"
}
}
handleClick() {
this.setState({text: "good morning})
}
render() {
return (
<BrowserRouter>
<div>
<Header />
<Switch>
<Route exact path="/" component={() => <Home handleClick={this.handleClick()} {...this.state}/>} />
<Route path="/login" component={Login} {...this.state}/>
/>
</Switch>
</div>
</BrowserRouter>
);
}
Inside my Home component I have a button click function and on clicking the handleClick function will trigger and the state value will change. I want to retain the new state value and pass it to login component as well as Home component after the login process is completed.
Right now when I goes to /login and go back to / the default state value is getting inside the Home component.
The state value is changing to new value on onClick function and there is no issue with that. I have the binding methods added in the App.js for the functions.
How can I retain the new state value to use inside Home component after some other router is called?
You need to either bind the function in the constructor, or use arrow functions instead. Additionally, you need to pass the function's reference only, without calling it. That means you can't use the () when passing it as a prop. And one last thing, you were passing the destructured state to the Route component instead of the Login component.
constructor(){
super()
this.state = {
text: "hello world"
}
// binding in constructor not needed if using arrow function
this.handleClick = this.handleClick.bind(this);
}
// arrow function not needed if binding in constructor
handleClick = () => {
this.setState({text: "good morning})
}
render() {
return (
<BrowserRouter>
<div>
<Header />
<Switch>
<Route
exact
path="/"
component={() => (
<Home
// important: this.handleClick without ()
handleClick={this.handleClick}
{...this.state}
/>
)}
/>
<Route
path="/login"
component={() => (
<Login {...this.state} />
)}
/>
</Switch>
</div>
</BrowserRouter>
);
}
Setting state in handleClick should be:
handleClick() {
this.setState({text: "good morning"})
}

withRouter Does Not passes Route props to component

Here is my navigation component:
import React from 'react'
class Navigation extends React.Component {
constructor(props) {
super(props);
this.state = {
type: 'signUp', // or login
showModal: false,
isLoggedIn: false,
}
}
...some code
render() {
const { showModal, type, isLoggedIn } = this.state
console.log(this.props.location); // all problem is this, I'm not getting it in console
return(
...some more code
)
}
}
export default withRouter(Navigation)
And here is where it it been used in app.js
class App extends React.Component {
render () {
return(
<Router>
<Fragment>
<Navigation /> // <= right there
<Switch>
<Route exact path='/' component={HomePage}/>
<Route exact path='/search' component={HomePage}/>
<Route component={Lost} />
</Switch>
</Fragment>
</Router>
)
}
}
I want to get updated route props like match and location and history in my <Navigation /> component but I get it only when the first time that component mounts on the DOM, in my other components I update the route using window.history.pushState but I am not able to get route props from withRouter after link in the browser is been updated.
I update route with window.history.pushState because:
I could not find any way to update just link in the address bar without showing user or redirecting user to new component with React router DOM (am I doing it in right way or not?)
based on that I then use window.location.pathname to add some specific stylings to some components)
Also, I read the entirety of this and this but I could not solve this issue. What am I doing wrong?
withRouter gives you the closest <Route>'s route props, and since the Navigation component is not inside a Route you will not get the route props.
You could e.g. put the Navigation component on a Route outside of the Switch that will always be visible.
Example
class App extends React.Component {
render() {
return (
<Router>
<Fragment>
<Route path="/" component={Navigation} />
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/search" component={HomePage} />
<Route component={Lost} />
</Switch>
</Fragment>
</Router>
);
}
}

React pass props to children edge case?

My app has the structure below and I would like to pass a prop based in the Header state to the RT component. I can pass it easily with Context but I need to call an API when the prop is a certain value and Context doesn't seem to be designed for this use due to it using the render pattern.
In Header.js I render the children with this.props.children. To pass props I've tried the following patterns but nothing is working. I'm missing a concept here. What is it?
(1) React.Children.map(children, child =>
React.cloneElement(child, { doSomething: this.doSomething }));
(2) {React.cloneElement(this.props.children, { loggedIn: this.state.loggedIn })}
(3) <Route
path="/issues"
render={({ staticContext, ...props }) => <RT {...props} />}
/>
Structure:
App.js
<Header>
<Main />
</Header>
Main.js
const Main = () => (
<Grid item xl={10} lg={10}>
<main>
<Switch>
<Route exact path="/" component={RT} />
<Route path="/projects" component={Projects} />
<Route path="/issues" component={RT}/>
<Route path="/notes" component={Notes} />
</Switch>
</main>
</Grid>
);
I would personally recommend using the React Context API to handle the user state rather than manually passing it via props. Here's an example of how I use it:
import React from 'react';
export const UserContext = React.createContext();
export class UserProvider extends React.Component {
constructor(props) {
super(props);
this.state = {
user: false,
onLogin: this.login,
onLogout: this.logout,
};
}
componentWillMount() {
const user = getCurrentUser(); // pseudo code, fetch the current user session
this.setState({user})
}
render() {
return (
<UserContext.Provider value={this.state}>
{this.props.children}
</UserContext.Provider>
)
}
login = () => {
const user = logUserIn(); // pseudo code, log the user in
this.setState({user})
}
logout = () => {
// handle logout
this.setState({user: false});
}
}
Then you can use the User context wherever you need it like this:
<UserContext.Consumer>
{({user}) => (
// do something with the user state
)}
</UserContext.Consumer>

Accessing parent state in child in React

I have (e.g.) two components in React. The first, app.js, is the root component. It imports some JSON data and puts it in its state. This works fine (I can see it in the React devtools).
import data from '../data/docs.json';
class App extends Component {
constructor() {
super();
this.state = {
docs: {}
};
}
componentWillMount() {
this.setState({
docs: data
});
}
render() {
return (
<Router history={hashHistory}>
<Route path="/" component={Wrapper}>
<IndexRoute component={Home} />
<Route path="/home" component={Home} />
<Route path="/docs" component={Docs} />
</Route>
</Router>
);
}
}
The second, docs.js, is meant to show this JSON data. To do that it needs to access the state of app.js. At the moment it errors, and I know why (this does not include app.js). But how then can I pass the state from app.js to docs.js?
class Docs extends React.Component {
render() {
return(
<div>
{this.state.docs.map(function(study, key) {
return <p>Random text here</p>;
})}
</div>
)
}
}
The proper way of doing this would be by passing state as props to Docs component.
However, because you are using React Router it can be accessed in a bit different way: this.props.route.param instead of default this.props.param
So your code should look more or less like this:
<Route path="/docs" component={Docs} docs={this.state.docs} />
and
{this.props.route.docs.map(function(study, key) {
return <p>Random text here</p>;
})}
Another way of doing this is:
<Route path="/docs" component={() => <Docs docs={this.state.docs}/>}>
If you need to pass children:
<Route path="/" component={(props) => <Docs docs={this.state.docs}>{props.children}</Docs>}>
If you are doing it like this, then you can access your props values directly by this.props.docs in Child Component:
{
this.props.docs.map((study, key)=> {
return <p key={key}>Random text here</p>;
})
}
Another way of doing this will be
<Route path='/' render={ routeProps => <Home
{...routeProps}
docs={this.state.docs}
/>
}
/>
While in the child component you can access docs using
this.props.docs
Hope it helps!

Categories

Resources