Accessing the data from localstorage from another route with vuex - javascript

i am getting the data from a config panel route setting it to the localstorage with vuex , storeJS:
const state = {
message: [],
// console.log(message);
sec: 0,
// other state
};
const getters = {
message: (state) => {
// console.log(this.state.message);
return state.message;
},
sec: (state) => {
return state.sec;
},
// other getters
};
const actions = {
setMessage: ({ commit, state }, inputs) => {
commit(
'SET_MESSAGE',
inputs.map((input) => input.message)
);
return state.message;
},
setSec: ({ commit, state }, newSecVal) => {
commit('SET_TIMEOUT', newSecVal);
return state.sec;
},
// other actions
};
const mutations = {
SET_MESSAGE: (state, newValue) => {
state.message = newValue;
localStorage.setItem('message', JSON.stringify(newValue)); ----->this
},
SET_TIMEOUT: (state, newSecVal) => {
state.sec = newSecVal;
localStorage.setItem('sec', JSON.stringify(newSecVal)); --->this
},
// other mutations
};
export default {
state,
getters,
actions,
mutations,
};
Now i am having a home route where i want to display this ,how can i access that?
I am getting the data (not the localstorage but the regular state data) with Mapgetters and i am using it like that:
computed: {
...mapGetters({
message: "message",
sec: "sec"
}),
how can i tell him that if there is nothing (when a page reloads ) to automaticcally get the data from localstorage.
This is my MOunted
mounted() {
this.$store.dispatch("SET_MESSAGE");
this.$store.dispatch("SET_SEC");
},

I will suggest you use this package to keep your state and local storage in sync vuex-persistedstate.
Alternatively, you can set your state like this.
const state = {
message: localStorage.getItem('message') || [],
// console.log(message);
sec: localStorage.getItem('sec') || '',
// other state
};

Related

How to dispatch action when a state changes in redux toolkit without using useEffect?

If I want to dispatch an action whenever a piece of state changes, currently how I do this is by listening to that state inside a component useEffect hook and dispatching an action whenever that state changes.
For example, let's say I have the following slice:
export const updateBar = createAsyncThunk("mySlice/updateBar", async () => {
const { data } = await axios.get("/some/api");
return data;
});
const mySlice = createSlice({
name: "mySlice",
initialState: {
foo: false,
bar: 0,
},
reducers: {
updateFoo: (state, { payload }) => {
state.foo = payload;
},
},
extraReducers: {
[updateBar.fulfilled]: (state, { payload }) => {
state.bar = payload;
},
},
});
Now if I want to update bar whenever foo changes, I have to go to the component side to add the following code:
const Component = () => {
const foo = useSelector((state) => state.mySlice.foo);
const dispatch = useDispatch();
useEffect(() => {
dispatch(updateBar());
}, [dispatch, foo]);
return <div></div>;
};
I am wondering is it possible to call updateBar whenever foo changes within the redux slice and without having to touch the component side, because I think that it would be cleaner if all state side effects are abstracted away from the component.
One solution is to create another thunk encapsulating the two update operations. We do the update foo operation in this thunk and use thunkAPI to dispatch update bar action.
We can continue to keep updateFoo action when you just want to update foo.
Thunk helps us encapsulate the logic of actions. Such as when are actions dispatched, what actions are dispatched, and their relationship, serial dispatch or concurrent dispatch.
E.g.
//#ts-nocheck
import { createAsyncThunk, createSlice, configureStore } from '#reduxjs/toolkit';
const mockApi = async () => {
return { data: 100 };
};
export const updateBar = createAsyncThunk('mySlice/updateBar', async () => {
const { data } = await mockApi();
return data;
});
export const updateFooWithBar = createAsyncThunk('mySlice/updateFooWithBar', (arg, thunkAPI) => {
// If bar depends on foo state or other state,
// you can use thunkAPI.getState() to get the whole state, pick what you need.
thunkAPI.dispatch(updateBar());
return arg;
});
const mySlice = createSlice({
name: 'mySlice',
initialState: {
foo: false,
bar: 0,
},
reducers: {
updateFoo: (state, { payload }) => {
state.foo = payload;
},
},
extraReducers: {
[updateBar.fulfilled]: (state, { payload }) => {
state.bar = payload;
},
[updateFooWithBar.fulfilled]: (state, { payload }) => {
state.foo = payload;
},
},
});
const store = configureStore({ reducer: mySlice.reducer });
store.subscribe(() => {
console.log(store.getState());
});
store.dispatch(updateFooWithBar('next foo')).then(() => {
store.dispatch(mySlice.actions.updateFoo('next next foo'));
});
Execution result:
{ foo: false, bar: 0 }
{ foo: false, bar: 0 }
{ foo: 'next foo', bar: 0 }
{ foo: 'next foo', bar: 100 }
{ foo: 'next next foo', bar: 100 }

Why the react component is not returning the current data from redux?

I have a react app that is connected with redux. The component has a form that makes a PUT call to the api when the form is submitted. When I submit the form, I can see that redux gets updated accordingly but when I try to access the redux state as a prop in my component, the props data does not return the current data and is off by 1. For example, here's the data in my redux store:
Redux store:
When I do console.log("THIS PROPS: ", this.props) in my component, I see that it accountError is showing up as null
When I dispatch the action again the second time, only then I see that I am getting the data from redux in my props:
Here is the code that I have currently:
OrgAccount.js
import { registerOrgAccount, getListOfOrgsAndAccts } from "../../store/actions";
handleSubmit = () => {
this.props.registerOrgAccount(this.state)
console.log("THIS PROPS: ", this.props)
if(this.props.accountError === null) {
this.toggleTab(this.state.activeTab + 1);
}
};
<Link
to="#"
className="btn w-lg"
onClick={() => {
if (this.state.activeTab === 1) {
this.handleSubmit();
}
}}
>
Next
</Link>
const mapStatetoProps = (state) => {
const { accounts, accountError, loading } = state.OrgAccount;
return { accounts, accountError, loading };
};
const mapDispatchToProps = (dispatch) => {
return {
getListOfOrgsAndAccts: () => {
dispatch(getListOfOrgsAndAccts())
},
registerOrgAccount: (data) => {
dispatch(registerOrgAccount(data))
},
}
}
export default connect(mapStatetoProps, mapDispatchToProps)(OrgAccount);
Reducer:
const initialState = {
accountError: null, accountsError: null, message: null, loading: null
}
const orgAccount = (state = initialState, action) => {
switch (action.type) {
case REGISTER_ORG_ACCOUNT:
state = {
...state,
account: null,
loading: true,
// accountError: null
}
break;
case REGISTER_ORG_ACCOUNT_SUCCESSFUL:
state = {
...state,
account: action.payload,
loading: false,
accountError: null
}
break;
case REGISTER_ORG_ACCOUNT_FAILED:
state = {
...state,
loading: false,
accountError: action.payload ? action.payload.response : null
}
break;
...
default:
state = { ...state };
break;
}
return state;
}
export default orgAccount;
Action
export const registerOrgAccount = (account) => {
return {
type: REGISTER_ORG_ACCOUNT,
payload: { account }
}
}
export const registerOrgAccountSuccessful = (account) => {
return {
type: REGISTER_ORG_ACCOUNT_SUCCESSFUL,
payload: account
}
}
export const registerOrgAccountFailed = (error) => {
return {
type: REGISTER_ORG_ACCOUNT_FAILED,
payload: error
}
}
Saga.js
import { registerOrgAccountSuccessful, registerOrgAccountFailed, getListOfOrgsAndAcctsSuccessful, getListOfOrgsAndAcctsFailed } from './actions';
import { putOrgAccount } from '../../../helpers/auth_helper';
function* registerOrgAccount({ payload: { account } }) {
try {
const response = yield call(putOrgAccount, {
orgId: account.orgId,
accountNumber: account.accountNumber,
accountName: account.accountName,
accountCode: account.accountCode,
urlLink: account.urlLink,
location: account.location,
accountType: account.accountType,
address: account.address,
city: account.city,
state: account.state,
zip: account.zip,
country: account.country,
email: account.email,
eula: "blah"
});
yield put(registerOrgAccountSuccessful(response));
} catch (error) {
yield put(registerOrgAccountFailed(error));
}
}
To understand the root cause here, I think it helps to know a little about immutability and how React rerenders. In short, React will rerender when it detects reference changes. This is why mutating a prop, wont trigger a rerender.
With that in mind, at the time you call handleSubmit, this.props.accountError is simply a reference to a value somewhere in memory. When you dispatch your action and your state is updated, a new reference will be created, which will trigger a rerender of your component. However the handleSubmit function that was passed to your element still references the old this.props.accountError, which is why it is still null.
You could get around this by implementing your check in the componentDidUpdate lifecycle method. E.g. something like this:
componentDidUpdate(prevProps) {
if (prevProps.accountError === null && this.props.accountError !== null) {
this.toggleTab(this.state.activeTab + 1)
}
}

Use Vuex in Nuxt

I was able to fetch data and display them using Nuxt's Fetch API, but I want to utilize Vuex instead.
store/index.js:
import Axios from 'axios'
export const getters = {
isAuthenticated (state) {
return state.auth.loggedIn
},
loggedInUser (state) {
return state.auth.user
}
}
export const state = () => ({
videos: []
})
export const mutations = {
storeVideos (state, videos) {
state.videos = videos
}
}
export const actions = {
async getVideos (commit) {
const res = await Axios.get(`https://api.themoviedb.org/3/movie/popular?api_key=${process.env.API_SECRET}&page=${this.currentPage}`)
commit('storeVideos', res.data)
}
}
pages/test.vue:
<template>
<Moviecards
v-for="(movie, index) in $store.state.videos"
:key="index"
:movie="movie"
:data-index="index"
/>
</template>
<script>
...
fetch ({ store }) {
store.commit('storeVideos')
},
data () {
return {
prevpage: null,
nextpage: null,
currentPage: 1,
pageNumbers: [],
totalPages: 0,
popularmovies: []
}
},
watch: {
},
methods: {
next () {
this.currentPage += 1
}
}
}
...
The array returns empty when I check the Vue Dev Tools.
In fetch(), you're committing storeVideos without an argument, which would set store.state.videos to undefined, but I think you meant to dispatch the getVideos action:
export default {
fetch({ store }) {
// BEFORE:
store.commit('storeVideos')
// AFTER:
store.dispatch('getVideos')
}
}
Also your action is incorrectly using its argument. The first argument is the Vuex context, which you could destructure commit from:
export const actions = {
// BEFORE:
async getVideos (commit) {} // FIXME: 1st arg is context
// AFTER:
async getVideos ({ commit }) {}
}

Resetting a vuex module's state to some initialState value

I'm attempting to come up with a method which can reset a modules state back to some initial value, and so far I am coming up o a few conceptual issues.
My base store is the following:
const store = new Vuex.Store({
modules: {
moduleA: moduleA,
},
actions: {
resetAllState: ({ dispatch }) => {"moduleA");
},
resetModuleState: (currentModule) => {
// Perform the reset here...somehow?
}
}
});
The moduleA.js store is created as follows with an intialState:
const initialState = () => ({
helloWorld: {}
});
const moduleA = {
namespaced: true,
state: {
initialState: initialState(),
...initialState(),
}
};
export default moduleA;
So I utilise a spread operator to create the following:
const moduleA = {
namespaced: true,
state: {
initialState: initialState(),
helloWord: {}
}
};
I can then mutate state.helloWorld whilst keeping a copy of the state.initialState...
So, my question now is, within the following global store
resetModuleState: (currentModule) => {
// Perform the reset here...somehow?
}
action, how do I actually perform the reset?
I have treid this as a global way of resetting state:
resetAllState: function() {
let defaultState = {};
Object.keys(store.state).forEach((key) => {
defaultState = {
...defaultState,
[key]: store.state[key].initialState ? store.state[key].initialState : store.state[key],
};
});
store.replaceState(Object.assign({}, defaultState));
},
But to no luck...?
The above code is having issue, the actions can have only access to
{ commit, dispatch, getters } as params
In above code "resetModuleState" takes modueleName in the first parameter, but it will have an object, so directly access the module object by below approach and reset the stathe by just calling initialState() function from each module you have
use this code inside the action "resetAllState"
resetAllState: function({ dispatch }) {
this._modules.root.forEachChild((childModule) => {
childModule.state.initialState();
});
},

Vuex not keeping currentUser state on page refresh

So I am Building a Vue.js SPA using Rails 6 api as the backend and Vue-cli (legacy webpack template)
When I sign a user in, everything works fine, I can see the users details, it sets my setCurrentUser mutation and state, as soon as I click away from my dashboard, I loose all my user's state. the vue dev tool panel essentially shows everything reset back to false.
I am rather new to Vue / Vuex so this may be an oversight on my part.
My signin method to grab the current user:
methods: {
signin () {
let formData = new FormData()
formData.append('user[email]', this.user.email)
formData.append('user[password]', this.user.password)
this.$http.plain.post('/signin', formData, { emulateJSON: true })
.then(response => this.signinSuccessful(response))
.catch(error => this.signinFailed(error))
},
signinSuccessful (response) {
if (!response.data.csrf) {
this.signinFailed(response)
return
}
this.$http.plain.get('/api/v1/me')
.then(meResponse => {
this.$store.commit('setCurrentUser', { currentUser: meResponse.data, csrf: response.data.csrf })
this.error = ''
this.$router.replace('/dashboard')
this.flashMessage.show({
status: 'info',
title: 'Signed In',
message: 'Signin successful, welcome back!'
})
})
.catch(error => this.signinFailed(error))
},
signinFailed (error) {
this.user.error = (error.response && error.response.data && error.response.data.error)
this.$store.commit('unsetCurrentUser')
},
checkSignedIn () {
if (this.$store.state.signedIn) {
this.$router.replace('/dashboard')
}
}
}
This image shows the api call to signin completes and returns the user object.
This image shows the Vue Panel sets the currentUser state and has the user object
Now when I go to do a page refresh or move away from my dashboard, I loose everything that was in state.
So like I said I'm brand new to Vuex, I have tried to use Vue.set and $set on my mutations in store.js but this did not remedy the problem either..?
Here is my store.js file:
import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'
Vue.use(Vuex)
export const store = new Vuex.Store({
state: {
currentUser: {},
signedIn: false,
csrf: null
},
mutations: {
setCurrentUser (state, { currentUser, csrf }) {
state.currentUser = currentUser
state.signedIn = true
state.csrf = csrf
},
unsetCurrentUser (state) {
state.currentUser = {}
state.signedIn = false
state.csrf = null
},
refresh (state, csrf) {
state.signedIn = true
state.csrf = csrf
}
},
getters: {
isOwner (state) {
return state.currentUser && state.currentUser.role === 'owner'
},
isManager (state) {
return state.currentUser && state.currentUser.role === 'manager'
},
isAdmin (state) {
return state.currentUser && state.currentUser.role === 'admin'
},
isUser (state) {
return state.currentUser && state.currentUser.role === 'user'
},
isSignedIn (state) {
return state.signedIn === true
}
},
plugins: [
createPersistedState({
})
]
})
Any assistance here would be greatly appreciated!
Add plugins: [createPersistedState()], when creating store instance
export default new Vuex.Store({
plugins: [createPersistedState()],}

Categories