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();
});
},
Related
My state in vuex store is huge.
Is there a way to reset all the data in state in one go, instead of manually setting everything to null?
I have just found the great solution that works for me.
const getDefaultState = () => {
return {
items: [],
status: 'empty'
}
}
// initial state
const state = getDefaultState()
const actions = {
resetCartState ({ commit }) {
commit('resetState')
},
addItem ({ state, commit }, item) { /* ... */ }
}
const mutations = {
resetState (state) {
// Merge rather than replace so we don't lose observers
// https://github.com/vuejs/vuex/issues/1118
Object.assign(state, getDefaultState())
}
}
export default {
state,
getters: {},
actions,
mutations
}
Thanks to Taha Shashtari for the great solution.
Michael,
Update after using the below solution a bit more
So it turns out that if you use replaceState with an empty object ({}) you end up bricking reactivity since your state props go away. So in essence you have to actually reset every property in state and then use store.replaceState(resetStateObject). For store without modules you'd essentially do something like:
let state = this.$store.state;
let newState = {};
Object.keys(state).forEach(key => {
newState[key] = null; // or = initialState[key]
});
this.$store.replaceState(newState);
Update (from comments): What if one needs to only reset/define a single module and keep the rest as they were?
If you don't want to reset all your modules, you can just reset the modules you need and leave the other reset in their current state.
For example, say you have mutliple modules and you only want to reset module a to it's initial state, using the method above^, which we'll call resetStateA. Then you would clone the original state (that includes all the modules before resetting).
var currentState = deepClone(this.state)
where deepClone is your deep cloning method of choice (lodash has a good one). This clone has the current state of A before the reset. So let's overwrite that
var newState = Object.assign(currentState, {
a: resetStateA
});
and use that new state with replaceState, which includes the current state of all you modules, except the module a with its initial state:
this.$store.replaceState(newState);
Original solution
I found this handy method in Vuex.store. You can clear all state quickly and painlessly by using replaceState, like this:
store.replaceState({})
It works with a single store or with modules, and it preserves the reactivity of all your state properties. See the Vuex api doc page, and find in page for replaceState.
For Modules
IF you're replacing a store with modules you'll have to include empty state objects for each module. So, for example, if you have modules a and b, you'd do:
store.replaceState({
a: {},
b: {}
})
You can declare an initial state and reset it to that state property by property. You can't just do state = initialState or you lose reactivity.
Here's how we do it in the application I'm working on:
let initialState = {
"token": null,
"user": {}
}
const state = Vue.util.extend({}, initialState)
const mutations = {
RESET_STATE(state, payload) {
for (let f in state) {
Vue.set(state, f, initialState[f])
}
}
}
I am not sure what you use case is, but I had to do something similar. When a user logs out, I want to clear the entire state of the app - so I just did window.reload. Maybe not exactly what you asked for, but if this is why you want to clear the store, maybe an alternative.
If you do a state = {}, you will remove the reactivity of the properties and your getters mutations will suddenly stop working.
you can have a sub-property like:
state: {
subProperty: {
a: '',
lot: '',
of: '',
properties: '',
.
.
.
}
}
Doing a state.subProperty = {} should help, without losing the reactivity.
You should not have a state too big, break them down to different modules and import to your vuex store like so:
import Vue from 'vue'
import Vuex from 'vuex'
import authorization from './modules/authorization'
import profile from './modules/profile'
Vue.use(Vuex)
export const store = new Vuex.Store({
modules: {
authorization,
profile
}
})
now in your individual files:
// modules/authorization.js
import * as NameSpace from '../NameSpace'
import { someService } from '../../Services/something'
const state = {
[NameSpace.AUTH_STATE]: {
auth: {},
error: null
}
}
const getters = {
[NameSpace.AUTH_GETTER]: state => {
return state[NameSpace.AUTH_STATE]
}
}
const mutations = {
[NameSpace.AUTH_MUTATION]: (state, payload) => {
state[NameSpace.AUTH_STATE] = payload
},
}
const actions = {
[NameSpace.ASYNC_AUTH_ACTION]: ({ commit }, payload) => {
someService.login(payload.username, payload.password)
.then((user) => {
commit(NameSpace.AUTH_MUTATION, {auth: user, error: null})
})
.catch((error) => {
commit(NameSpace.AUTH_MUTATION, {auth: [], error: error})
})
}
}
export default {
state,
getters,
mutations,
actions
}
If you should want to clear the state you can just have a mutation implement:
state[NameSpace.AUTH_STATE] = {
auth: {},
error: null
}
Here's a solution that works in my app. I created a file named defaultState.js.
//defaultState.js
//the return value is the same as that in the state
const defaultState = () => {
return {
items: [],
poles: {},
...
}
}
export default defaultState
And then Where you want to use it
//anywhere you want to use it
//for example in your mutations.js
//when you've gotten your store object do
import defaultState from '/path/to/defaultState.js'
let mutations = {
...,
clearStore(state){
Object.assign(state, defaultState())
},
}
export default mutations
Then in your store.js
import Vue from 'vue';
import Vuex from 'vuex';
import actions from './actions';
import getters from './getters';
import mutations from './mutations'; //import mutations
import state from './state';
Vue.use(Vuex);
export default new Vuex.Store({
actions,
mutations,
state,
getters,
});
and That's it
If you want to reset your entire state you can use the built in replaceState method.
Given a state set in index.js:
const state = { user: '', token: '', products: [] /* etc. */ }
const initialStateCopy = JSON.parse(JSON.stringify(state))
export const store = new Vuex.Store({ state, /* getters, mutations, etc. */ })
export function resetState() {
store.replaceState(initialStateCopy)
}
Then in your vue component (or anywhere) import resetState:
import { resetState } from '#/store/index.js'
// vue component usage, for example: logout
{
// ... data(), computed etc. omitted for brevity
methods: {
logout() { resetState() }
}
}
Based on these 2 answers (#1 #2) I made a workable code.
My structure of Vuex's index.js:
import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'
import { header } from './header'
import { media } from './media'
Vue.use(Vuex)
const store = new Vuex.Store({
plugins: [createPersistedState()],
modules: {
header,
media
}
})
export default store
Inside each module we need to move all states into separated var initialState and in mutation define a function resetState, like below for media.js:
const initialState = () => ({
stateOne: 0,
stateTwo: {
isImportedSelected: false,
isImportedIndeterminate: false,
isImportedMaximized: false,
isImportedSortedAsc: false,
items: [],
stateN: ...
}
})
export const media = {
namespaced: true,
state: initialState, // <<---- Our States
getters: {
},
actions: {
},
mutations: {
resetState (state) {
const initial = initialState()
Object.keys(initial).forEach(key => { state[key] = initial[key] })
},
}
}
In Vue component we can use it like:
<template>
</template>
<script>
import { mapMutations } from 'vuex'
export default {
name: 'SomeName',
data () {
return {
dataOne: '',
dataTwo: 2
}
},
computed: {
},
methods: {
...mapMutations('media', [ // <<---- define module
'resetState' // <<---- define mutation
]),
logout () {
this.resetState() // <<---- use mutation
// ... any code if you need to do something here
}
},
mounted () {
}
} // End of 'default'
</script>
<style>
</style>
Call router.go() or this.$router.go()
That will refresh the page and your state will be reset to how it was when the user first loaded the app.
Myself has read above and implemented a solution. could help you as well!!
All objects stored in Vue act as an observable. So if reference of a value is changed/mutated it triggers the actual value to be changed too.
So, Inorder to reset the state the initial store modules has to be copied as a value.
On logging out of an user, the same value has to be assigned for each modules as a copy.
This can be achieved as following:
Step 1: Create a copy of your initial module.
// store.ts
// 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
[types.RESET_STATE](state: any) {
_.forOwn(initialStoreModules, (value: IModule, key: string) => {
state[key] = _.cloneDeep(value.state);
});
},
}
});
Step 2: Call the action to mutate the state to initial state.
// user_action.ts
const logout = ({ commit }: any) => {
commit(types.LOGOUT_INIT);
new UserProxy().logout().then((response: any) => {
router.push({
name: 'login',
});
// reset the state
commit(types.RESET_STATE);
}).catch((err: any) => {
commit(types.LOGOUT_FAIL, err);
});
};
You could take it easy by tiny package: vuex-extensions
Check out the example on CodeSandbox.
Creating Vuex.Store
import Vuex from 'vuex'
import { createStore } from 'vuex-extensions'
export default createStore(Vuex.Store, {
plugins: []
modules: {}
})
Store resets to initial State
// Vue Component
this.$store.reset()
// Vuex action
modules: {
sub: {
actions: {
logout() {
this.reset()
}
}
}
}
You can do this
index.js
...
const store = new Vuex.Store({
modules: {
...
}
})
store.initialState = clone(store.state)
store.resetState = () => {
store.replaceState(store.initialState)
}
export default store
Other place
this.$store.resetState()
function initialState () {
return { /* .. initial state ... */ }
}
export default {
state: initialState,
mutations: {
reset (state) {
// acquire initial state
const s = initialState()
Object.keys(s).forEach(key => {
state[key] = s[key]
})
}
}
}
This is an official recommendation
issue
if you clear your complete vuex store use:
sessionStorage.clear();
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
};
I'm migrating a class-based react system to hooks, and I'm facing some challenges which I can't understand.
Take a look at the snippet below:
async onSearchforOptions(elementId) {
await this.props.onFetchOperatingSystems()
//(3) [{…}, {…}, {…}]
console.log(this.props.operatingSystems)
}
In this method, I am dispatching an action to update the redux state, and right after this I'm logging the result to make sure the information was fetched and updated in the redux state.
The problem is that in an application which uses functional components, the result doesn't seem the same. Instead of updating the redux state and recovering the info right after, it simply doesn't seem to update the state, even if I'm using "await" and the very same actions and reducers the class component is using:
const onSearchforOptions = async (elementId) => {
await props.onFetchOperatingSystems()
//[]
console.log(props.operatingSystems)
}
My connection for both components (the class component and the functional component):
const mapStateToProps = state => {
return {
operatingSystems: state.operatingSystemReducer.operatingSystems
}
}
const mapDispathToProps = dispatch => {
return {
onFetchOperatingSystems: () => dispatch(actions.fetchOperatingSystems())
}
}
export default connect(mapStateToProps, mapDispathToProps)(productsForm)
My actions:
export const fetchOperatingSystemsStart = () => {
return {
type: actionTypes.FETCH_OPERATING_SYSTEMS_START
}
}
export const fetchOperatingSystemsFail = (error) => {
return {
type: actionTypes.FETCH_OPERATING_SYSTEMS_FAIL,
error: error
}
}
export const fetchOperatingSystemsSuccess = (operatingSystems) => {
return {
type: actionTypes.FETCH_OPERATING_SYSTEMS_SUCCESS,
operatingSystems: operatingSystems
}
}
export const fetchOperatingSystems = () => {
return dispatch => {
dispatch(fetchOperatingSystemsStart())
return axios.get(url)
.then(response => {
const fetchedData = []
for (let key in response.data) {
fetchedData.push({
...response.data[key],
id: response.data[key].id
})
}
dispatch(fetchOperatingSystemsSuccess(fetchedData))
})
.catch(error => {
if (error.response !== undefined) dispatch(fetchOperatingSystemsFail(error.response.data))
else dispatch(fetchOperatingSystemsFail(error))
})
}
}
My Reducer:
const initialState = {
operatingSystems: [],
loading: false
}
const fetchOperatingSystemsStart = (state) => {
return updateObject(state, { loading: true })
}
const fetchOperatingSystemsSuccess = (state, action) => {
return updateObject(state, { operatingSystems: action.operatingSystems, loading: false })
}
const fetchOperatingSystemsFail = (state) => {
return updateObject(state, { loading: false })
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.FETCH_OPERATING_SYSTEMS_START: return fetchOperatingSystemsStart(state)
case actionTypes.FETCH_OPERATING_SYSTEMS_SUCCESS: return fetchOperatingSystemsSuccess(state, action)
case actionTypes.FETCH_OPERATING_SYSTEMS_FAIL: return fetchOperatingSystemsFail(state)
default: return state
}
}
export default reducer
updateObject function:
export const updateObject = (oldObject, updatedProperties) => {
const element = {
// The values of the object oldObject are being spread, at the same time the values of
// updatedProperties are (I'm taking out the attributes of both objects with the spread operator).
// In this case, since the names of the attributes are the same,
// the attributes (which were spread) of the first object will have their values replaced
// by the values of the second object's attributes.
...oldObject,
...updatedProperties
}
return element
}
My Goal:
Accoding to the snippet below, my goal is to dynamically search for options and update it in my form, which is in the component state.
const onSearchforOptions = async (elementId) => {
let elementUpdated
switch (elementId) {
case 'operatingSystem': {
await props.onFetchOperatingSystems()
console.log(props.operatingSystems)
elementUpdated = {
'operatingSystem': updateObject(productsForm['operatingSystem'], {
selectValue: {
value: props.selectedElement.operatingSystem ? props.selectedElement.operatingSystem.id : undefined,
label: props.selectedElement.operatingSystem ? props.selectedElement.operatingSystem.name : undefined
},
elementConfig: updateObject(productsForm['operatingSystem'].elementConfig, {
options: props.operatingSystems
})
})
}
break
}
case 'productType': {
await props.onFetchProductTypes()
elementUpdated = {
'productType': updateObject(productsForm['productType'], {
selectValue: {
value: props.selectedElement.productType ? props.selectedElement.productType.id : undefined,
label: props.selectedElement.productType ? props.selectedElement.productType.name : undefined
},
elementConfig: updateObject(productsForm['productType'].elementConfig, {
options: props.productTypes
})
})
}
break
}
default: break
}
const productsFormUpdated = updateObject(productsForm, elementUpdated)
setProductsForm(productsFormUpdated)
}
The props object passed to the render function initially is not going to be mutated; rather the props passed to your component on its next render will be updated. This is more in keeping with the flux architecture. You fire-and-forget an action, the reducer runs, and then your component is re-rendered with new props.
Before, this same thing was happening, but the new props were being assigned to this.props again. Since there's no meaningful "this" anymore, you can't use this pattern. Besides, depending on this behavior is not idiomatically the React way of doing things.
Update:
I think this is like a great number of cases I've also encountered where the React team seemed to overcorrect for a lot of use cases of people handling derived state poorly (see You Probably Don't Need Derived State). I've seen plenty of cases, like yours, where the now-deprecated componentWillReceiveProps lifecycle method solved this problem for class-based components very nicely.
Thankfully, useEffect now gives you something like a replacement. Think about it this way: when props.operatingSystems changes, you want to perform the effect of changing the state of your form. It's an unfortunate double update issue, but you had that before. Here's how you could go about writing that:
const [productsForm, setProductsForm] = useState(...);
useEffect(() => {
// Handle the case where props.operatingSystems isn't initialized?
if (!props.operatingSystems || !props.selectedElement.operatingSystem)
return;
setProductsForm({
...productsForm,
operatingSystem: {
...productsForm.operatingSystem,
selectValue: {
value: props.selectedElement.operatingSystem.id,
label: props.selectedElement.operatingSystem.name
},
elementConfig: {
...productsForm.operatingSystem.elementConfig,
options: props.operatingSystems
}
}
});
}, [props.operatingSystems]);
The way this works is that your effect code is only kicked off whenever your props.operatingSystems value changes since the last render. You can do a similar sort of effect for product types.
Another option which is maybe less elegant is for your async function that kicked off the redux actions to also resolve to a value which you can then use in your state setting code:
const operatingSystems = await props.onFetchOperatingSystems();
// ...now set your state
i usually implements thunks in a functional component like:
`export default connect(mapStateToProps, {fetchOperatingSystems})(productsForm)`
can you try this and comment back.
How to save in localStorage using Redux, only a particular piece of state?
For example, my state in list reducer is defined as follows:
state = {
companies: [],
currentDisplay: '',
recordNotFound: false,
}
This is my combineReducer file:
const rootReducer = combineReducers({
list: listReducer,
form: formReducer
})
localStorage.js:
export const loadState = () => {
try {
const serializedState = localStorage.getItem('state')
if (serializedState === null) {
return undefined;
}
return JSON.parse(serializedState)
} catch (err) {
return undefined
}
}
export const saveState = (state) => {
try {
const serializedState = JSON.stringify(state)
localStorage.setItem('state', serializedState)
} catch (err) {
// to define
}
}
And after browser reloads I want only companies: [{obj1}, {obj2}, ...] array to be preloaded and the rest of state reset to default values f.e. currentDisplay: '' to be equal ''.
Right now responsible for this operation code looks like this:
store.subscribe(() => {
saveState({
list: store.getState().list
})
})
And it stores the whole list obviously...
I guess I could easily reset these parameters in React using setState(), but would like to do this properly.
You can save just the companies parameter on localStorage if you don't need the other parameters to be loaded.
store.subscribe(() => {
saveState({
companies: store.getState().list.companies
})
})
I'm storing my settings with redux-persist and would like to ignore some of them to have them reset on every restart, e.g. after a crashing.
It's possible to add an array of reducer-names as blacklist or whitelist, but I'd like to ignore specific keys, e.g. settings.isLoggedIn instead of settings.
// ...
function configureStore(initialState) {
const store = createStore(
RootReducer,
initialState,
enhancer
);
persistStore(store, {
storage: AsyncStorage,
blacklist: ['router', 'settings'] // works, 'settings.isLoggedIn' doesn't.
}, () => {
// restored
});
return store;
}
// ...
Do I have to create another reducer or does anyone a solution to this problem?
Thanks in advance!
As per the documentation, the blacklist parameter contains: 'keys (read: reducers) to ignore', so I am afraid it is not possible to implement the behaviour that you want. You can try and implement that functionality yourself, but I think the codebase of the package is really focused on blacklisting reducers instead of properties (see this). I am afraid that the only solution is to create a separate reducer for your non-persistent keys (in my experience it is not much of a hassle).
Use transforms for save separate fields, for example for username in redux-form MyForm inside state.form.MyForm:
const formName = `MyForm`
const formTransform = createTransform(
(inboundState, key) => {
return {
...inboundState,
[formName]: {
values: {
username: _.get(inboundState, `${ MyForm }.values.username`)
}
}
}
},
(outboundState, key) => {
return outboundState
},
{ whitelist: [`form`] }
)
persistStore(store, {
whitelist: [
`form`
],
transforms: [
formTransform
]
})
You can use Nested Persists for this.
import { persistStore, persistReducer } from 'redux-persist';
const rootPersistConfig = {
key: 'root',
storage: storage,
blacklist: ['auth']
}
// here you can tell redux persist to ignore loginFormData from auth reducer
const authPersistConfig = {
key: 'auth',
storage: storage,
blacklist: ['loginFormData']
}
// this is your global config
const rootReducer = combineReducers({
auth: persistReducer(authPersistConfig, authReducer),
other: otherReducer,
})
// note: for this to work, your authReducer must be inside blacklist of
// rootPersistConfig
const myReducerConfig = {
key: "cp",
storage: storage,
blacklist: ["authReducer"],
debug: true
};
you have to create reducer for every prop you want to save.
A simple solution is to save the whole reducer in the whitelist and after in the reducer using 'persist/REHYDRATE' action to filter only the keys that you want to keep.
Example:
// configureStore.js
const persistConfig = {
keyPrefix: 'webapp',
whitelist: ['filters'],
}
// filtersReducer.js
const projectsBase = {
[KEYS.SORT]: PROJECTS_SORT_TYPE.NAME,
[KEYS.TEXT]: '',
}
const itemsBase = {
[KEYS.SORT]: ITEMS_SORT_TYPE.INDEX,
[KEYS.TEXT]: '',
}
const base = {
[KEYS.PROJECTS]: projectsBase,
[KEYS.ITEMS]: itemsBase
}
export const filters = (state = base, action) => {
const { type } = action
switch (type) {
case PERSIST_REHYDRATE_ACTION_TYPE: {
if (action.payload.filters) {
const filters = action.payload.filters
const projectsSort = _.get(filters, [KEYS.PROJECTS, KEYS.SORT])
const itemsSort = _.get(filters, [KEYS.ITEMS, KEYS.SORT])
const newBase = { ...base,
[KEYS.PROJECTS]: {
[KEYS.SORT]: projectsSort
},
[KEYS.ITEMS]: {
[KEYS.SORT]: itemsSort
}}
state = newBase
}
}
break
default:
break
}
return state
}
As #martinarroyo mentioned to create a separate reducer which is a good option and if we follow it and create a seperate reducer for errors, we can simply return an empty state as the default;
const initialState = {
error: null
}
export default errorReducer = (state = initialState, action) => {
switch (action.type) {
...
default:
return {
...state,
error: null
}
}
}
This will clear the state everytime we visit the site as defualt is setting the errors to null.