Async react routes and infinite loop - reactjs

I decided to use async router loading and i created HOC like this:
const asyncComponent = (importComponent) => {
return class extends React.Component {
state = {
component: null
}
componentDidMount() {
importComponent()
.then(cmp => {
this.setState({component: cmp.default});
});
}
render() {
const C = this.state.component;
return C ? <C {...this.props}/> : null;
}
}
};
export default asyncComponent;
I used this HOC in my router for profile:
import asyncComponent from '../../../../hoc/async.routes.hoc.jsx'
export const UserProfileRoute = () => (
<div>
<Route path="/" name="applicantProfile" exact component={
asyncComponent(() => (import('../containers/profile.ctrl.jsx')))
} />
</div>
)
At the component I call action in the method componentDidMount and after action state updated, method componentDidMount called again and i get infinite loop. Also HOC call all methods and call Router again, Router call component from the scratch - constructor, render, componentDidMount.
My component after update state call all method inside like it is first render of component.
First Main component:
#withRouter
#connect(mapStateToProps)
#i18n
#oauth
export default class Main extends React.Component {
constructor(props){
super(props);
}
render() {
return (
<div className="fl fl--dir-col fl--justify-b h-100">
<Header />
<CombineRoutes {...this.props} />
<Footer />
</div>
)
}
}
Main component call this:
export const CombineRoutes = (props) => (
<Switch>
<IncludedProfile {...props} />
</Switch>
)
When i change the state in User container . 1 - 'Main component' calls 'render' method, 2 - CombineRoutes return UserProfileRoute, 3 - UserProfileRoute again load component throught async HOC, 4 - 'User container' calls 'constructor()' again. Maybe HOC again load my component? thats why my component call 'constructor()' again?
Does someone have the same problem?

You can't call asyncComponent inside UserProfileRoute functional component because on every render it will call asyncComponent again and again. And it actually is the cause of your problem. You should extract asyncComponent call outside of the UserProfileRoute and assign it to a constant.

Related

How do I render a class (react component) passed as a prop?

import SomeComponent from 'Somewheere';
class MyPage {
render() {
return '<OtherComponent AcceptsSomeClass={SomeComponent} />';
}
}
in OtherComponent I want to be able to do
class OtherComponent {
render() {
return <this.props.AcceptsSomeClass open={true} someOtherProp={123}/>;
}
}
I want to be able to render SomeComponent inside OtherComponent. I know I can just pass a node or a function. But I've seen a library before that accepts a class like this and I want to pass the class so that I can control it more in OtherComponent instead of deciding how it renders in MyPage and passing it thee node/function.
In other words I want to pass a class (react component) as a prop and then be able to use it in the JSX.
What I did is that we are passing a function that renders a component, then we can call that function inside the OtherComponent to render it there.
class MyPage extends React.Component {
render() {
return <OtherComponent AcceptsSomeClass={() => <SomeComponent />} />
}
}
class OtherComponent extends React.Component {
render() {
return (
<div>
<p>Content inside OtherComponent</p>
{this.props.AcceptsSomeClass()}
</div>
)
}
}
class SomeComponent extends React.Component {
render() {
return <h1>HELLO WORLD!</h1>
}
}
ReactDOM.render(<MyPage />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id='root'></div>
An example of which you can pass components as props is when you are dealing with HOC (higher-order-components)
I use HOC to handle HTTP requests, for instance, using a modal to pop up on the screen with the loading / error when fetching /putting data, or authentication.
I will present you with a simple example:
import React from 'react'
import Modal from 'modal' //this would be a modal covering the screen
const httpHandler = WrappedComponent => {
const wrappedComponent = props => {
//handle some logic here, coded here, or as a result from some middleware
return (
<Fragment>
<Modal>...</Modal> //handle the HTTP async stuff here, like loading,
//or authentication, or an error message
<WrappedComponent {...props} />
</Fragment>
)
}}
You can call this inside a Component like this when you export another component
//all of the component stuff above
export default httpHandler(WrappedComponent)

React constructor(), componentDidmount with props variable

Where I can call the constructor() and componentDidmount event with below code:
export const Home = props => (props.isAuthenticated ? (
<DashBoard {...props} />
) : (<Marketing {...props} />));
What is the meaning of the above code and how it's work?
This is a functional component, correctly formatted is probably a little easier to read:
export const Home = props => (
props.isAuthenticated ? (
<DashBoard {...props} /> // if authenticated return and render Dashboard
) : (
<Marketing {...props} /> // else return and render Marketing
)
);
In functional components use the useEffect hook with an empty dependency array to achieve the equivalent of a class-based component's componentDidMount. Hooks are called on mount and whenever a variable in its dependency array are updated.
effect hook
export const Home = props => {
useEffect(() => console.log("I just mounted!", []); // empty so called once when the component is mounted
return (
props.isAuthenticated ? (
<DashBoard {...props} /> // is authenticated return and render Dashboard
) : (
<Marketing {...props} /> // else return and render Marketing
)
);
};
You cannot use react lifecycle hooks in a functional component. Refer to react documentation below for usage of lifecycle hooks, and to convert functional components to class components.
https://reactjs.org/docs/state-and-lifecycle.html
export default class Home extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {}
render() {
const { isAuthenticated } = this.props;
return (
<>
{isAuthenticated ? <DashBoard {...this.props} /> : <Marketing {...this.props} />}
</>
);
}
}
export const Home = props => (props.isAuthenticated ? (
<DashBoard {...props} />
) : (<Marketing {...props} />));
Details
So the above code is a functional component, currently functional components can handle all the lifecycle methods that we use in class based components
So prev, before 16.8 of reactjs we can have state and life cycle methods in a functional components, It was only used for rendering the elements like as a presentational components. So at a point for complex applications we need to convert the functional components to class based components to handle a single state change
So this made the evolution of hooks, you can read more on the official docs of react js
So comming to your case if you need to call the method in componentDidMount, you can call as shown below
useEffect(() => {
// your logic same as componentDidMount in class based components
}, [])
So the second argument is the dependencies for the useEffect to trigger
if you pass it as like this it will call every time
useEffect(() => {})
If you pass it as like this it will call whenever the passed variable changes from props or state
useEffect(() => {}, [data, userName])
I hope this will give a better understanding of the problem

React call parent method from child of a child

i'm facing a small problem with my react app.
I'm using bluprintjs Toaster, and i need to display them on top of all other component, no matter what. Like this if there is an error during login or logout the user will always see the toast even if there is a redirection.
My problem is, that i have a middle component that is used to protect access to unAuthenticated user.
On my app class i have a ref to the Toaster and can easily call renderToaster to display a toast. So the method is working correctly.
But when i pass it to my ProtectedRoute and then to MyForm Component i can't call it in the MyFrom component.
From App -> ProtectedRoute -> MyForm if i print this.props i can see the renderToaster() Method, but i think the link from MyFrom -> ProtectedRoute -> App is somehow broken because on MyFrom i have the this.toaster is undefined error.
How can i call my parent parent method. Or how can i create a link between app and MyForm compenent passing through ProtectedRoute?
Thank you for your help.
My App class:
class App extends Component {
renderToaster(intent, message) {
this.toaster.show({
intent: intent,
message: message
});
}
<React.Fragment>
<NavBarComponent />
<Switch>
<ProtectedRoute
exact
path="/path1"
name="path1"
location="/path1"
renderToaster={this.renderToaster}
component={MyForm}
/>
<ProtectedRoute
exact
path="/path2"
name="path2"
location="/path2"
component={params => <MyForm {...params} renderToaster={this.renderToaster} />}
/>
</Switch>
<Toaster
position={Position.BOTTOM}
ref={element => {
this.toaster = element;
}}
/>
</React.Fragment>
}
My ProtectedRoute class:
import React, { Component } from 'react';
import { Route, Redirect } from 'react-router-dom';
import { AuthContext } from '../providers/AuthProvider';
class ProtectedRoute extends Component {
state = {};
render() {
const { component, ...rest } = this.props;
const Component = component;
return (
<AuthContext>
{({ user }) => {
return user ? (
<Route render={params => <Component {...params} {...rest} />} />
) : (
<Redirect to="/" />
);
}}
</AuthContext>
);
}
}
export default ProtectedRoute;
And on my last class (MyForm passed to the protected Route) i call my renderToaster Method like this:
/**
* Component did Mount
*/
componentDidMount() {
this.props.renderToaster(Intent.PRIMARY, 'helloo');
}
You either need to bind renderToaster in the class constructor:
constructor(){
this.renderToaser = this.renderToaster.bind(this);
}
or declare renderToaser as an ES7 class property.
renderToaster = (intent, message) => {
this.toaster.show({
intent: intent,
message: message
});
}
The problem is this in renderToaster isn't pointing where you think it is when the method is passed to the child component. If you use either of these methods, then this will refer back to the class.
See the official docs for more detail: https://reactjs.org/docs/handling-events.html

Pass history as props to an event handler

I am creating a form which after being filled up and the submit button is clicked should navigate to another component. However, I cant seem to be able to pass history as a prop. I assume I am doing something wrong with the bindings of this but cant figure this out. Thanks.
Here is my App.js
import React from 'react';
import {BrowserRouter, Route, Switch} from 'react-router-dom';
import {LandingPage} from './landingPage/LandingPage';
import {ReportsPage} from './reportsPage/ReportsPage';
export class App extends React.Component {
render() {
return (
<BrowserRouter >
<Switch>
<Route path="/" exact component={LandingPage}/>
<Route path="/reports"
render={() => <ReportsPage/>}
/>
</Switch>
</BrowserRouter>
);
}
}
Here is my LandingPage.js
export class LandingPage extends React.Component {
constructor(props){
super(props);
this.state = {
...
this.formAnswersUpdater = this.formAnswersUpdater.bind(this)
}
formAnswersUpdater(e) {
e.preventDefault()
...
history.push("/reports")
}
render() {
return (
<div>
...
<MyForm
onClickOnLastInputsForm={e => this.formAnswersUpdater}
/>
</div>
)
}
And here is where my event is happening. MyForm.js
export class MyForm extends React.Component {
render() {
return(
...
<Route render={({history}) => (
<button className="uk-button uk-button-primary uk-width-1-1#s"
style={{backgroundColor:'#41f44f',
color:'#666', margin: 0}}
id='buttonSliders'
/*if done here it works*/
/*onClick={() => {history.push("/reports")}}*/
/*However if passed to the event handler it does not*/
onClick={() => {this.props.onClickOnLastInputsForm}}
>
ClickMe!
</button>
)}/>
)
My react-router-dom version is: "^4.2.2"
Ok, here is how I handled the issue.
Instead of exporting the LandingPage component, I wrapped it in withRouter function and then exported it.
class LandingPage extends React.Component {
constructor(props){
super(props);
this.state = {
...
this.formAnswersUpdater = this.formAnswersUpdater.bind(this)
}
formAnswersUpdater(e) {
e.preventDefault()
...
//added this.props. here
this.props.history.push("/reports")
}
render() {
return (
<div>
...
<MyForm
onClickOnLastInputsForm={e => this.formAnswersUpdater}
/>
</div>
)
}
// wrapped it with withRouter
export default withRouter(LandingPage)
And then in MyForm component I just called the eventHandler.
export class MyForm extends React.Component {
render() {
return(
...
<button className="uk-button uk-button-primary uk-width-1-1#s"
style={{backgroundColor:'#41f44f',
color:'#666', margin: 0}}
id='buttonSliders'
onClick={this.props.onClickOnLastInputsForm()}
>
ClickMe!
</button>
)
I don't know if this will solve all of your problems, but I see something important missing in your LandingPage render function
onClickOnLastInputsForm={e => this.formAnswersUpdater}
You forgot to pass in your argument: e
onClickOnLastInputsForm={e => this.formAnswersUpdater(e)}
Make sure you add this on MyForm as well. You forgot it there as well. Do you get any other errors after fixing those?
edit: After some inspection of the docs, it looks like the typical use of react-router-dom has a different pattern than what you have. Most common pattern with route handling is to have a root set of routes and use Link from the npm package to navigate. I only see history and its derivatives in the docs for react-router. I'm aware that react-router-dom is a derivative of react-router, but I think if you follow the normal patterns for React, it'll be a lot easier for you in the future when you're debugging.
A nice article on it. It even has a sample app using it: https://medium.com/#pshrmn/a-simple-react-router-v4-tutorial-7f23ff27adf

React. How to pass props inside a component defined on a prop?

If we have the following structure on a React application:
class BasePage extends React.Component {
render() {
return <div>
{this.props.header}
{/*<Header title={this.props.title} />*/}
</div>
}
}
BasePage.defaultProps = {
header: <header>Base Page</header>
}
class Header extends React.Component {
render() {
return <header>
<h1>{this.props.title}</h1>
</header>
}
}
class TestPage extends BasePage {
}
TestPage.defaultProps = {
header: <Header />
}
class Root extends React.Component {
render() {
return <div>
<TestPage
title="Test Page Title"
/>
</div>
}
}
ReactDOM.render(
<Root />,
document.getElementById('root')
)
If we have a common component like <Header /> we can pass a title property easily like <Header title={this.props.title} />.
But how can we pass props inside a component if this component is defined as a prop itself?
For example, how can we do something like:
{this.props.header title={this.props.title}}
So it will render the Test Page Title correctly?
Important note: we could overwrite the render method inside the Test component. But the purpose of this question is to solve this problem without doing this.
Firstly, props are read-only and a component should never be update it's own props, so lines like
componentWillMount() {
this.props.header = <header>Base Page</header>
}
should not be used. defaultProps can do what I think you are trying to do:
class BasePage extends React.Component {
render() {
return <div>
{this.props.header}
{/*<Header title={this.props.title} />*/}
</div>
}
}
BasePage.defaultProps = {
header: <header>Base Page</header>
}
Secondly, inheritance is not often done in React. I'm not saying don't do what your'e doing, but take a read of the docs and see if there is perhaps a simpler way to achieve the same goals.
Finally, setting props on components passed as props. There are a couple different ways to do this.
If you pass the Component rather than the <Component /> you can add props like normal:
ChildComponent = (props) => {
const HeaderComponent = props.header
return <HeaderComponent title="..." />
}
ParentComponent = () => <ChildComponent header={Header} />
You can clone the element to override props:
ChildComponent = (props) => {
const HeaderComponent = React.cloneElement(props.header. { title: "..." })
return <HeaderComponent />
}
ParentComponent = () => <ChildComponent header={<Header />} />
NOTE: I have used functional components instead of class components for brevity, but the concepts are the same.
This seems like a great use case for React.cloneElement.
React.cloneElement(this.props.header, { title: this.props.title });
It returns a clone of the component with the new props included.

Resources