-
Notifications
You must be signed in to change notification settings - Fork 107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to merge slicer subset into initial state #2
Comments
Hmm, the challenge here is that in your You could do something like the following to merge the export default function jobs (state = initialState, action) {
switch (action.type) {
case '@@redux/INIT':
const newState = {...state, ...initialState};
return newState;
// etc ...
}
} |
Note that I am not sure if hooking into redux's |
Yes, for now I've done something similar, although I'm not sure if this is the correct way: in this issue they mention "Handling |
I thought just as much. Thanks for linking the issue, I had been wondering about the status on that. To ensure your code works in the future (and does not break hot reloading), you definitely should not manually hook into this action. Some of the solutions provided in that redux issue could be used. You could also take advantage of the // this object would hold all the initial states for each reducer
// you would probably want to be able to import the initialState from each reducer
// to ensure the logic lives in one place only (in each reducer probably)
const initialStates = {
reducer1: {},
reducer2: {},
...
};
// set up config for `persistState`
const config = {
deserialize: (stateStr) => {
// parse string from local storage
let state = JSON.parse(stateStr);
// merge localStorage state with initialStates
Object.keys(state).forEach((key) => {
state[key] = {...state[key], ...initialState[key]};
});
return state;
}
};
const myPersistState = persistState(paths, config); |
Thanks a lot, interesting approach and I'll dive into it 👍 |
Hi klaasman, I just reopened this issue. Current suggestions might provide a workaround, but I’d like to discuss it further. Could you share your custom slicer? Am I correct in thinking that you’re only persisting jobs.favorites or jobs.list and not the other one? The first thing that comes to my mind, is to split your reducer into two, for guides/examples of different ways to do this have a look here: http://gaearon.github.io/redux/docs/basics/Reducers.html. That way you can provide each reducer with it's own |
Thats good to hear :-) Your assumption is correct, my slicer currently looks like this: function storageSlicer () {
return (state) => ({
jobs: {
favorites: state.jobs.favorites || []
}
})
} Initially the store should be populated with the I've thought about splitting up reducers, but, since it's possible to supply this module with 'root' paths, doesn't that somehow make the slicer unnecessary, at least for deeply nested properties? To be honest, the Would you suggest, in case of a subset of the store coming from |
There are a number of options at your disposal. Option 1 is to pass in your initialState when calling your "enhanced" createStore: createPersistentStore(reducer, {jobs: {list: [], favorites: [], isLoading: false}}) The initialState passed to createStore and your persistedState are merged during initialisation, so this should give you the result you're looking for. Everything is used as it is intended so it's relatively clean, but it still feels like a workaround, you'd probably rather keep this contained within your reducer. Options 2 is to check whether state.list is defined in your reducer. export default function jobs (state = initialState, action) {
if (!state.list) state.list = []
...
} Everything is contained in your reducer, but it's hacky. This added line is going to look super redundant and someone else (or you yourself at a later time) is going to question why it's there, perhaps even remove it and create a bug. Option 3 split your reducers further. export default function jobs (state = initialState, action) {
if (action.FETCH_JOBS_DATA_SUCCESS) {
return action.payload // e.g. {favorites: [fetched data], list: [fetched data]}
} else {
return {
favorites: favorites(state.favorites, action)
list: list(state.list, action)
}
}
} This might not be a great code snippet, the point I'm trying to illustrate though, is that (further) splitting of your reducer can be done in a way that your components don't have to know about. Each function p.s. I don't fully understand your remark about needing separate actions to fetch both jobs and favorites if they each have their own totally separate reducer, I wonder if there might be other things that could be improved, but then we're really getting into the implementation details of your application and redux in general.. |
You're right, if you end up flattening your store state your custom slicer function wouldn't be needed anymore - which would be a good thing! Though, like I said in my previous comment, splitting your reducers further doesn't have to mean flattening your store state. On a side note, I'm looking into supporting nested paths, which would remove the need for a custom slicer in this scenario and instead allow you to pass in something like |
They also had there own actions, e.g. // actions/jobs.js
export function fetchJobs (options) {
return dispatch => api.fetchJobs().then(list => dispatch({
type: constants.FETCH_JOBS_SUCCESS,
list
// ...
}))
}
// actions/favorites.js
export function addToFavorites (id) {
return {
type: constants.FAVORITES_ADD,
id
// ...
}
} Somewhere else in a React component we need those actions: import * as jobActions from '../actions/jobs'
import * as favoritesActions from '../actions/favorites'
// ...
const { fetchJobs } = bindActionCreators(jobActions, dispatch)
const { addToFavorites } = bindActionCreators(favoriteActions, dispatch)
// do something with `fetchJobs()` and `addToFavorites()` I wanted to prevent to call those bindActionCreators multple times, although it's not that big of an issue. For the described case in this issue it doesn't really matter and it is indeed an implementation detail. Let's focus on the nested data instead :-) |
Hey Klaasman, I just published version 1.0.0-beta1, I recommend checking it out. It takes care of your (original) issue without any workarounds or adjustments! |
Thanks! I'll take a look at it |
First, I really like the new abstraction of the adapter 👍 I might be missing something, but got it working this way: const REDUX_LOCAL_STORAGE_INIT = 'redux-localstorage/INIT'
const storageSlicer = (slicer) => (storage) => ({
...storage,
put: (key, state, callback) => {
storage.put(key, slicer(state), callback)
}
})
const storage = compose(
storageSlicer(state => ({
jobs: {
favoriteIds: state.jobs.favoriteIds
}
})),
adapter(window.localStorage)
)
const mergeLocalStorage = (reducer) => (state, action) => {
if (action.type === REDUX_LOCAL_STORAGE_INIT) {
return action.payload ? defaultsDeep({}, state, action.payload) : state
}
return reducer(state, action)
}
createPersistentStore(
applyMiddleware(thunk),
persistState(storage),
createStore
)
const wbdoApp = compose(mergeLocalStorage, combineReducers(reducers))
const store = createPersistentStore(wbdoApp) Any suggestions? |
Hi Klaasman, I just published What does your import {compose, applyMiddleware, createStore} from 'redux'
import thunk from 'redux-thunk'
import adapter from 'redux-localstorage/lib/adapters/localStorage'
import {filter} from 'redux-localstorage/lib/enhancers'
import persistState from 'redux-localstorage'
import * as reducers from './reducers'
const storage = compose(
filter('jobs.favoriteIds'),
adapter(window.localStorage)
)
const createPersistentStore = compose(
applyMiddleware(thunk),
persistState(storage),
createStore
)
const reducer = combineReducers(reducers)
const store = createPersistentStore(reducer) |
It's working as expected now! The |
Thanks klaasman, glad to hear everything is working now! |
I yelled too early I'm afraid, the deep merge still is necessary: const initialState = {
something: {
key: 123
},
somethingElse: {
key1: [],
key2: {},
key3: false,
// ...
},
}
const persistedState = {
somethingElse: {
key1: [1, 2, 3]
}
}
const mergedResult = {...initialState, ...persistedState}
/*
mergedResult equals:
{
something: {
key: 123
},
somethingElse: {
key1: [1, 2, 3]
// key2 and key3 are missng due to the not so deep merge
}
}
* Perhaps a deep merge could be made the standard merge strategy? |
Third time's a charm right? Grab |
Exactly, thanks again ;-) facing no problems with |
You're welcome again =) |
I've defined a slicer to specifiy a subset which is saved to localStorage.
Inside one of my reducers I've got an initial state defined, comparable to this example.
My reduce won't consume the initial state anymore, since there's another initial state passed to this reducer, together with the action named
"@@redux/INIT
.How should I merge both the
initialState
and the state coming from local storage?The text was updated successfully, but these errors were encountered: