Zustand not rerendering once token is set - javascript

TLDR: I could be doing this wrong, but I had this working with Redux but after switching to Zustand I can't figure this out. After logging in and confirming with devtools, a token is set. Yet Zustand is not rerendering the component. So after logging in I'm directed to my home component but no data is shown (all fetches fail due to no token). If I refresh the page, all is good.
The issue is my "apiClient" uses the token, but seems like the token isn't getting updated before use.
Store:
let token = localStorage.getItem("token");
const apiClient = axios.create({
baseURL: apiURL,
headers: {
"...etc, Token=${token}`,
},
});
I need apiClient in the store.js to pass to my slices, for example:
const useStore = create(
devtools((set, get) => ({
auth: authSlice(set, get, apiClient),
views: viewsSlice(set, get, apiClient),
}))
);
It would make sense to be able to get the state of the token rather than localStorage, but I can't use get() outside a slice in the store. What would be a better approach?

Related

Can we use axios interceptor for http calls made by window.fetch in react?

Our react app is using window.fetch method for the http calls in most or all of its areas. Before, it had only one token for accessing the content, and what we did is that, we added a header with every request individually, to send the token etc. Below is the sample code of such request:
useEffect(() => {
// GET S3 CREDANTIONS
fetch("/dashboard/de/question/s3credentials", {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${loginReducer}`,
},
})
But now we have configured our api to access two tokens, one for accessing content, the other for refreshing the access token. And for that, after searching from internet, we decided to add an axios interceptor, to check if the access_token is valid, before each http call.
So, I made the interceptor and put it inside the index.js of the react app. to check globally for every http request before sending it, if the access_token is valid; and refresh if it is not. I got this idea from this link:
https://blog.bitsrc.io/setting-up-axios-interceptors-for-all-http-calls-in-an-application-71bc2c636e4e
Other than this link, I studied it from many other websites including stackoverflow, and I understood this more so applied it.
Below is the axios interceptor's code in the index.js file (as a whole):
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import axios from "axios";
import AdminIndex from "./AdminPanel/Pages/AdminIndex";
import Cookies from "js-cookie";
// React Redux
import allReducers from "./reducers/index";
import { createStore } from "redux";
import { Provider } from "react-redux";
import StateLoader from "./reducers/PresistedState";
const stateLoader = new StateLoader();
let store = createStore(
allReducers,
stateLoader.loadState(),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
axios.interceptors.request.use(
async (config) => {
let serverCallUrl = new URL(config.url);
if (serverCallUrl.pathname.includes("/superuser/login")) return config;
let access_token = Cookies.get("access");
let refresh_token = localStorage.getItem("refresh_token");
await AdminIndex.hasAccess(access_token, refresh_token);
return config;
},
(error) => {
return Promise.reject(error);
}
);
store.subscribe(() => {
stateLoader.saveState(store.getState());
});
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
there is some problem with the axios interceptor I think. Because when I start the app and enter the login credentials, the app says wrong username and password etc.. I wonder if the problem is because our app is using the fetch method instead of axios? Because we are trying to implement axios interceptor for the fetch http calls. And just for the login code, we have applied axios to authenticate the user. Below is the handleSubmit function of login:
const handleSubmit = async (e) => {
e.preventDefault();
axios({
method: "post",
url: "/superuser/login",
headers: {
"content-type": "application/x-www-form-urlencoded;charset=utf-8",
},
data: `username=${values.username}&useremail=${values.useremail}`,
})
.then((res) => {
const { access_token, refresh_token } = res.data;
Cookies.set("access", access_token);
localStorage.setItem("refresh_token", refresh_token);
props.set_login(res.data.access_token);
history.push("/admin/panel/papers");
})
.catch((err) => setDialogStatus(true));
};
Please have a look what is causing the error to not being able to log in the app. If I remove the interceptor, the app logs in successfully, but then I won't be able to refresh the access_token when it expires. In the interceptor, I have put the function to refresh the access_token and store it again in the props of app. Hopefully I have clearly stated my issue. But still if something is missing, please don't block the question just add a comment and I will immediately respond to it.
Thank you in advance!
No! axios interceptors is the internal mechanism of axios; which means, it only intercepts the requests that are sent by the axios library itself. If we want to intercept the window.fetch requests, we'll need to setup custom interceptors for that, for example filtering in the catch block if the response from api is a 403 error, then perform specific action.
But even still, the most preferred approach is to just use the axios library totally and be replaced all over the project.

How to make sure the axios request is using the latest token from the redux store?

I have an accessToken in my redux store. I am creating a axios client like this:
import axios from 'axios';
import store from '../redux/store';
const apiClient = axios.create({
baseURL: 'http://localhost:5002/api/',
headers: {
Authorization:`Bearer ${store.getState().auth.accessToken}` // ???
}
});
apiClient.defaults.headers.common['Authorization'] = `Bearer ${store.getState().auth.accessToken}`; // ???
export default apiClient;
I want every request that is sent by the apiClient to contain the current value of the accessToken from the redux store (at the time the request is sent). And with the lines that are marked with ???, I'm not sure whether the Authorization header is only set once when the apiClient is created, or whether the value of the header also changes when the accessToken in the store has been updated.
Later I want to make a request like this:
apiClient.get<User[]>('Users/Friends');
And I want the request to include the accessToken that is in the store at the time the request is sent not at the time the client is created. Is there a way to achieve this while the creation of the client or do I always have to put the Authorization header config with the current accessToken in every request?
Now I added this code:
const apiClient = axios.create({
baseURL: 'http://192.168.178.188:5002/api/'
});
apiClient.interceptors.request.use(config => {
config.headers['Authorization'] = `Bearer ${store.getState().auth.accessToken}`
return config;
});
Is this the most elegant solution?

Koa-session making new session per request?

I'm setting up a Vue app with Koa as the backend. I've implemented a simple login system, using koa-session for sessions. This is all fine when I access the server directly (actually navigating to my server port and performing a request), but when I use fetch to access the data from my front end port, it does not recall each fetch as the same session and creates a new session each time. This is problematic because it doesn't detect the user as being logged in. What ends up happening is that a new session key is created each time I access the API, even without refreshing the page.
Here is my Koa-session config file:
import Koa from "koa";
import session from "koa-session";
import { getCookie } from "../actions/getCookie";
import { setCookie } from "../actions/setCookie";
import { destroyCookie } from "../actions/destroyCookie";
export const config: Readonly<Partial<session.opts>> = {
store: {
get: getCookie,
set: setCookie,
destroy: destroyCookie
},
key: "api:sess",
maxAge: 86400000,
httpOnly: true,
renew: true,
signed: false
};
export const useSession = (app: Koa) => session(config, app);```
Turns out it was with my fetch. I had credentials: "same-origin" instead of credentials: "include" and it messed everything up.

Avoid new axios request each time vue component is rendered

I'm very much new to Vue so please excuse my lack of knowledge here.
In one of my (child) components (Product_Collection.vue) I make an axios request to get some products from my Shopify store via their GraphQL API:
data: () => {
return {
products: null
}
},
methods: {
getProducts: function(){
// 1. axios code here...
// 2. ...then assign result to "this.products" data property
}
}
The products are displayed as thumbnails (let's say there's 10 t-shirts). I then click a product to view it in more detail with more product info and images etc (very much an Amazon-like experience).
The product is shown on it's own in a Product_Single.vue component. So far so good.
But here's the problem...
When I click back to the products page (Product_Collection.vue) (the page with the 10 t-shirts on) the axios request to the Shopify API gets called again and the component is re-rendered from scratch.
My question is how do I tell Vue to stop fetching the data from the Shopify API each time the component is rendered? Thank you
Sounds like you want cache-control, which Axios supports with an extension.
https://github.com/kuitos/axios-extensions
import axios from 'axios';
import { cacheAdapterEnhancer } from 'axios-extensions';
const http = axios.create({
baseURL: '/',
headers: { 'Cache-Control': 'no-cache' },
// cache will be enabled by default
adapter: cacheAdapterEnhancer(axios.defaults.adapter)
});
http.get('/users'); // make real http request
http.get('/users'); // use the response from the cache of previous request, without real http request made
http.get('/users', { cache: false }); // disable cache manually and the the real http request invoked
Or you could go a more complex route and cache the results yourself into an object, and check if that object exists, but then you'll have to deal with when you want to expire the data in that object.
If you use a cache-control, and cache-control headers and expiry, you can drive all that from the API layer and not have to deal with manually managed stale data in the application.
If you are not using Vue Router, and hiding the list page, you can try a simple option.
Instead of v-if, use v-show. The component will be hidden and displayed again. It won't be re-created.
Also, where do you call the API? From created or mounted hook?
If You are using GraphQL then there is the Apollo state management way which integrates nicely with graphQL. Check this out: https://dev.to/n_tepluhina/apollo-state-management-in-vue-application-8k0
So instead of rewriting the app with axios and adding vuex in the mixture, maybe Apollo client would be more convenient

React / Redux and Swagger client

I'm trying to figure out the best way to structure a React / Redux app that will primarily use a swagger client for api access.
The problem is I'm not entirely sure where to store a reference to the swagger client. After logging in and obtaining a JWT auth token, I need to tell all subsequent requests to add the authorize header. With axios this is trivial because it persists it's headers until told otherwise. It doesn't appear the swagger client does this. So ideally, I would create a swagger client once upon login, add the header info and just reference it for all future requests (that way too it only fetches the schema json once in a single page application).
Since I'm doing this in the context of an action, would it be best to store the Swagger client in the Redux store (and how would I accomplish that)? Or would I create a static instance of it outside of Redux?
// app init
const createStoreWithMiddleware = applyMiddleware(promise)(createStore);
const store = createStoreWithMiddleware(reducers);
export const swaggerClient = { instance: authService.createFromState().then(() => {
ReactDOM.render(
<Provider store={store}></Provider>
...
);
});
do some login stuff, create swagger client:
// redux action
import { swaggerClient } from '../index';
// ... do login, get bearerToken
Swagger({
url: 'https://localhost/swagger/v1/swagger.json',
requestInterceptor(req) {
req.headers['Authorization'] = `Bearer ${bearerToken}`;
return req;
}
}).then((client) => {
// store reference for all future ajax calls
swaggerClient.instance = client;
});
and in case the page is refreshed, we need to rebuild the swagger client from the bearerToken in local storage
// authService
import { swaggerClient } from '../index';
function createFromState() {
// if authentication is known from localstorage, we can rebuild
// a swagger client
if(isAuthenticated()) {
const authentication = getAuthentication();
return Swagger({
url: 'https://localhost/swagger/v1/swagger.json',
requestInterceptor(req) {
req.headers['Authorization'] = `Bearer ${authentication.bearerToken}`;
return req;
}
}).then((client) => {
swaggerClient.instance = client;
return client;
});
}
}
I'm a little confused if this is the right direction, probably a newbie question. Having to wait for the swagger client to load while restoring from localstorage seems a kinda crazy way to do this (to prevent race conditions on future calls).

Categories

Resources