Quick story of my problem:
Absolutely no data is stored in my vuex state when the page loads
If the user is logged in(or has info stored in window.localStorage and therefore gets auto logged in) my vuex store retrieves all the info from a socket that requires authentication.
Then the user logs out, But my vuex state save still retains all its data
This would be a security issue as not logged in people(or hackers) on a public pc could view what the state was before the user logged out.
I have seen How to clear state in vuex store?
But I feel that this is a hack and should be avoided.
My current solution is just to refresh the page using location.reload();
Is there a better way to prevent this data leak?
All objects stored in Vue act as an observable. So if the reference of a value is changed/mutated it triggers the actual value to be changed too.
So, In order to reset the state the initial store modules has to be copied as a value.
On logging out of a user, the same value has to be assigned for each module as a copy.
This can be achieved as follows:
// store.js
// Initial store with modules as an object
export const initialStoreModules = {
user,
recruitment,
};
export default new Vuex.Store({
/**
* Assign the modules to the store
* using lodash deepClone to avoid changing the initial store module values
*/
modules: _.cloneDeep(initialStoreModules),
mutations: {
// reset default state modules by looping around the initialStoreModules
resetState(state) {
_.forOwn(initialStoreModules, (value, key) => {
state[key] = _.cloneDeep(value.state);
});
},
}
});
Then call commit("resetState"); when the user logs out.
Normal Approach
If user logs in, then you can add few boolean flags to ensure that user has been loggedin/loggedout.
So initial approach would be -
this.$store.commit('insertToken', {realtoken, isLoggedIn: true})
In vuex than,
insertToken (state, payload) {
state.token = payload.realtoken
state.isLoggedIn = payload.isLoggedIn
localStorage.setItem('token', payload.realtoken)
}
And when user logs out you should set all flags to false,
In component -
logout () {
this.$store.commit('logOut')
this.$router.replace('/login')
}
and in vuex,
logOut (state, payload) {
state.token = null
state.isLoggedIn = false
localStorage.setItem('token', null)
},
So by means of isLoggedIn and token you can tell router where to navigate by using term called Navigation Guards
Example -
const checkToken = () => {
if ((localStorage.getItem('token') == null) ||
(localStorage.getItem('token') == undefined)) {
return false
} else {
return true
}
}
// Navigation guards
if (to.path === '/') {
if (checkToken()) {
next()
} else {
router.push('/login')
}
}
This is the way I use when authentication is done by means of using token as part of interacting with Vuex.
This extension does a nice job
https://www.npmjs.com/package/vuex-extensions
With it installed I can just call reset in the Vuex Logout Action
logout(context) {
// do the logout stuff, such as
context.commit("setUser", {});
// On logout, clear all State, using vuex-extensions
this.reset();
// if using router, change to login page
router.replace("/login");
}
This might be late but I found window.localStorage.removeItem('vuex') useful. Thanks to Thomas von Deyen, https://github.com/championswimmer/vuex-persist/issues/52#issuecomment-413913598
Related
I'm trying to make a Post request on component Mount. But if user reloads the page or states changes, then the function is called again as I'm useEffect and it sends the request again. But I want any better thing where the Post request should be made once and if even the page refreshes the shouldn't be called again if it has been called.
I'm using the Function base component. and make Post requests using redux.
const Main = () => {
// ....
// Here I'm forcing user to login if there's user is logged in then want to make a silent post request, But it sends request everytime on state change.
useEffect(() => {
getLocalStorage()
if (!userInfo) {
setModalShow(true)
}
if (userInfo) {
dispatch(postRequest())
setModalShow(false)
}
}, [userInfo])
return (
<div>Some JSX </div>
)
}
export default Main
So need your help to fix that issue. Can we use localStorage to store the information either the post request is already have been made or any other better idea?
Best way is to use localstorage, not sure if my placements of setting ang getting value from localstorage are on the right spot.
const Main = () => {
// ....
// Here I'm forcing user to login if there's user is logged in then want to make a silent post request, But it sends request everytime on state change.
useEffect(() => {
getLocalStorage()
// Check if the value of logged is true initiali will be false until the
// first request if made
if (!!localStorage.getItem('logged')) {
setModalShow(true)
}
if (userInfo) {
dispatch(postRequest())
setModalShow(false)
// set the value when the request is finished
localStorage.setItem('logged', true)
}
}, [userInfo])
return (
<div>Some JSX </div>
)
}
export default Main
There is a package named redux-persist that you can save the state, for example in localStorage. You can use this package, and send post request if there is not any data in state.
Using localStorage for that purpose is pretty useful, you can save the information on post request whether it was made or not.
For a basic setup;
this could be like that:
const postRequestStatus = localStorage.getItem('postRequestMade') ? JSON.parse(localStorage.getItem('postRequestMade')) : null
useEffect(() => {
getLocalStorage()
if (!userInfo) {
setModalShow(true)
}
if (userInfo) {
setModalShow(false)
if (!postRequestStatus) {
dispatch(postRequest())
console.log('Post Request Made')
localStorage.setItem('postRequestMade', true)
}
}
}, [userInfo, postRequestStatus])
Here's a catch. As far there is information in localStorage, of postRequestMade true . The request won't be made. So some point on the site you should set any logic to clear it out where it is necessary.
Secondly, What if the request was not successful if there was an error from the server. Then, you should also consider error handling as well. As you mentioned you are using redux and I'm sure there would be Axios as well try the functionality like that:
useEffect(() => {
getLocalStorage()
if (!userInfo) {
setModalShow(true)
}
if (userInfo) {
setModalShow(false)
if (!postRequestStatus) {
dispatch(postRequest())
// That block will take care if request was successful
// After a successful request postRequestMade should be set to true.
if (success) {
console.log('Successful Request')
localStorage.setItem('postRequestMade', true)
}
}
}
}, [userInfo, postRequestStatus, success])
I have a login component which makes an API call with email and password to Ruby API and sets the isLoggedIn = true if the credentials are correct. When this is true, the navbar shows "logout" link and shows signin/signout when false. However, the issue is that it only works fine when page is not reloaded since I am not storing the session anywhere.
I am trying to set login state in local storage when the login is successful. It works fine in the login component, but doesn't when I try to read the local storage value in the other component. I assume this is because I am not storing it in shared state. Is it possible to store the value in state?
Login.js
axios
.post(apiUrl, {
user: {
email: email,
password: password,
},
})
.then((response) => {
if ((response.statusText = "ok")) {
setLoginStatus({ props.loginState.isLoggedIn: true });
localStorage.setItem('props.loginState.isLoggedIn', true);
console.log(response)
history.push("/");
}
})
App.js
let data = {
isLoggedIn: false,
user: {},
setSession:null
};
const [loginState, setLoginStatus] = useState(data);
const rememberMe = localStorage.getItem('loginState.isLoggedIn')
When I use console log the below either in App.js or Nav.js the value is null always.
localStorage.getItem('props.loginState.isLoggedIn');
The localStorage items as are saved as key value pairs. This is a string 'props.loginState.isLoggedIn', and will be access the same way from any component. Since you saved it with that key name it can only be accessed with that key name from any component.
I have a navbar where I only show certain menu items based off the user's role.
Here is the HTML (I removed all the menu items except one to simplify):
<v-speed-dial class="speed-dial-container contextual-text-menu" v-if="user && user.emailVerified" fixed top right
direction="bottom" transition="slide-y-transition">
<v-icon v-if="checkAuthorization(['superAdmin', 'admin', 'pastor', 'member']) === true" class="mt-2"
#click="sendComponent({ component: 'Dashboard' })">$admin</v-icon>
</v-speed-dial>
My method:
async checkAuthorization(permissions) {
if (permissions.length > 0) {
const temp = await this.$store.dispatch('UserData/isAuthorized', permissions)
return temp
}
},
Vuex store:
isAuthorized({
state
}, permissions) {
const userRoles = state.roles
if (permissions && userRoles) {
const found = userRoles.some(r => permissions.includes(r))
return found
}
},
All of my console logs show the correct values but the HTML is not responding accordingly.
Example: in this line of code checkAuthorization(['superAdmin', 'admin', 'pastor', 'member']) === true I added 'member' and I am logged in as a user that ONLY has the 'member' role. When looking through the console logs everything returns true so I should see this menu item but it does not show.
As someone pointed out in the comments, checkAuthorization is an async function and will return a Promise, so you cannot check for promise === true.
That aside, I would change isAuthorized to be a vuex getter and not an action, e.g.
getters: {
// ...
isAuthorized: (state) => (permissions) => {
if (permissions && state.roles) {
return state.roles.some(r => permissions.includes(r))
}
return false;
}
}
And update checkAuthorization to not return a promise e.g.
function checkAuthorization(permissions) {
if (permissions.length > 0) {
return this.$store.getters.isAuthorized(permissions);
}
return false;
}
What I usually do :
I add another user state as Unknown and make it the default state.
In main main.js (or main.ts) I call state.initialize(), which determines user's state.
And, the key thing is to use navigation guards. Instead of checking routing guards on router-link (or url or anywhere in this step), you should define it in the router. There is a router.beforeEach function you can use, so that you can check if user is authorized to use that route, and redirect the user to 401 page if the user don't have permission.
https://router.vuejs.org/guide/advanced/navigation-guards.html
I have a Vuex instance which loads data from API. The first time I access the store it should load the data from API, and when I access it again it should return the loaded data from the store.empresas. This is how my Vuex module looks like:
import Empresas from '#/api/empresas'
import moment from 'moment'
export default {
state: {
loaded: false,
lastLoadedDate: null,
empresas: []
},
getters: {
empresas: state => {
if (!state.loaded || moment().diff(state.lastLoadedDate, 'minutes') > 30) {
//Was not loaded yet or was loaded more than 30 minutes ago,
//Sould load from api -> actions.carregarEmpresas()
//Don't know how to proceed here
} else {
//Already loaded
return state.empresas
}
}
},
mutations: {
setEmpresas (state, payload) {
state.loaded = true
state.lastLoadedDate = moment()
state.empresas = payload
}
},
actions: {
carregarEmpresas ({ commit }) {
Empresas.listar()
.then(({ data }) => {
commit('setEmpresas', data.empresas)
})
}
}
}
The reason I need this is that I will need to access empresas in multiple files in my application, and I don't want to make an API call every time.
However, I don't know how to implement it inside the getter. Is it possible to do it?
This might be a duplicate of this post: Can I do dispatch from getters in Vuex. Check if the second answer helps you.
Basically, you shouldn't call actions from you getters. I would recommend you call the action at the initial load of the app. Then you will just use the getter without the conditional.
I have a react app and I am using geolocated to get users location.
Following the instructions for the initialization I have wrapped the component:
export default geolocated({
positionOptions: {
enableHighAccuracy: true,
},
userDecisionTimeout: 15000,
})(ShowPois);
As soon as the user accepts (allows) the location finding on the browser I want two things to happen.
First I need to set a flag when then location is available to the app, so I have this:
static getDerivedStateFromProps(props, state) {
if (!state.geolocatedReady && props.coords) {
return {
geolocatedReady: true
}
}
return null;
}
Notice that props.coords comes from geolocated
The second thing is that I want to complete an input box with the address of the location found. In order to do this I have to do a post request to an api to get the address, but the problem is I cannot use the getDerivedStateFromProps() method because the method must return a value, not a promise (made by axios post request).
So how can I make a post request and then set the state when a prop changes in the component?
getDerivedStateFromProps is only for edge cases. The case you have here sounds like a fit for componentDidUpdate.
componentDidUpdate() {
if(!this.state.geolocatedReady && this.props.coords) {
this.setState({
geolocatedReady: true,
});
this.getAddress(this.props.coords);
}
}
getAddress = async (coords) => {
const address = await mapApi.getAddress(coords);
// or whatever you want with it.
this.setState({
address
})
}