Can I persist environments that I pass to the tree on creation? #1990
-
Hi all. What I am trying to achieve is saving the API configs, particularly the headers, to apply Authorization bearer token on the requests made by the tree's models. |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments
-
Hi @hamzakat! The environment can not be part of the model snapshot sadly, so the axios API instance that you inject can not be persisted in that way. You could however sync the API configs with e.g. localStorage outside of MST and use this potential value in localStorage on initialization to get around this. It is often useful to have the token(s) be part of a model so that you can e.g. render logged in/logged out views depending on if the user is authenticated, so something like this might give some inspiration: Example import axios from "axios";
import { flow, types as t, getRoot } from "mobx-state-tree";
export const AuthStore = t
.model("AuthStore", {
accessToken: t.maybeNull(t.string),
refreshToken: t.maybeNull(t.string)
})
.views((self) => ({
get isLoggedIn() {
return self.refreshToken !== null;
}
}))
.actions((self) => {
let refreshTokensPromise = null;
const refreshTokens = flow(function* () {
// No catch clause because we want the caller to handle the error.
try {
const res = yield axios({
method: "POST",
url: "/api/token/refresh/",
data: {
refresh: self.refreshToken
}
});
self.accessToken= res.data.access;
} finally {
refreshTokensPromise = null;
}
});
return {
// Multiple requests could fail at the same time because of an old
// accessToken, so we want to make sure only one token refresh
// request is sent.
refreshTokens() {
if (refreshTokensPromise) return refreshTokensPromise;
refreshTokensPromise = refreshTokens();
return refreshTokensPromise;
},
logIn: flow(function* (email, password) {
const tokenRes = yield axios({
method: "POST",
url: "/api/token/",
data: {
email,
password
}
});
self.accessToken = tokenRes.data.access;
self.refreshToken = tokenRes.data.refresh;
}),
logOut() {
self.accessToken = null;
self.refreshToken = null;
}
};
})
.actions((self) => {
return {
authRequest: flow(function* (conf) {
const authConf = {
...conf,
headers: {
...conf.headers,
Authorization: `Bearer ${self.accessToken}`
}
};
try {
return yield axios(authConf);
} catch (error) {
const { status } = error.response;
// Throw the error to the caller if it's not an authorization error.
if (status !== 401) throw error;
try {
yield self.refreshTokens();
} catch (err) {
// If refreshing of the tokens fail we have an old
// refreshToken and we must log out the user.
self.logOut();
// We still throw the error so the caller doesn't
// treat it as a successful request.
throw err;
}
authConf.headers.Authorization = `Bearer ${self.accessToken}`;
return yield axios(authConf);
}
})
};
});
const RootStore = t.model({
authStore: AuthStore
// ...
});
/**
* Extension for extending models deeper down the tree
* with an authenticated axios instance.
*/
const withRequest = (self) => ({
views: {
get request() {
const rootStore = getRoot(self);
if (!rootStore.authStore) {
throw new Error("The model needs to be part of the RootStore tree!");
}
return rootStore.authStore.authRequest;
}
}
});
// ...
const SomeDeepModel = t
.model({
foo: "foo"
})
.extend(withRequest)
.actions((self) => ({
getFoo: flow(function* () {
const res = yield self.request({ url: "/api/foo/" });
self.foo = res.data.foo;
})
})); |
Beta Was this translation helpful? Give feedback.
-
Wow, that was a fast helpful response! Thank you so much, it works great with |
Beta Was this translation helpful? Give feedback.
Hi @hamzakat!
The environment can not be part of the model snapshot sadly, so the axios API instance that you inject can not be persisted in that way. You could however sync the API configs with e.g. localStorage outside of MST and use this potential value in localStorage on initialization to get around this.
It is often useful to have the token(s) be part of a model so that you can e.g. render logged in/logged out views depending on if the user is authenticated, so something like this might give some inspiration:
Example