Wrapping multiple react-router routes in error boundary - reactjs

How do you wrap one or more routes in an error boundary component?
I am using React version 16 and have tried wrapping two routes in error boundaries but am experiencing some unexpected behaviour.
I do not get any error messages at all - but one component will, sometimes, not mount.
When switching between two routes using the same child component (form), the parent component will not update or mount at all. The URL in the web browser location bar updates correctly though. (I am using the same child component for add/edit but with different props)
Is this an issue with ErrorBoundary? Do I need to instruct it somehow?
I have read the documentation on reactjs.org but cannot find any information regarding my issue.
Am I missing how the ErrorBoundary is supposed to work?
Happy if you can lead me in the right direction for solving this issue.
See simple code example below.
export const history = createHistory();
const AppRouter = () => (
<Router history={history}>
<div>
<PrivateRoute component={Header} />
<Switch>
<PrivateRoute path="/dashboard" component={DashboardPage} />
<ErrorBoundary key="eb01">
<PrivateRoute path="/category/edit_category/:id" component={EditCategoryPage} exact={true} />
</ErrorBoundary>
<ErrorBoundary key="eb02">
<PrivateRoute path="/create_category" component={AddCategoryPage} exact={true} />
</ErrorBoundary>
</Switch>
</div>
</Router>
);
The Error boundary component
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { error: null, errorInfo: null };
const { history } = props;
history.listen((location, action) => {
if (this.state.hasError) {
this.setState({
hasError: false,
});
}
});
}
componentDidCatch(error, errorInfo) {
// Catch errors in any components below and re-render with error message
this.setState({
error: error,
errorInfo: errorInfo
})
// You can also log error messages to an error reporting service here
}
render() {
if (this.state.errorInfo) {
// Error path
return (
<div>
<h2>Something went wrong</h2>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}
<br />
{this.state.errorInfo.componentStack}
</details>
</div>
);
}
// Normally, just render children
return this.props.children;
}
}
export default ErrorBoundary;
Update
I also tried to wrap the component -not the route- in the ErrorBoundary component.
<PrivateRoute path="/category/edit_category/:id"
render={() => (
<ErrorBoundary>
<EditCategoryPage/>
</ErrorBoundary>
)}
exact={true}/>
I now receive an error (the components are correctly imported - I can use them elsewhere in the same file)
Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

I had wrapped the React-Router in my own component. That is why my code did not work! doh!
When adding ErrorBoundary in the right place (my own component) everything worked as expected. :)
export const PrivateRoute = ({
isAuthenticated,
component: Component,
useErrorBoundary,
...rest
}) => (
<Route {...rest} component = {(props) => (
isAuthenticated ? (
(useErrorBoundary) ?
<div>
<ErrorBoundary>
<Component {...props} />
</ErrorBoundary>
</div>
:
<div>
<Component {...props} />
</div>
) : (
<Redirect to="/" /> //redirect if not auth
)
)
}/>
);

Related

Effecting different component's state in React

I have two different components in React "Header" and "Main".
Header:
import React, { Component } from 'react'
import Logo from './HeaderComps/Logo'
import UserHeader from './HeaderComps/UserHeader'
export default class Header extends Component {
render() {
return (
<header>
<Logo />
<UserHeader name ="Boris"/>
</header>
)
}
}
Main:
export default class Main extends Component {
state ={isLogged : false}
handleClientFirstImpact = () =>{
if(this.state.isLogged === false){
return <Switch>
<Route exact path ='/registration' component={Register} />
<Route exact path ='/login' component={Login} />
</Switch>
}
}
render() {
return (
<Router>
<div className="Main">
{this.handleClientFirstImpact()}
</div>
</Router>
)
}
}
In "Main" I have two components "Register" and "Login".
How do I make Login page effect the Header's state? is there a React technology or a way to do that?
I want the "UserHeader" to show the User name but I don't know how to effect it's parent's state.
There might be some common component where you will be calling the Main as well as the Header Component. Suppose that component is App Component.
So inside App you have
render() {
return
<div>
<Header />
<Main />
</div>
}
Now what you can do is keep the userName in this App Component's state and this App Component will pass userName to the Component as :-
<Header userName={userName} />
Also pass a function as a prop to the Main Component which will enable the component to set the State of the Parent Component.
<Main setUserName={(newUserName) => this.setState(newUserName)} />
now this setUserName prop should be passed on to the components which are called via Route inside the Main Component. Keeping your example in mind (use render prop instead of component for Route):
export default class Main extends Component {
state ={isLogged : false}
handleClientFirstImpact = () =>{
const { setUserName } =this.props;
if(this.state.isLogged === false){
return
<Switch>
<Route exact path ='/registration'
render={(props) => <Register {...props} setUserName={setUserName} />}
/>
<Route exact path ='/login'
render={(props) => <Login {...props} setUserName={setUserName} />}
/>
</Switch>
}
}
render() {
return (
<Router>
<div className="Main">
{this.handleClientFirstImpact()}
</div>
</Router>
)
}
}
Now you have passed setUserName as a prop to both login and register and you can use this method to set App component's state which will in turn reflect the changes on the Header component.
Although the solution might work for you. I would advise you to simplify the Application layout. Keep the routing functionality in the main app Component. Use a separate layout component to render similar page layouts. It would avoid confusion in the long run.

React Context Provide not passing the value to the Consumer

I am having the below Provide which contains the authentication state in it.
export const AuthenticationContext = React.createContext();
export default class AuthenticationProvider extends React.Component {
state = {
isAuthenticated: false
};
render() {
return (
<AuthenticationContext.Provider value={{ state: this.state }}>
{this.props.children}
</AuthenticationContext.Provider>
);
}
}
I have this wrapped to my Routes as below:
<Provider store={store}>
<ConnectedRouter history={history}>
<>
<GlobalStyle />
<AuthenticationProvider>
<SiteHeader />
<ErrorWrapper />
<Switch>
<PrivateHomeRoute exact path="/" component={Home} />
<Route exact path="/login" component={LoginPage}
</Switch>
</AuthenticationProvider>
</>
</ConnectedRouter>
</Provider>
But when I am trying to get context state into the <SiteHeader />, there is nothing passed down by the Context.Provide. My Cosumer is inside <SiteHeader /> is:
class SiteHeader extends React.Component {
render() {
return (
<div>
<AuthenticationContext.Consumer>
{context => (
<header>this is header {context.state.isAuthenticated}</header>
)}
</AuthenticationContext.Consumer>
</div>
);
}
I checked the React devtools, but it's same. Context.Consumer doesn't have value prop from the Provider.
What might be the issue here?
If a value is false, null or undefined, it will not render. I tried your code in a sandbox here. Just added the .toString() in the header, and the value of the boolean is shown in the header.
Context Consumers don't get any data as a props.
instead we pass them a render prop, in this case the children that we pass is the render prop function. and then in the render method of the Consumer something like this happens
render(){
this.props.children(value)
}
this is how we get the value as an argument of the render prop function.
The value of the context provider is not supposed to be passed in through props. You can learn more about render props here

React Maximum update depth exceeded

I receive an error while running my application in React. There are lots of questions with this error but I do not how to solve it. When I press the link, it directs to Login component. 'http://localhost:3000/login'
This is the error which I got on the site:
'Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.'
Here is my LoginPage:
class LoginPage extends React.Component {
constructor(props) {
super(props);
// reset login status
this.props.dispatch(userActions.logout());
this.state = {
username: '',
password: '',
submitted: false
};
}
render() {
return (
<div className="col-md-6 col-md-offset-3">
<h2>Login</h2>
</div>
);
}
}
function mapStateToProps(state) {
return { auth: state.auth };
}
const loginComp = connect(mapStateToProps)(LoginPage);
export { loginComp as LoginPage };
And this is the routing part.
render() {
const { alert } = this.props;
return (
<Router history={history} >
<Switch>
<PrivateRoute path="/" name="Home" component={DefaultLayout} />
<Route exact path="/login" component={LoginPage} />
</Switch>
</Router>
);
}
And that one is DefaultLayout:
import React, { Component } from 'react';
import { Redirect, Route, Switch } from 'react-router-dom';
import { Container } from 'reactstrap';
import {
AppAside,
AppBreadcrumb,
AppFooter,
AppHeader,
AppSidebar,
AppSidebarFooter,
AppSidebarForm,
AppSidebarHeader,
AppSidebarMinimizer,
AppSidebarNav,
} from '#coreui/react';
// sidebar nav config
import navigation from '../../_nav';
// routes config
import routes from '../../routes';
import DefaultAside from './DefaultAside';
import DefaultFooter from './DefaultFooter';
import DefaultHeader from './DefaultHeader';
class DefaultLayout extends Component {
render() {
return (
<div className="app">
<AppHeader fixed>
<DefaultHeader />
</AppHeader>
<div className="app-body">
<AppSidebar fixed display="lg">
<AppSidebarHeader />
<AppSidebarForm />
<AppSidebarNav navConfig={navigation} {...this.props} />
<AppSidebarFooter />
<AppSidebarMinimizer />
</AppSidebar>
<main className="main">
<AppBreadcrumb appRoutes={routes}/>
<Container fluid>
<Switch>
{routes.map((route, idx) => {
return route.component ? (<Route key={idx} path={route.path} exact={route.exact} name={route.name} render={props => (
<route.component {...props} />
)} />)
: (null);
},
)}
<Redirect from="/" to="/dashboard" />
</Switch>
</Container>
</main>
<AppAside fixed hidden>
<DefaultAside />
</AppAside>
</div>
<AppFooter>
<DefaultFooter />
</AppFooter>
</div>
);
}
}
export default DefaultLayout;
The problem could be somewhere else other than this jsx??
You are not going to believe me maybe but I solved the issue by changing the order of Route tags. First one is the tag with path "/login" and it works correctly.
I think its because you are calling this.props.dispatch(userActions.logout());
in your constructor and that is not a good place to update the application state, because that will make the component to re-render before it gets mounted and each time it wants to re-render you are again calling the function in its constructor and setting the state so it gets into a loop where it calls the setState again and again, its better to put this.props.dispatch(userActions.logout()); in a lifeCycle method like componentDidMount(){} so each time your component gets mounted, your logout action gets called and updated the application state.
From my experience, i understood that calling setState inside arrow functions (that is, function written in ES6 format) repeatedly causes this error. Therefore, as much as much possible i started using ES5 format functions only.

high order component throws an invalid react element error (reactjs)

So I've been reading about HOCs lately and decided to use them in my application to pass down authorisation logic to the child components.
I'm trying to render a <Route /> component through the HOC but it logs the error:
Uncaught Error: AuthRoute(...): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.
Here's the code for the HOC:
const AuthRoute = ({ component: Component }) => {
class AuthComponent extends Component {
// Authorisation logic here
render() {
return (
<Route render={props => <Component {...props}/>} />
)
}
}
return AuthComponent;
};
And then I'm using this HOC as an alias of <Route /> component like this:
<BrowserRouter>
<AuthRoute path="/account" component={PrivateComponent} />
</BrowserRouter>
EDIT:
But this approach works fine:
const AuthRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
checkAuth() ? (<Component {...props}/>) : (<Redirect to={{pathname: '/', state: { from: props.location }}}/>)
)}/>
);
<BrowserRouter>
<AuthRoute path="/account" component={PrivateComponent} />
</BrowserRouter>
You need to review your architecture . Indeed, Your approach is totally contradictory regarding HOC pattern . You may need to do the following instead :
<BrowserRouter>
<Route path="/account" component={AuthRoute(PrivateComponent)} />
</BrowserRouter>
If you agree with this design of calling the HOC, The HOC implementation will be :
const AuthRoute = (Composed) => {
class AuthComponent extends React.Component {
// Authorisation logic here
componentWillMount() {
if (!checkAuth()) {
this.props.history.push({pathname: '/', state: { from: this.props.location }});
}
}
render() {
return (
<Composed {...this.props} />
)
}
}
return AuthComponent;
};
In the first case you are returning a class instance
const AuthRoute = ({ component: Component }) => {
class AuthComponent extends Component {
// Authorisation logic here
render() {
return (
<Route render={props => <Component {...props}/>} />
)
}
}
return AuthComponent; // returning the class object here and not an instance
};
So if your wish to use it you would need to write
<BrowserRouter>
<Route path="/account" component={AuthRoute(PrivateComponent)} />
</BrowserRouter>
where AuthRoute(PrivateComponent) is a class object and Route creates an instance out of it internally
However in the second case, its not an HOC, but a functional component that returns a valid React Element,
const AuthRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
checkAuth() ? (<Component {...props}/>) : (<Redirect to={{pathname: '/', state: { from: props.location }}}/>)
)}/>
);
and hence
using <AuthRoute path="/account" component={PrivateComponent} /> , you called a component instance whereby props path and component are received by the functional component.
I’m not very familiar with the syntax you wrote. But spotted one thing. Your parameter is component but inside you are using Component which in this context is undefined
It should be :
const AuthRoute = (Component ) => { // <-- Component which refers to PrivateComponent
class AuthComponent extends React.Component { // <-- React.Component
....
and Not :
const AuthRoute = ({ component: Component }) => {
class AuthComponent extends Component {
...
Also check out my other answer to have elegant HOC .

Carry ownProps deep react router

I am trying to gain access to params in ownProps. Is there a way to solve this?
Current structure is
const Router = () => (
<router>
<Switch>
<Route path='/signup' component={Signup} />
<Route path='/browse' component={Browse}/>
<Route path='/detail/:id' component={Detail} />
</Switch>
</router>
)
i want ownProps to pass on to these pages in Detail
export default class App extends Component {
render() {
return (
<div>
<Info />
<Message/>
</div>
);
}
}
After messing around I found a way which is that I passed the required information as props to nested components.
export default class App extends Component {
render() {
return (
<div>
<Info idInfo={this.props}/>
<Message idInfo={this.props}/>
</div>
);
}
}
after this I got information in ownProps.

Resources