Redux store overwritten instead of merged - react-native

I am writing an app in React Native using Redux. When I execute an action, instead of updating the store, it seems to replace the store, defining only the values that are explicitly stated in my reducer.
I am using handleActions from the redux-actions package. An example reducers looks like this;
import { handleActions } from 'redux-actions';
export default handleActions({
DOCUMENT_GET_ALL_PENDING: state => ({
isRefreshing: true,
}),
DOCUMENT_GET_ALL_FULFILLED: (state, action) => ({
document: action.payload.documents,
isRefreshing: false,
}),
}, { isRefreshing: false, documents: [] });
For example, when it reacht DOCUMENT_GET_ALL_PENDING, the documents array gets set to undefined.
Here is what the creation of my store looks like;
I am using redux-promise-middleware, redux-devtools-extension and thunk middleware.
const store = createStore(
rootReducer,
defaultState,
composeWithDevTools(applyMiddleware(promiseMiddleware(), thunk)),
);
Any and all help or suggestions is welcome.

You need to merge your states with the previous one. Right now you're just returning the new state values.
There are many ways to do it but you can use the es6 spread operator.
DOCUMENT_GET_ALL_PENDING: state => ({
...state,
isRefreshing: true,
}),
DOCUMENT_GET_ALL_FULFILLED: (state, action) => ({
...state,
document: action.payload.documents,
isRefreshing: false,
})

Related

Pinia shared reactivity in stores

Is it possible to share reactive state between stores in Pinia, for example:
export const useMainStore = defineStore('mainStore', {
state: () => ({
data: [{name: "some name", amount: useSomeStore().amount}]
}),
export const useSomeStore = defineStore('someStore', {
state: () => ({
amount: 0
}),
The idea is that useSomeStore.amount value is synced with the useMainStore data value.
So when I change the amount in useSomeStore to 5 I expect that data value in useMainStore will change accordingly to:
[{name: "some name", amount: 5}]
I know that I can subscribe to the store or use watchers but is there a better solution for this?
I made working solution using storeToRefs but not sure if there are drawbacks to this.
https://codesandbox.io/s/competent-breeze-wylkoj?file=/src/stores/tomato.ts
Remember pinia states are reactive objects.
Therefore, you can always set a computed on one of them which references another store's state.
Generic example:
const useStoreOne = defineStore('one', {
state: () => ({
foo: 'bar'
})
})
const useStoreTwo = defineStore('two', {
state: () => ({
foo: computed({
get() { return useStoreOne().foo },
set(val) { useStoreOne().foo = val }
})
})
})
Note: storeToRefs does the same as above. So you can write storeTwo as:
const useStoreTwo = defineStore('two', {
state: () => ({
foo: storeToRefs(useStoreOne()).foo
})
})
But it's kind of pointless. Why would you want to use useStoreTwo().foo anywhere instead of using useStoreOne().foo directly?
Make no mistake: the above pattern sets a two-way binding between the two store's foos. But, the second store's foo is useless. Anywhere you use it, you could be using the first one's foo directly.
On general principles, when you come across this pattern (or across the need for it), it should raise a flag: you're using state management because you want "one source of truth". Not more.

redux-toolkit state change in extraReducer does not initiate rerender

I am trying to logout and purge the store at the same time, so on click I dispatch this:
dispatch({type: PURGE, key: 'root', result: () => { } });
Redux persist catches it, and reports purging the store. Great.
In another reducer I catch that dispatch, and remove my access token like this:
import { PURGE } from 'redux-persist/es/constants';
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
setAccessToken(state: AuthState, action: PayloadAction<Auth>): void {
state.accessToken = action.payload.accessToken;
state.expiresIn = action.payload.expiresIn;
},
},
extraReducers: {
[PURGE]: (state: AuthState, action: string): void => {
state.accessToken = initialState.accessToken;
state.expiresIn = initialState.expiresIn;
},
},
});
The PURGE reducer actually is called, and modifies the state, but still no re-rendering happens. so redux must not pick that up. But according to the docs the Redux toolkit uses a Proxy object for the state and does a comparison to see if it's modified.
Things I tried:
state = initialState;
and
state = { ...initialState };
Didn't work. The store works, and holds data, other actions work. How do I proceed?
EDIT: Further debugging revealed that my own reducer was called BEFORE the redux-persist reducer, and redux-logger reported that my reducer did not change the state at all.
I'm facing a similar issue (not re-rendering) and came by this thread today:
Seems like you can't replace state objects entirely.
From: https://redux-toolkit.js.org/usage/immer-reducers
Sometimes you may want to replace the
entire existing state, either because you've loaded some new data, or
you want to reset the state back to its initial value.
WARNING A common mistake is to try assigning state = someValue
directly. This will not work! This only points the local state
variable to a different reference. That is neither mutating the
existing state object/array in memory, nor returning an entirely new
value, so Immer does not make any actual changes.
const initialState = []
const todosSlice = createSlice({
name: 'todos',
initialState,
reducers: {
brokenTodosLoadedReducer(state, action) {
// ❌ ERROR: does not actually mutate or return anything new!
state = action.payload
},
fixedTodosLoadedReducer(state, action) {
// ✅ CORRECT: returns a new value to replace the old one
return action.payload
},
correctResetTodosReducer(state, action) {
// ✅ CORRECT: returns a new value to replace the old one
return initialState
},
},
})
So
state = initialState;
would be
return initialState;
This turned out to be the solution:
extraReducers: {
[PURGE]: (state: UserState, action: string): UserState => ({
...state,
...initialState,
}),
},
I don't understand why, as modifying the state object should work too, according to the documentation:
To make things easier, createReducer uses immer to let you write
reducers as if they were mutating the state directly. In reality, the
reducer receives a proxy state that translates all mutations into
equivalent copy operations.

Dispatching Redux Data and Get the State

I stuck on Redux Implementation during developing an app using React Native and Redux. I do this for the first time and followed this example.
I've already installed Redux and React Native Navigation. I would like to save the state containing data for countries (the user picked a country and would like to keep the choice by the time when it browses to all screens).
Good. I've created a component that could be seen to all screens like this:
LinksScreen.navigationOptions = {
headerTitle: 'Links',
headerRight: <CountriesPickButton/>,
};
Next, I visualize the button and wait for a change in the component. By default, it should show primary country. Next, the user clicks on the button and it opens a modal where has a dropdown menu. For example, I show you the default fetching a country:
import { connect } from 'react-redux'
import store from '../../redux/countries'
export default class CountriesPick extends Component {
render() {.... // here is the button and modal, etc. It's work.
}
constructor(props, context) {
super(props, context);
this.state = store.getState();
store.subscribe(() => {
this.setState(store.getState());
});
this.defaultCountry(251);
}
async defaultCountry(countryId) {
return fetch(URL)
.then((response) => response.json())
.then((responseJson) => {
for (const key of Object.keys(responseJson.result)) {
// this works for current screen: this.setState({ defaultCountry: responseJson.result[key], selectedCountry: responseJson.result[key].country_id });
store.dispatch({ defaultCountry: responseJson.result[key], selectedCountry: responseJson.result[key].country_id , type: 'countries' });
}
return responseJson.result;
})
.catch((error) => {
console.error(error);
});
state = {
showModal: false,
countries: [],
selectedCountry: 0,
defaultCountry: [],
type: 'countries'
};
}
Without store.dispatch({}) I can change the state with the country but it has not to share between screens. That's because I started with Redux.
Here is the Redux code ():
import { createStore } from 'redux'
const defaultState = {
showModal: false,
countries: [],
selectedCountry: 0,
defaultCountry: [],
type: 'countries'
};
function store(state = defaultState) {
return state;
}
export default createStore(store);
Something is not like it should be. When I invoke store.dispatch({...}) it's not changing the state, it returns the default array. I guess I should use <Provider></Provider> in App.js to catch every change but first, I need to understand what I wrong?
Is it connected at all? In the example that I followed, I did not see connect(). Also, I'm not sure I'm using type properly.
Thank you in advance.
Problems here are the following:
Example on the link you provided is bad to say the least. Do not follow it
You said to be using react-native-navigation, but the code you provided comes from react-navigation. I suggest using the latter, especially for starters
Your createStore code is not going to work, as reducer for the store should be a function of state and action
With that being said, you should definitely see Basic Tutorial of redux with examples. You will almost never have to do store.getState() or store.dispatch() while using react with redux, as react-reduxpackage (included in the tutorial I linked) will do this for you. You will instead declare dependency between your store state and props your component receives

vuex base state is shared between modules

I am using multiple vuex modules in nuxt store and I want to use the same base state in multiple modules like this:
// ~/utils/Sharedstore.js
export default {
state: {
byId: {},
allIds: [],
}
},
// ~store/entities/myEntity.js
import SharedStore from '~/utils/SharedStore';
export const state = () => ({ ...SharedStore.state });
But it doesn't work, whenever i mutate one state the state of all modules will be changed.
When I do this for all my modules it works:
// ~store/entities/myEntity.js
export const state = () => ({
byId: {},
allIds: [],
});
Problem is I would like to have the duplicated base states in one place (SharedStore.state). Why does it not work when importing and how can I fix it?
I found a fix:
export const state = () => JSON.parse(JSON.stringify(ModelStore.state));
Need to deep clone the object using JSON.parse(JSON.stringify(obj)) instead of spreading.
I guess the contents of byId and allIds still get used by reference when spreading?

VueStoreFront: Custom module: state is updated, component not

I’m creating a custom module that fetches data in the “afterRegistration” hook and saves the result into the store.
The state is updated (can see the update in VueDevTools), but the component has still the default state. What I’m doing wrong?
// afterRegistration
export function afterRegistration ({Vue, config, store, isServer}) {
store.dispatch(${KEY}/isVisible)
}
// component
export default {
name: 'Test',
components: { Fragment },
props: {
…
},
computed: {
isVisible: el => el.$store.getters['myModule/simpleTest']
}
}
You have lost the reactivity. Set the initial value of simpleTest in state object to whatever you like (from the context i see it's Boolean). There is no getter or setter for this field, if it's not in initial state.
Ok, I found out that I need to dispatch in an AsyncLoader
// afterRegistration
export function afterRegistration ({Vue, config, store, isServer}) {
AsyncDataLoader.push({
execute: ({ route, store, context }) => {
return new Promise ((resolve, reject) => {
store.dispatch(`${KEY}/getInlineTranslationConfig`)
.then(() => resolve(null))
})
}
})
Now it works!