http://redux.js.org/docs/api/createStore.html
[preloadedState] (any): The initial state. You may optionally specify
it to hydrate the state from the server in universal apps, or to
restore a previously serialized user session.
I can successfully initialize store with the preloadedState parameter.
However there are times I need to change the structure of the state.
As an example, I initially had
{
totalCount: 3,
usedCount: 1
}
now I want to change it to
{
totalCount: 3,
unusedCount: 2
}
Then stored state in the first structure won't be valid now.
At least I want to discard old state and start afresh from the new initialState.
I'm storing state in the server and using it as the preloadedState param.
Is there a way to discard the server stored state when state structure changes?
I''m sharing my solution.
I created HOC reducer (http://redux.js.org/docs/recipes/reducers/ReusingReducerLogic.html)
export function versionedReducer(reducerFunction) {
return (state, action) => {
const isInitializationCall = state === undefined
if (isInitializationCall) {
return state
}
const { version } = reducerFunction(undefined, {type: undefined})
if (!version) {
throw "should versioned"
}
const shouldAcceptPreloadedState = state.version && state.version >= version
if (!shouldAcceptPreloadedState) {
state = undefined
}
return reducerFunction(state, action)
}
}
Related
The bounty expires tomorrow. Answers to this question are eligible for a +50 reputation bounty.
Boern wants to draw more attention to this question:
explanation where the error is originating, what I can do conceptionally different to avoid it
I'm currently migrating a project and trying to figure out what the Vue3, VueFire3, VueX pendant to the following Vue2 + Vuexfire code is:
//actions.ts
[...]
bindDummy: firestoreAction((
{ bindFirestoreRef }) => {
return bindFirestoreRef(
"dummyVariable",
collectionDummy)
}),
[...]
//state.ts
function state (): dummyState {
return {
dummyVariable: {}
};
}
This is a setting up a binding to a variable in the VueX state called nameOfVariableInState. The variable will be updated automatically when the data in the firestore database changes).
I tried
// actions.ts
bindDummy ({ commit }) {
const todos = useCollection(dummyCollection);
if (todos.data) {
commit("setDummy", todos.data)
}
}
// mutations.ts
const mutation: MutationTree<DummyStateInterface> = {
setDummy (state, value) {
state.dummyVariable = value;
},
};
This works to the extend that the data is displayed and automatically updated BUT this triggers Uncaught Error: [vuex] do not mutate vuex store state outside mutation handlers.. In the cryptic stacktrace walkSet vuefire.823f1be4.mjs:29 appears.
I am using the aurelia-store state management library for managing state. This question is not specific to Aurelia store, but actually to redux best practices in general since Aurelia store is very much the same thing.
I have an action that fetches unit updates from an API like so:
export const fetchNewUnits = async (state: State): Promise<State> => {
const fetchedUnits = await apiClient.getUnitsMarkers();
// no new updates so don't trigger change in units
// IS THIS ACCEPTABLE?
if (fetchedUnits.length === 0) {
return {
...state,
highwaterMark: new Date()
};
}
const units: UnitMarker[] = state.units.slice();
_.forEach(fetchedUnits, (newUnit) => {
// look for matching unit in store
const idx = _.findIndex(units, {
imei: newUnit.imei
});
// unit was found in store, do update
if (idx !== -1) {
// replace the unit in the store
const replacement = new UnitMarker({...newUnit});
units.splice(idx, 1, replacement);
}
});
// OR SHOULD I ALWAYS DEEP COPY THE ARRAY REFERENCE AND IT'S OBJECTS
return {
...state,
highwaterMark: new Date(),
units: [...units]
};
};
If I do not have any unit changes (i.e. my store is up to date) can I simply return the state with the spread operator as shown in the first return statement? Is this fine since I did not modify the objects?
Or do I always have to do deep replacements such as:
return {
...state,
highwaterMark: new Date(),
units: [...state.units]
};
even if the objects in the array did not change?
The reason why you’re supposed to create a new object is because React components check for prop changes in order to know when to re-render.
If you simply modify an object and pass it in as a prop again, React won’t know that something changed and will fail to rerender.
So in your case, the question is: do you want to rerender, or not? If you don’t, returning the same object is fine and a simple ‘return state’ will let React know that no rerenders are necessary.
See: Why is the requirement to always return new object with new internal references
Currently I have the below reducer switch statement. All it does is toggles the state of Sidebar, so first it shows then hides then shows. It's easy.
switch(action.type) {
case 'SIDEBAR_DISPLAY_TOGGLE':
return {
...state,
Sidebar : {
...state.Sidebar,
Display : !state.Sidebar.Display
}
}
default:
return state;
}
Now I have a input field like here
that people can type to search account. I am trying to set up Redux so when user types, it gets saved to the Redux global state and I can pull it from another component. I have this reducer code set up for it but I don't know how can I pull what user types into this reducer from that component?
function reducer(state = initialState, action) {
switch(action.type) {
case 'ACCOUNT_SEARCH':
return {
...state,
AccountNumberSearch : {
...state.AccountNumberSearch,
AccountNumber : ''
}
}
default:
return state;
}
}
}
An action is just an object with a string value named type. Any other properties on this object will also be passed, so you use this to pass the typed text.
If you're using a function to create your actions, something along the lines of:
export function accountNumberSearch(accountNumber) {
return { type: 'ACCOUNT_SEARCH', accountNumber };
}
Then in your reducer, you'll be able to assign the value in the state to action.accountNumber.
AccountNumberSearch : {
...state.AccountNumberSearch,
AccountNumber : action.accountNumber,
}
Then you can map your state to props as you normally would (as you did for the sidebar toggle).
Also, as an aside, you should look into modularising your reducers with combineReducers - Docs
This would be much easier than the way you're doing it.
EDIT: Handling the changes
First of all, you'd want to wire up your input field for the search box to an onChange listener. If you do this like onChange={this.onSearchChange} you can get the value from event in the function:
onSearchChange = event => {
this.props.AccountNumberSearch(event.target.value);
}
Then in mapDispatchToProps you'd send your action + the passed value to dispatch:
const mapDispatchToProps = dispatch => {
return {
AccountNumberSearch: AccountNumber => dispatch(importedActions.AccountNumberSearch(AccountNumber)),
}
}
Then, in the component you want to RECEIVE this value, you'd map the redux state to props, like:
const mapStateToProps = state => {
return {
AccountNumber: state.AccountNumberSearch.AccountNumber,
}
}
Then you can access that value in your render function by calling this.props.AccountNumber.
If you need to do something when this value changes, you can always listen on componentDidUpdate, and compare the value with the old one - if it changed, call whatever function that you need to do.
Gee, I feel foolish about this, but I have read every part of: http://redux.js.org/ (done the egghead tutorials, and read 4 times the FAQ at: http://redux.js.org/docs/faq/ImmutableData.html
What I did was stub one of my reducers, to always return state, and that is the only reducer being called (checked with breakpoints). Even so, my subscribe event is being called every time the reducer returns state. What Do I not understand? (Action.SetServerStats is being called at a 1Hz rate, and the subscribe is also being called at a 1Hz Rate
BTW the Chrome Redux Extension says thats states are equal, and the React Extension for Chrome with Trace React Updates, is not showing any updates.
I will be glad to remove the question, when someone clues me in. But right now, what I see each each of the reducers being called at 1Hz, and all of them returning the slice of the store that they got (state).
So do I not understand subscribe, and that it returns every time even when the store tree does not get modified (and it is up to react-redux to do shallow compare to figure out what changed if any?)
create store & subscribe
let store = createStore(reducer, initialState, composeWithDevTools(applyMiddleware(thunk)))
store.subscribe(() => console.log("current store: ", JSON.stringify(store.getState(), null, 4)))
reducers.js
import A from './actionTypes'
import { combineReducers } from 'redux'
export const GLVersion = (state = '', action) => {
switch (action.type) {
case A.SetGLVersion:
return action.payload
default:
return state
}
}
export const ServerConfig = (state = {}, action) => {
switch (action.type) {
case A.SetServerConfig: {
let { ServerPort, UserID, PortNumber, WWWUrl, SourcePath, FMEPath } = action.payload
let p = { ServerPort, UserID, PortNumber, WWWUrl, SourcePath, FMEPath }
return p
}
default:
return state
}
}
export const ServerStats = (state = {}, action) => {
switch (action.type) {
case A.SetServerStats:
return state
// let { WatsonInstalled, WatsonRunning, FMERunning, JobsDirSize } = action.payload
// let s = { WatsonInstalled, WatsonRunning, FMERunning, JobsDirSize }
// return s
default:
return state
}
}
export default combineReducers({ GLVersion, ServerConfig, ServerStats })
Correct. Redux will execute all subscription callbacks every time an action is dispatched, even if the state is not updated in any way. It is up to the subscription callbacks to then do something meaningful, such as calling getState() and checking to see if some specific part of the state has changed.
React-Redux is an example of that. Each instance of a connected component class is a separate subscriber to the store. Every time an action is dispatched, all of the wrapper components generated by connect will first check to see if the root state value has changed, and if so, run the mapStateToProps functions they were given to see if the output of mapState has changed at all. If that mapState output changes, then the wrapper component will re-render your "real" component.
You might want to read my blog post Practical Redux, Part 6: Connected Lists, Forms, and Performance, which discusses several important aspects related to Redux performance. My new post Idiomatic Redux: The Tao of Redux, Part 1 - Implementation and Intent also goes into detail on how several parts of Redux actually work.
How can one persist the full router history of a user visiting an SSR react-redux app? I have tried modifying the react-redux-router package's reducer.js file as such...but when the user loads via SSR, the history array is reset.
/**
* This action type will be dispatched when your history
* receives a location change.
*/
export const LOCATION_CHANGE = '##router/LOCATION_CHANGE'
const initialState = {
locationBeforeTransitions: null,
locationHistory: []
}
/**
* This reducer will update the state with the most recent location history
* has transitioned to. This may not be in sync with the router, particularly
* if you have asynchronously-loaded routes, so reading from and relying on
* this state is discouraged.
*/
export function routerReducer(state = initialState, { type, payload } = {}) {
if (type === LOCATION_CHANGE) {
return { ...state,
locationBeforeTransitions: payload,
locationHistory: state.locationHistory.concat([payload]) }
}
return state
}
ref: https://github.com/reactjs/react-router-redux/blob/master/src/reducer.js
However, I think this is supposed to be achieved in a middleware.
Irregardless, this (storing the entire previous session history) seems like a common enough use case that perhaps someone has already formulated a best practice.??
Perhaps even this full history is accessible via the historyjs object in react-router w/o react-router-redux.
I'm looking for answers to how to fulfill storing the full history of a user's session in the redux state and post it to my api server when the user closes the browser or navigates away from the site. (if this is not possible, i could just post it upon every navigation.) Then I would like to show this history in a 'recently viewed' list of pages on the users' home pages.
First of all, you don't have to meddle with the internals of react-redux-router.
As you can see in the code you presented, react-redux-router exports a LOCATION_CHANGE action.
You can use this action in a reducer of your own. Here's an example:
// locationHistoryReducer.js
import { LOCATION_CHANGE } from 'react-router-redux';
export default function locationHistory(state = [], action) {
if (action.type === LOCATION_CHANGE) {
return state.concat([action.payload]);
}
return state;
}
However, this may be unnecessary. Your assumption that this can be be achieved with middleware is correct. Here's an example of a middleware layer:
const historySaver = store => next => action => {
if (action.type === LOCATION_CHANGE) {
// Do whatever you wish with action.payload
// Send it an HTTP request to the server, save it in a cookie, localStorage, etc.
}
return next(action)
}
And here's how to apply that layer in the store:
let store = createStore(
combineReducers(reducers),
applyMiddleware(
historySaver
)
)
Now, how you save and load data is entirely up to you (and has nothing to do with react-router and the browser's history).
In the official docs, they recommend injecting the initial state on the server side using a window.__PRELOADED_STATE__ variable.