How to use React error boundary with promises - javascript

I'm trying to implement generic error page for any unhandled exceptions in React application. However, it seems like the error boundary doesn't work with exceptions thrown by promises such as API operations. I know I could catch the exception at component level and re-throw it at the render method. But this is boilerplate I would like to avoid. How to use error boundary with promises?
I'm using latest React with hooks and react-router for navigation.

You can do by creating a HOC which will take two parameters First one is the component and second the promise. and it will return the response and loading in the props Please find the code below for your reference.
HOC
import React, { Component } from "react";
function withErrorBoundary(WrappedComponent, Api) {
return class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false, response: null, loading: false };
}
async componentDidMount() {
try {
this.setState({
loading: true
});
const response = await Api;
this.setState({
loading: false,
response
});
console.log("response", response, Api);
} catch (error) {
// throw error here
this.setState({
loading: 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 (
<WrappedComponent
response={this.state.response}
loading={this.state.loading}
{...this.props}
/>
);
}
};
}
export default withErrorBoundary;
How to use HOC
import ReactDOM from "react-dom";
import React, { Component } from "react";
import withErrorBoundary from "./Error";
class Todo extends Component {
render() {
console.log("this.props.response", this.props.response);
console.log("this.props.loading", this.props.loading);
return <div>hello</div>;
}
}
const Comp = withErrorBoundary(
Todo,
fetch("https://jsonplaceholder.typicode.com/todos/1").then(response =>
response.json()
)
);
ReactDOM.render(<Comp />, document.getElementById("root"));
Please check the codesandbox for your refrence

Related

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

This was my code
import React, { Component } from "react";
import axios from "axios";
class App extends Component {
state = {
invites: [],
};
constructor() {
super();
axios.get(`http://localhost:8080/security/allUser`).then((res) => {
console.log(res.data);
this.setState({ invites: res.data });
});
}
render() {
return (
<div>
{this.state.invites.map((invite) => (
<h2 key={invite.id}>{invite.name}</h2>
))}
<h1>Welcome</h1>
</div>
);
}
}
export default App;
state and setState have worked for me alright for more complex codes before. This one keeps showing the same error
This is the error:
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 App component.
Add a componentDidMount() and write your request call inside it. When the component first loads the componentDidMount function will run.
Actually you can make request in constructor (React allows it but you shouldnt) but you have to make the request only after the component has been mounted or just before it is about to be mounted.
So it is wise to make your requests in componentDidMount().
import React, { Component } from "react";
import axios from "axios";
class App extends Component {
constructor(props) {
super(props);
this.state = {
invites: [],
};
}
componentDidMount() {
axios.get(`http://localhost:8080/security/allUser`).then((res) => {
console.log(res.data);
this.setState({ invites: res.data });
});
}
render() {
return (
<div>
{this.state.invites.map((invite) => (
<h2 key={invite.id}>{invite.name}</h2>
))}
<h1>Welcome</h1>
</div>
);
}
}
export default App;

Can't re-render components using react-starter-kit

Working with kriasoft/react-starter-kit to create a web application.
It fetches objects from my api server, and shows them on a page.
The app should show a loading icon while fetching the data.
My code doesn't re-render a component after fetching objects.
It continues to show 'Loading...', even though I want to see 'Data loaded!'.
import React from 'react';
import fetch from 'isomorphic-fetch'
class Search extends React.Component {
constructor(...args) {
super(...args);
this.getObjectsFromApiAsync = this.getObjectsFromApiAsync.bind(this);
this.state = {
isLoading: true,
};
}
async getObjectsFromApiAsync() {
try {
const response = await fetch('http://localhost:3030/api');
const content = await response.json();
// console.log(content)
this.setState({isLoading: false});
} catch(e) {
console.error(e);
}
}
componentWillMount() {
this.getObjectsFromApiAsync();
};
render() {
if (this.state.isLoading) {
return (
<p>Loading...</p>
);
}
return (
<p>Data loaded!!</p>
);
}
}
Solved the problem by adding res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000'); to my api server.
The problem was not in the React.
Here is the solution resource:
https://stackoverflow.com/a/18311469/6209648

Using NetInfo middleware in React Native with Redux

I want to test in all components whether the user has connection to the internet.
I could use NetInfo in each component, but since I am using redux, I thought it could be done easier with a middleware(?).
I have used
import { createStore, applyMiddleware } from 'redux';
const netInfo = store => next => action => {
const listener = (isConnected) => {
store.dispatch({
type: types.NET_INFO_CHANGED,
isConnected,
});
};
NetInfo.isConnected.addEventListener('change', listener);
NetInfo.isConnected.fetch().then(listener);
return next(action);
};
const store = createStore(AppReducer, applyMiddleware(netInfo));
where AppReducer is just combineReducers(navReducer, netInfoReducer, ...).
It does seem to work, but I am really worried if this performs well enough. It seems it is only run once, but I am never removing the listener or anything.
Is this how you normally would do if you want to populate all components with an isConnected variable?
I would create a Higher-Order Component for this:
import React, { Component } from 'react';
import { NetInfo } from 'react-native';
function withNetInfo(WrappedComponent) {
return class extends Component {
constructor(props) {
super(props);
this.state = {};
this.handleChange = this.handleChange.bind(this);
NetInfo.isConnected.fetch().then(this.handleChange);
}
componentDidMount() {
NetInfo.isConnected.addEventListener('change', this.handleChange);
}
componentWillUnmount() {
NetInfo.isConnected. removeEventListener('change', this.handleChange);
}
handleChange(isConnected) {
this.setState({ isConnected });
}
render() {
return <WrappedComponent isConnected={this.state.isConnected} {...this.props} />;
}
}
}
export default withNetInfo;
Then you can wrap whatever component you would like to render:
class MyComponent extends Component {
render() {
const { isConnected } = this.props;
return(
<View>
<Text>
{`Am I connected? ${isConnected}`}
</Text>
</View>
);
}
}
export default withNetInfo(MyComponent);
Bonus: if you want to keep the statics methods of your original component (if you have defined some) you should use the package hoist-non-react-statics to copy the non-react specific statics:
import React, { Component } from 'react';
import { NetInfo } from 'react-native';
import hoistStatics from 'hoist-non-react-statics';
function withNetInfo(WrappedComponent) {
class ExtendedComponent extends Component {
constructor(props) {
super(props);
this.state = {};
this.handleChange = this.handleChange.bind(this);
NetInfo.isConnected.fetch().then(this.handleChange)
}
componentDidMount() {
NetInfo.isConnected.addEventListener('change', this.handleChange);
}
componentWillUnmount() {
NetInfo.isConnected. removeEventListener('change', this.handleChange);
}
handleChange(isConnected) {
this.setState({ isConnected });
}
render() {
return <WrappedComponent isConnected={this.state.isConnected} {...this.props} />;
}
}
return hoistStatics(ExtendedComponent, WrappedComponent);
}
export default withNetInfo;
There shouldn't be a performance issue using middleware to keep "isConnected" in your redux store, but you would want to make sure the listener is only added once. I use https://github.com/michaelcontento/redux-middleware-oneshot to achieve that.
I considered middleware, too, but was also afraid how to handle the sub/unsub. I've decided to go with adding and removing the listener in componentDidMount and componentWillUnmount of my AppContainer class, which holds the rest of the app in my MainNavigator. This class' lifecycle should follow that of the app, and thus make sure to sub/unsub correctly. I am, however, also going to use a redux action to set the status and listen to it in the relevant views to show a 'no connection' banner.

Can't access module in componentWillMount

I have this module in ./src/utils/errorPopup.js
import { Toast } from 'native-base';
export default function errorPopup(problem = 'process your request') {
Toast.show({
text: `Unfortunately we cannot ${problem}. An error report has been created and we will look into it shortly. Please get in touch with us if the problem doesn't disappear in the next 6 business hours.`,
position: 'bottom',
buttonText: 'Okay'
});
}
I then try and call it inside the componentWillMount method of ./src/components/JobList.js and it says Uncaught ReferenceError: errorPopup is not defined.
import React, { Component } from 'react';
import { View } from 'react-native';
import axios from 'axios';
import errorPopup from '../utils/errorPopup';
export default class JobsList extends Component {
constructor() {
super();
this.state = {
someObject: undefined
};
}
componentWillMount() {
axios.get('http://api.test.dev:5000/')
.then(response => {
this.setState({ someObject: response.data })
})
.catch(() => {
errorPopup('fetch your accepted and available jobs');
});
}
render() {
return (
<View />
);
}
}
Strangely enough, when I debug and use the console, I can see an object _errorPopup2.
How can I call errorPopup() without changing the design pattern of utils that I adopted from here?

Using middleware to check for users session react redux

I've spent about 1 week reading up on redux before plunging into anything sizeable. After completing most of the tutorials I've done I realised, ok I understand redux but how the hell do I make a complex system :P
I started going about by creating my systems actions:
function requestLogin(creds) {
return {
type: LOGIN_REQUEST,
isFetching: true,
isAuthenticated: false,
creds
}
}
function receiveLogin(user) {
return {
type: LOGIN_SUCCESS,
isFetching: false,
isAuthenticated: true,
id_token: user.id_token
}
}
function loginError(message) {
return {
type: LOGIN_FAILURE,
isFetching: false,
isAuthenticated: false,
message
}
}
But how can I with each router used (using react-router) check to see if the user has a session after storing the users logged in state in the redux state?
I wanted to create something that gets executed with each view. Just simply write a function that exec()'s in each view?
Yes, you create a function that executes whenever you go to a route that requires a login.
import LoginActions from '../loginActions';
const requireLogin = (nextState, replace) => {
store.dispatch(LoginActions.CheckLogin());
const {login} = store.getState();
if (!login.isAuthenticated)
replace('/login');
};
Call it in your router:
<Router component={Root}>
<Route path="/dashboard" onEnter={requireLogin} component={dashboard}/>
</Router>
You can implement auth filter for paths requiring the user to be authenticated using Higher Order Components.
Create wrapping component
import React from 'react';
import { connect } from 'react-redux';
export default function(ComposedComponent) {
class AuthFilter extends React.Component {
// triggered when component is about to added
componentWillMount() {
if (!this.props.userAuthenticated) {
console.log("navigate to login");
this.context.router.push('/login');
}
}
// before component updated
componentWillUpdate(nextProps) {
if (!nextProps.userAuthenticated) {
console.log("navigate to login");
this.context.router.push('/login');
}
}
render() {
return <ComposedComponent {...this.props} />
}
}
AuthFilter.contextTypes = {
router: React.PropTypes.func.isRequired
}
function mapStateToProps(state) {
return { userAuthenticated: state.authenticated };
}
return connect(mapStateToProps)(AuthFilter);
}
And then add wrapping to your route component as:
Route path="/asset" component={AuthFilter(AssetRoute)}/>

Categories

Resources