Reputation: 3164
Most of my project is built with React class components so I am looking for a solution that won't require me to refactor my class components to functional components. The problem I am having is that I have a file with some helper functions that enable calling an API. In that helper file, I also want to handle errors. One of my errors could result in logging the user out. In my App.js
component, I have the following state and functions for logging a user out:
export const AppContext = React.createContext("app");
class App extends Component {
constructor() {
super();
this.state = {
loggedInStatus: "NOT_INITIALIZED",
user: {},
};
}
handleLogout = () => {
request.delete("/logout").then(response => {
this.setState({
loggedInStatus: "NOT_LOGGED_IN",
user: {},
});
}).catch(error => {
console.log("error", error);
});
};
render() {
return(
<AppContext.Provider
value={{
state: this.state,
handleLogout: this.handleLogout
}}
>
<BrowserRouter>
{code that renders certain pages}
</BrowserRouter>
</AppContext.Provider>
)
}
}
I've also created this HOC:
import React from 'react';
import {AppContext} from '../App.js'
export function withAppContext(Component) {
return function WrapperComponent(props) {
return (
<AppContext.Consumer>
{state => <Component {...props} context={state} />}
</AppContext.Consumer>
);
};
}
I can import and use this context in any of my class components and call the handleLogout
function that is provided via the context. However, I have this helper file and I want to access the handleLogout
function in the helper file rather than some child component. Here is my helper file:
import axios from 'axios';
const request = (function(){
function url(){
return window.$url;
}
return {
get: function(uri, params={}){
return axios.get(url() + uri, {withCredentials: true, params: params})
},
post: function(uri, data){
return axios.post(url() + uri, data, {withCredentials: true})
},
patch: function(uri, data){
return axios.patch(url() + uri, data, {withCredentials: true})
},
delete: function(uri){
return axios.delete(url() + uri, {withCredentials: true})
},
handleError: function(error){
//I want to call the handleLogout function here
}
}
})();
export default request;
I currently call these other helper functions from within a class component like so:
import request from "./RequestHelper";
class CustomerForm extends Component{
constructor(props){
super(props);
this.state = {
isSubmitting: false
}
}
handleSubmit = () => {
request.get("/someurl").catch((err) => {
//I call request.handleError(err) here
//which will log the user out depending on the error.
});
}
}
Upvotes: 1
Views: 3368
Reputation: 2885
Context is meant to pass props from one component to another. Since in your case you have handleLogout
tied to your component, you can't use it outside of components tree.
If you want to use it everywhere in your code and not only in react, you can create some service that you can subscribe to in components, so when you logging out, your components would be notified about it. The most plain and simple implementation would be this:
class LogoutService {
// you maintain list of subscribers
subscrribers = new Set();
// Any part of code can subscribe with callback to do necessary stuff
subscribe(fn) {
this.subscribers.add(fn);
// wheen component unmounts, it should unsubscribe
return function unsubscribe() {
this.subscribers.delete(fn);
};
}
// your main function that do stuff and then triggers all subscribers
logout() {
return new Promise((resolve) => {
resolve();
}).then(() => this.subscribers.forEach((fn) => fn()));
}
}
// you export this instance which can be imported wherever in your application as a singleton
let logoutService = new LogoutService();
Your compoents now can subscribe to such service and will be notified if logout event occured. And your non components can trigger logout anywhere.
Here is an example where I trigger logout from one react app and it notifies another
Upvotes: 1
Reputation: 202696
Instead of creating a self-invoked request
, expose out the ability to pass a React context (or any other config object, etc) to it.
For example:
const request = (context = {}) => {
function url() {
return window.$url;
}
return {
get: function (uri, params = {}) {
return axios.get(url() + uri, { withCredentials: true, params: params });
},
post: function (uri, data) {
return axios.post(url() + uri, data, { withCredentials: true });
},
patch: function (uri, data) {
return axios.patch(url() + uri, data, { withCredentials: true });
},
delete: function (uri) {
return axios.delete(url() + uri, { withCredentials: true });
},
handleError: function (error) {
// Access the enclosed context
if (error === 404) context.logout();
}
};
};
Usage
import { context } from './path/to/context';
...
const requestWithContext = request(context);
requestWithContext
.get("/somePath")
.catch(requestWithContext.handleError);
I will advise that this appears to unnecessarily couple your axios request utility to your UI/context, in a sub-optimal way. It may be easier/cleaner to incorporate request
into your context provider instead, and simply handle the axios failures directly in the .catch
block of the Promise chain.
Upvotes: 2