I have a component wrapped in an Error Boundary, when a button is clicked a validate() function is called, this function throws an error if there is not information provided however the ErrorBoundary is not catching this error.
Render function on Component
return (
<ErrorBoundary>
...
<Playlist
...
onClick={this.addPlaylistToSpotify} // this function can throw an error
...
/>
</ErrorBoundary>
);
Function with error
addPlaylistToSpotify = () => {
try {
addPlaylist(this.state.newPlaylist); // this function throws an error on validate()
} catch (error) {
throw new Error(error);
}
...
};
ErrorBoundary Component
import React, { Component } from "react";
import { ErrorOverlay } from "../../components/index";
import styles from "./ErrorBoundary.css";
export class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: ""
};
}
componentDidCatch(error, errorInfo) {
this.setState({
hasError: true,
error: error,
errorInfo: errorInfo
});
// TODO: log the error somewhere in the db
}
dismiss() {
this.setState({
hasError: false,
error: null,
errorInfo: ""
});
}
render() {
if (this.state.hasError) {
return (
<ErrorOverlay message={this.state.errorInfo} dismiss={this.dismiss} />
);
} else {
return this.props.children;
}
}
}
Any help would be hugely appreciated, thanks!
Any help would be hugely appreciated. Thanks!
From React docs
https://reactjs.org/docs/error-boundaries.html#how-about-event-handlers
Note
Error boundaries do not catch errors for:
Event handlers
Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
Server-side rendering
Errors thrown in the error boundary itself (rather than its children)
In your code, the error is thrown from an event handler (addPlaylistToSpotify) so componentDidCatch can't catch the error. Therefore you need to do something like this:
import React from 'react';
export class Playlist extends React.Component {
constructor (props) {
super(props);
this.state = {
error: false
// ...
}
}
addPlaylistToSpotify = () => {
try {
// Something throws an error here.
} catch (error) {
this.setState({ error: true });
}
}
render() {
if (this.state.error) {
throw new Error('I crashed!');
}
return (
<div>
...
<button onClick={this.addPlaylistToSpotify}>Add song</button>
...
</div>
)
}
}
I hope this helps.
Related
I am working with a functional component in which I implemented Lazy loading and compoent is lazy loading fine. But in a sanario while there is no component ErrorBoundary is not working and app is breaking with no fallback UI/msg.
My code is
export const SiteContent = () => {
const [selectedOption, setSelectedOption] = useState(null);
let LazyLoadedComponent = null;
if (selectedOption?.component) {
LazyLoadedComponent = React.lazy(
() =>
import(
`./components/${selectedOption.option.value}/${selectedOption.option.value}`
)
);
}
return (
<>
<ErrorBoundary>
<Suspense fallback={<div>Loading....</div>}>
{selectedOption?.component && LazyLoadedComponent && (
<LazyLoadedComponent />
)}
</Suspense>
</ErrorBoundary>
</>
);
};
ErrorBoundary code is
import React from "react";
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
export default ErrorBoundary;
Try setting your hasError state to true in the componentDidCatch method.
componentDidCatch(error, errorInfo) {
this.setState({ hasError: true});
}
You have to set the hasError state to true for the fallback UI to be shown.
Change your ErrorBoundary's componentDidCatch to :
componentDidCatch(error, errorInfo) {
this.setState({ hasError: true});
}
im upgrading an old react pp to use functional components. I am having troubles with the error boundary class component . I simply dont understand how to update the static getDerivedStateFromError what is the correct syntax to update this function?
initial component
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
};
}
static getDerivedStateFromError(_error) {
return { hasError: true };
}
componentDidCatch(error, info) {
sendError("ErrorBoundary", { info }, error);
}
render() {
if (this.state.hasError) {
return <ErrorText />;
} else {
return this.props.children;
}
}
}
New component, which is imssing something for sure as it never sets the error to true, which was done by the static function..
const ErrorBoundary = (props) => {
const [hasError, setHasError] = useState(false)
try {
if (hasError) {
return <ErrorText />;
} else {
return props.children;
}
} catch {
sendError("ErrorBoundary", { info }, error);
}
}
There's no way to do the componentDidCatch nor the getDerivedStateFromError on hooks right now. Here's the documentation:
https://reactjs.org/docs/hooks-faq.html#from-classes-to-hooks
getSnapshotBeforeUpdate, componentDidCatch and getDerivedStateFromError: There are no Hook equivalents for these methods yet, but they will be added soon.
A class is used to intercept axios error. to bind arrow functions, componentDidMount() is being used. now I need to initiliaze data from the server so I have to use componentWillMount() except it will be removed in React 17 and warning message suggest I use constructor. When I do it gives me an error.
import React, {Component} from "react";
import Modal from "../../components/UI/Modal/Modal";
import Aux from "../Auxiliary/Auxiliary";
const withErrorHandler = (WrappedComponent, axios) => {
return class extends Component{
state = {
error: null
};
// componentWillMount() {
// axios.interceptors.request.use(request => {
// this.setState({
// error: null
// });
// return request;
// });
// axios.interceptors.response.use(res => res, (error) => {
// this.setState({
// error: error
// })
// });
// }
constructor(props) {
super(props);
axios.interceptors.request.use(request => {
this.setState({
error: null
});
return request;
});
axios.interceptors.response.use(res => res, (error) => {
this.setState({
error: error
})
});
}
errorConfirmedHandler = () => {
this.setState({error: null})
};
render() {
return (
<Aux>
<Modal show={this.state.error} modalClosed = {this.errorConfirmedHandler}>
{this.state.error ? this.state.error.message : null}
</Modal>
<WrappedComponent {...this.props}></WrappedComponent>
</Aux>
);
}
}
};
export default withErrorHandler;
I removed .json from URL to produce an error
class BurgerBuilder extends Component {
state = {
ingredients: null,
totalPrice: 4,
purchasable: false,
purchasing: false,
loading: false,
// axiosError: null
};
componentDidMount() {
axios.get('https://burger-m.firebaseio.com/ingredients').then(response => {
this.setState({ingredients: response.data});
}).catch(error => {});
}
..
export default withErrorHandler(BurgerBuilder, axios);
&
Error Message: "index.js:1 Warning: Can't call setState on a component that
is not yet mounted. This is a no-op, but it might indicate a bug in your
application. Instead, assign to `this.state` directly or define a `state = {};`
class property with the desired state in the _temp component."
componentWillMount() does work however. so What Should I change?
Keep constructor simple by just adding state and do not register axios interceptors in constructor method, instead register interceptors in render method.
componentWillUnmount(){
console.log('unregistering interceptors', this.reqInterceptor, this.resInterceptor)
axios.interceptors.request.eject(this.reqInterceptor);
axios.interceptors.response.eject(this.resInterceptor);
}
render() {
if(!this.resInterceptor){
console.log('Registering Interceptors');
this.reqInterceptor = axios.interceptors.request.use(req => {
this.setState({ error: null })
return req;
})
this.resInterceptor = axios.interceptors.response.use(response => response, error => {
this.setState({error})
})
}
return (
<Aux>
<Modal show={this.state.error} modalClosed={this.errorConfirmedHandler }>{this.state.error ? this.state.error.message : null}</Modal>
<WrappedComponent />
</Aux>
)
The constructor initializes the state, that's why you are prohibited from using setState() there.
You could use componentDidMount() instead, I think it matches better your needs and will avoid any confusion.
const withErrorHandler = (WrappedComponent, axios) => {
return class extends Component{
state = {
error: null
};
componentDidMount() {
axios.interceptors.request.use(request => {
this.setState({
error: null
});
return request;
});
axios.interceptors.response.use(res => res, (error) => {
this.setState({
error: error
})
});
}
errorConfirmedHandler = () => {
this.setState({error: null})
};
render() {
return (
<Aux>
<Modal show={this.state.error} modalClosed = {this.errorConfirmedHandler}>
{this.state.error ? this.state.error.message : null}
</Modal>
<WrappedComponent {...this.props}></WrappedComponent>
</Aux>
);
}
}
};
Use this.state = {error: null} and this.state = {error} instead of setState in then blocks of interceptors.
Regarding this specific example I think the best solution is to use a "temporary" variable , call the use method on the interceptors, and the define the state. All this inside the constructor.
Like so:
constructor() {
super();
let tempErrorState = null;
this.requestInterceptor = axios.interceptors.request.use(req => {
tempErrorState = null;
return req;
});
this.resoponseInterceptor = axios.interceptors.response.use(res => res, error => {
tempErrorState = error;
});
this.state = {
error: tempErrorState
};
}
As per your condition, you can't use the componentDidMount() method. You MUST NOT use the render() method for setting up Axios interceptors because you are using withErrorHandler as a higher-order component which can lead to many interceptors eventually created while wrapping other components too. This is because for each render cycle, you are unnecessarily setting up an interceptor if you define it inside a render() method. You should set up an Axios interceptor only once in the component.
The constructor() would be the best place to set this up (as per the latest version of React where componentWillMount() is deprecated).
You can edit your code to include a constructor():
constructor(props) {
super(props);
this.state = {
error: null
};
// clear the error when sending a request
axios.interceptors.request.use(req => {
this.state = {
error: null
};
return req;
});
axios.interceptors.response.use(res => res, err => {
this.state = {
error: err
};
});
}
Notice that here, you are not using the setState() method which throws a warning in constructor but you are directly setting the state, which is allowed only inside the constructor().
I want to log any errors I am getting to the console and I have been getting this warning when I try to run my tests:
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.
in ErrorDialog (at ErrorDialog.test.js:36)
in WrapperComponent
This is what I have for my component:
import React from 'react';
import log from '../pathtologlevel';
import ErrorContext from '../pathtoErrorContext';
class Error extends React.Component {
static type = ErrorContext;
constructor(props, context) {
super(props, context);
this.state = { err: null };
this.processError = this.processError.bind(this);
this.list= [];
}
componentDidMount() {
const errorService = this.context;
errorService.attach((err) => {
log.info('changing state');
this.array.push(err);
this.processError();
});
}
processError() {
const { err } = this.state;
log.info('processNextError', this.array.length, err);
if (!err && this.array.length > 0) {
log.info('Displaying next error');
this.setState({ err: this.array.shift() });
}
}
render() {
const { err } = this.state;
log.info(err);
if (err) {
return this.processError;
}
return null;
}
}
export default Error;
You are returning the function this.processError instead of calling it in the render method. Just call the function this.processError() and you should not be getting any warnings.
I searched for error handling in react.
and i found componentDidCatch.
but componentDidCatch only catch in render.
I want to catch a error in method.
My source
import React from 'react';
class Child extends React.Component{
handleError = () => {
throw new Error('Error');
}
render() {
return (
<button onClick={this.handleError}>Error</button>
);
}
}
export default Main extends React.Component {
componentDidCatch(error,info) {
console.log(error, info); // NOT WORK
}
render() {
return (<Child/>);
}
}
You can check official React's Error handeling page where they use this code:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
// Display fallback UI
this.setState({ hasError: true });
// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
this codebox that is official Dan Abramov's (creator of React) CodeBox: https://codepen.io/gaearon/pen/wqvxGa?editors=0010