Props are not passed with React.cloneElement in the render() method - javascript

I have two react components, a Layout class and a HomePage class:
HomePage is a component which needs to have a products prop.
HomePage.js
import React, { Component } from 'react';
export class HomePage extends Component {
render() {
if (!this.props.products) {
return (<div>Products not loaded yet</div>);
}
return (<div>Products loaded!</div>);
}
}
Layout is a component that displays children coming from routes established with react-router.
This class is in charge to pass the products prop to children using React.cloneElement
Layout.js
import React, { Component } from 'react';
import { NavMenu } from './NavMenu';
import { Footer } from './Footer';
export class Layout extends Component {
constructor(props) {
super(props);
this.state = {
products: null,
loading: true
};
}
// Make an api call when the component is mounted in order to pass
// additional props to the children
componentDidMount() {
this.populateProductsData();
}
async populateProductsData() {
const response = await fetch('api/products/all');
const data = await response.json();
this.setState({ products: data, loading: false });
}
render() {
if (this.state.loading) {
return (<div>App loading</div>);
}
const childrenWithProps = React.Children.map(this.props.children, child => {
const props = { products: this.state.products };
if (React.isValidElement(child)) {
return React.cloneElement(child, props);
}
return child;
});
return (
<div>
<NavMenu />
{childrenWithProps}
<Footer />
</div>
);
}
}
The routing is made in an App component:
App.js
export default class App extends Component {
render () {
return (
<Layout>
<Route exact path='/'
component={HomePage}/>
</Layout>
);
}
Hence, I am expecting to
Have a page with the App loading message while the API call hasn't been made
Have a page with the Products not loaded yet message while the prop hasn't been passed to the Layout children
Have a page with the Products loaded! message
However, the application is stuck at step two: the products prop is never received by the children components. The code compiles, there are no runtime errors, and the back-end Api is triggered and sends a valid response.
Why the product props will never be available in the render() method of the child HomePage component?
EDIT:
Following #Nikita Chayka's answer, the props should be passed at routing:
Layout.js
export class Layout extends Component {
render() {
return (
<div>
<NavMenu />
{this.props.children}
<Footer />
</div>
);
}
}
App.js
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
products: null,
loading: true
};
}
componentDidMount() {
this.populateProductsData();
}
async populateProductsData() {
const response = await fetch('/api/products/all');
const data = await response.json();
this.setState({ products: data, loading: false });
}
render() {
if (this.state.loading)
return (<div>App loading</div>);
return (
<Layout>
<Route exact path='/'
render={(props) => (<HomePage {...props} products={this.state.products}/>)}/>
</Layout>
);
}
}

Your Layout component will pass products prop to Route component, not Home component, basically you will have
<Route products={} component={Home} path="/" exact/>
But you need to pass it down to Home, you could check for ideas here - https://ui.dev/react-router-v4-pass-props-to-components/
EDIT
You should not provide component property to Route, only render.

Related

React Js state error (Cannot update during an existing state)

I wanted to set state for logged in user if user not logged in then navigate is true by this.setstate() method. but problem its showing error at console and not working the process here is error bellow
0.chunk.js:219252 Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
in Header (at App.js:9)
in App (at src/index.js:17)
in ErrorBoundary (at src/index.js:16)
in Router (created by BrowserRouter)
in BrowserRouter (at src/index.js:15)
Here bellow my app.js code
import React from 'react';
import './App.scss';
import Header from './components/Header/Header';
import Sidebar from './components/Sidebar/Sidebar';
import Routes from './routes/Routes';
function App() {
return (
<>
<Header />
<Sidebar />
<Routes />
</>
);
}
export default App;
Signin.js code
export default class SignIn extends Component {
constructor(props) {
super(props)
this.state = {
redirect: false,
};
}
}
render() {
if (this.state.redirect) {
return <Redirect to="/" />;
}
}
onSignInHandler = () => {
.......
this.setState({
....
redirect: true,
});
}
At Header.js where logout button have. I wanted to give this link onclick() handler. here below are code
export default class Header extends Component {
constructor(props) {
super(props);
this.state = {
navigate: false
}
}
onLogoutHandler = () => {
....
this.setState({
navigate: true,
});
};
render() {
const { navigate } = this.state;
if (navigate) {
return <Redirect to="/signin" push={true} />;
}
return(<header></header>)
}
}
At header.js I have logout link which will trigger the onLogoutHandler and state will then will redirect to signin page. please help me for this issue
Please check if this works
onLogoutHandler = async () => {
....
await this.setState({
navigate: true,
});
};
You should have return statement at the end of your render() method from your class component
render() {
const { navigate } = this.state;
if (navigate) {
return <Redirect to="/signin" push={true} />;
}
// you should also have return statement in this line here
// return something like
return null;
}

Pass props or data beteween components React

Good afternoon friends,
My pages and components are arranged in the main class of my application, can I pass some results from any component or page to the main class and get this property from main class to any other component.
To describe question well I will show an example:
This is my main class App.js
import React, {Component} from 'react';
import { BrowserRouter as Router, Route} from "react-router-dom";
import HomePage from "./Pages/HomePage";
import NavBar from "./Components/NavBar";
import PaymentStatus from "./Pages/PaymentStatus";
class App extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
};
render() {
return (
<Router>
<NavBar/>
<Route name={'Home'} exact path={'/'} component={HomePage}/>
<Route name={'PaymentStatus'} exact path={'/payment-status/:tId'} component={PaymentStatus}/>
</Router>
);
}
}
export default App;
Now my navigation bar component: NavBar.js
import React, {Component} from 'react';
class NavBar extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
};
_makeSomething =async() => {
// Somw function that returns something
}
render() {
return (
<div>
<div id={"myNavbar"}>
<div>
<a onClick={()=>{this._makeSomething()}} href={'/'}/> Home</a>
<a onClick={()=>{this._makeSomething()}} href={"/payment-status"} />Payment Status</a>
</div>
</div>
);
}
}
export default NavBar;
HomePage.js
import React, {Component} from 'react';
class HomePage extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
};
async componentDidMount() {
console.log(this.props.match.params.tId)
};
render() {
return (
<div>
<div id={"main"}>
<div>
<p>This is home page</p>
</div>
</div>
);
}
}
export default HomePage;
PaymentStatusPage.js
import React, {Component} from 'react';
class PaymentStatusPage extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
};
async componentDidMount() {
console.log(this.props.match.params.tId)
};
render() {
return (
<div>
<div id={"status"}>
<div>
<p>This is payment Status Page</p>
</div>
</div>
);
}
}
export default PaymentStatusPage;
Now here is the question:
Can I pass to App.js events (or props) when HomePage.js or PaymentStatusPage.js or when something was changed in NavBar.js
Also, want pass received peprops to any component.
Thank you.
You can decalare method in class App and then pass it to another component via props.
For example
Then you can call this method in MyComponent and pass some value to it. This is the way you pass value from subcomponent to parent component. In method in App you can simply use setState.
What's left to do is to pass this new state attribute to another component via props.
To pass value to component, while using you have to change
<Route component={SomeComponent}
To
<Route render={() => <SomeComponent somethingChanged={this.somethingChangedMethodInAppClass}}/>
Hope it helps!
EDIT: You can also use Redux to externalize state and reuse it in child components
You have two options here:
Keep all of your state in your parent component, App, and pass any props down to your children component, even actions that could update the parent state. If another children uses that state, then that child will be rerendered too.
Manage your state with Redux and make it available for all your components.
I created a small example out of your scenario.
In this example, the App component has a state with a property called title and a function that is passed down via props to the Navbar.
class App extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {
title: "Home Page"
};
}
_makeSomething = title => {
this.setState({ title: title });
};
render() {
return (
<Router>
<NavBar clicked={this._makeSomething} />
<Route
name={"Home"}
exact
path={"/"}
component={() => <HomePage title={this.state.title} />}
/>
<Route
name={"PaymentStatus"}
exact
path={"/payment-status/:tId"}
component={() => <PaymentStatus title={this.state.title} />}
/>
</Router>
);
}
}
The components HomePage and PaymentStatus will get that title as props from the App's state and NavBar will get the _makeSomething function as props. So far, all that function does is update the state's title.
class NavBar extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
}
render() {
return (
<div>
<div id={"myNavbar"}>
<NavLink
onClick={() => {
this.props.clicked("Home Page");
}}
to={"/"}
>
{" "}
Home
</NavLink>
<NavLink
onClick={() => {
this.props.clicked("Payment Page");
}}
to={"/payment-status/1"}
>
Payment Status
</NavLink>
</div>
</div>
);
}
}
In the Navbar, when the function I passed down from App as props is clicked, it will go all the way back to the App component again and run _makeSomething, which will change the App's title.
In the mantime, the components HomePage and PaymentStatus received title as props, so when the state's title is changed, these two children component will change too, since their render function relies on this.props.title.
For example, HomePage:
class HomePage extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
}
async componentDidMount() {
console.log(this.props.match.params.tId);
}
render() {
return (
<div>
<div id={"main"}>
<p>This is {this.props.title}</p>
</div>
</div>
);
}
}
Like I said before, by keeping your state in the parent component and sending down to the children component just what they need, you should be able to accomplish what you need.
A note: I did change the anchor tag from <a> to NavLink which is what you're supposed to use with react-router-dom if you don't want a complete refresh of the page.
The full code can be found here:
Have a look at Context. With this you can pass an object from a Provider to a Consumerand even override properties with nested providers: https://reactjs.org/docs/context.html
AppContext.js
export const AppContext = React.createContext({})
App.js
someFunction = ()=>{
//implement it
}
render() {
const appContext = {
someFunction: this.someFunction
}
return (
<AppContext.Provider value={appContext}>
<Router>
<NavBar/>
<Route name={'Home'} exact path={'/'} component={HomePage}/>
<Route name={'PaymentStatus'} exact path={'/payment-status/:tId'} component={PaymentStatus}/>
</Router>
</AppContext>
);
}
Homepage.js
class HomePage extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
};
async componentDidMount() {
console.log(this.props.match.params.tId)
this.props.appContext.someFunction(); //calls the function of the App-component
};
render() {
return (
<div>
<div id={"main"}>
<div>
<p>This is home page</p>
</div>
</div>
);
}
}
export default (props) => (
<AppContext.Consumer>
{(appContext)=>(
<HomePage {...props} appContext={appContext}/>
)}
</AppContext.Consumer>
)
You can also use this mechanic with function components. I'm normally encapsulating the Consumer to an extra component. So all values available for the component as normal property and not just inside the rendered components.

Component rendering too early

I am trying to create a PrivateRoute(HOC) to test if a user has been authenticated(check is 'auth' exist in redux store) before sending them to the actual route. The issue is the privateroute finishes before my auth shows up in redux store.
The console.log runs twice, the first time, auth doesnt appear in the store, but it does the second time, but by that time, its already routed the user to the login screen.... How can I give enough time for the fetch to finish? I know how to do this condition when I simply want to display something conditionally(like login/logout buttons) but this same approach does not work when trying to conditionally route someone.
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Route } from 'react-router-dom'
class PrivateRoute extends Component {
render() {
const { component: Component, ...rest } = this.props
console.log(this.props)
return (
<Route {...rest} render={(props) => (props.auth ? <Component {...props} /> : props.history.push('/login'))} />
)
}
}
function mapStateToProps({ auth }) {
return { auth }
}
export default connect(mapStateToProps)(PrivateRoute)
I didn't use redux here, but I think you would get the main point. Hope this will help and feel free to ask any questions!
import React, { Component } from "react";
import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";
import Dashboard from "path/to/pages/Dashboard";
class App extends Component {
state = {
isLoggedIn: null,
};
componentDidMount () {
// to survive F5
// when page is refreshed all your in-memory stuff
// is gone
this.setState({ isLoggedIn: !!localStorage.getItem("sessionID") });
}
render () {
return (
<BrowserRouter>
<Switch>
<PrivateRoute
path="/dashboard"
component={Dashboard}
isLoggedIn={this.state.isLoggedIn}
/>
<Route path="/login" component={Login} />
{/* if no url was matched -> goto login page */}
<Redirect to="/login" />
</Switch>
</BrowserRouter>
);
}
}
class PrivateRoute extends Component {
render () {
const { component: Component, isLoggedIn, ...rest } = this.props;
return (
<Route
{...rest}
render={props =>
isLoggedIn ? <Component {...props} /> : <Redirect to="/login" />
}
/>
);
}
}
class Login extends Component {
state = {
login: "",
password: "",
sessionID: null,
};
componentDidMount () {
localStorage.removeItem("sessionID");
}
handleFormSubmit = () => {
fetch({
url: "/my-app/auth",
method: "post",
body: JSON.strigify(this.state),
})
.then(response => response.json())
.then(data => {
localStorage.setItem("sessionID", data.ID);
this.setState({ sessionID: data.ID });
})
.catch(e => {
// error handling stuff
});
};
render () {
const { sessionID } = this.state;
if (sessionID) {
return <Redirect to="/" />;
}
return <div>{/* login form with it's logic */}</div>;
}
}
When your action creator return the token, you need to store it in localStorage. and then you can createstore like below,
const store = createStore(
reducers,
{ auth: { authenticated : localStorage.getItem('token') }},
applyMiddleware(reduxThunk)
)
if user already logged in then token will be there. and initial state will set the token in store so you no need to call any action creator.
Now you need to secure your components by checking if user is logged in or not. Here's the HOC for do that,
import React, { Component } from 'react';
import { connect } from 'react-redux';
export default ChildComponent => {
class ComposedComponent extends Component {
componentDidMount() {
this.shouldNavigateAway();
}
componentDidUpdate() {
this.shouldNavigateAway();
}
shouldNavigateAway() {
if (!this.props.auth) {
this.props.history.push('/');
}
}
render() {
return <ChildComponent {...this.props} />;
}
}
function mapStateToProps(state) {
return { auth: state.auth.authenticated };
}
return connect(mapStateToProps)(ComposedComponent);
};

Reusing component in react with dynamic router links

I'm trying to reuse a component in react to render a table view from mysql. I have my navigation generated by a query that lists all of the tables in the database.
import React, { Component } from 'react';
import { BrowserRouter as Router, NavLink } from "react-router-dom";
import './navigation.scss';
class Navigation extends Component {
state = {
items: []
}
Normalize = name => {
return name.split('_')
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
.join(' ');
}
componentDidMount() {
fetch('http://localhost:3132/api/tables')
.then(data => {
return data.json();
})
.then(json => {
let tables = json.map((table, index) => {
var name = table.table_name;
return <li key={index}>
<NavLink activeClassName="active-nav" to={'/table/' + name} name={name}>{this.Normalize(name)}</NavLink>
</li>;
})
this.setState({ tables: tables });
});
}
render() {
return (
<div className="navigation">
<h2>Tables</h2>
<Router>
<ul>
<li><NavLink exact activeClassName="active-nav" to="/">Home</NavLink></li>
{this.state.tables}
</ul>
</Router>
</div>
)
}
}
export default Navigation;
The links work in that they update the url and the active class gets assigned. However, the 'view' component never changes after initial page load.
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import Home from './components/home/home.component';
import Table from './components/table/table.component';
const Routes = () => {
return (
<div className="main">
<Switch>
<Route exact path="/" component={Home}></Route>
<Route path={"/table/:path"} component={(props) => (<Table {...props} />)}></Route>
</Switch>
</div>
)
}
export default Routes;
Even the static Home route doesn't update when it is active. This is what the Table component looks like:
import React, { Component } from 'react';
class Table extends Component {
state = {};
componentDidMount() {
this.setState({tableName: this.props.match.params.path});
}
render() {
return <h1>{this.state.tableName}</h1>
}
}
export default Table;
What do I have to do to get the 'Table' component to render/update?
Edit:
Tried a functional component as suggested in the comment as such and no luck
import React, { Component } from 'react';
const Table = props => {
return <h1>{props.match.params.path}</h1>
}
export default Table;

React cascade rendering

I've got react-toastify element in my App.js component implemented this way:
class App extends Component {
componentDidUpdate(prevProps) {
const { toast } = this.props
if(toast.id !== prevProps.toast.id) {
this.notify(toast)
}
}
notify = (data) => {
switch(data.type) {
case TOAST.TYPE.ERROR:
...
return toast.show()
}
}
render() {
return (
<BrowserRouter>
<div className="app">
<Switch>
<Route path={ getRoutePath('password.set') } component={ PasswordSet } />
<Route path={ getRoutePath('password.reset') } component={ PasswordReset } />
<Route path={ getRoutePath('login') } component={ LoginSection } />
<Route path={ getRoutePath('home') } component={ AuthenticatedSection } />
</Switch>
<ToastContainer
className="custom-toastify"
autoClose={ 5000 }
hideProgressBar={ true }
closeButton={ <CloseButton /> }
/>
</div>
</BrowserRouter>
)
}
}
function mapStateToProps({ toast }) {
return { toast }
}
Now consider the following scenario: I've got a UsersAdmin PureComponent inside AuthenticatedSection where you can enable/disable users. When you click on enable/disable button, the UsersAdmin component re-renders because of users redux state change and then it also re-renders, because I'm showing toast on success/error api call.
toggleUsersDisabled = (user) => () => {
const { modifyUser, showToast } = this.props
modifyUser(user.id, {
disabled: user.disabled === 0 ? 1 : 0
}).then((response) => {
showToast(`${response.value.name} has been ${response.value.disabled ? 'disabled' : 'enabled'}`)
}).catch(_noop)
}
The showToast dispatches new message to redux state for toasts. Is it possible to somehow prevent re-rending of child components when the toast is shown?
Edit:
added UsersAdmin redux connection including selector
// users selector
import { createSelector } from 'reselect'
const getUsers = state => state.users.get('data')
const getIsFulfilled = state => state.users.get('isFulfilled')
export const getFulfilledUsers = createSelector(
[getUsers, getIsFulfilled],
users => users
)
// UsersAdmin
const mapStateToProps = (state) => {
return {
users: getFulfilledUsers(state)
}
}
UsersAdmin.propTypes = {
users: PropTypes.object.isRequired,
fetchUsersList: PropTypes.func.isRequired,
modifyUser: PropTypes.func.isRequired,
deleteUser: PropTypes.func.isRequired,
showToast: PropTypes.func.isRequired
}
export default connect(mapStateToProps, { fetchUsersList, modifyUser, deleteUser, showToast })(UsersAdmin)
I don't really get why you added all the toasts login inside your App.js
if you look over the docs in:
https://github.com/fkhadra/react-toastify#installation
the only thing you need to do is adding <ToastContainer /> to your app and you are done, exactly like you have in your example.
Now for calling toasts you just import:
import { toast } from 'react-toastify';
in to any component you like in the system, and now you just run the toast function and you got yourself a toast.
ie:
import { Component } from 'react';
import { toast } from 'react-toastify';
class SomeComponent extends Component {
showToast() {
toast('you now see a toast');
}
render() {
return <button onClick={()=>this.showToast()}>toast it</button>;
}
}

Categories