Comments (17)
At a minimum, the persistor needs to log a warning when saving a date object. This is a really nasty gotcha that violates the general API promise of getting back what you put in, and even if it's unquestionably better to store the integer, the user shouldn't have to spend an hour tracking down undefined is not a function
errors because the object that was a Date is now a string with no warning.
from redux-persist.
Having x many more months of redux experience, I think the best practice is to store your dates as integers (or other serializable format). 👍
from redux-persist.
@digitalmaster got the point. However instead of traversing object tree, one may just json-serialize and deserialize objects using replacer and reviver to handle dates.
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Parameters
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Syntax
Here the "codec":
const replacer = (key, value) => date instanceof Date ? value.toISOString() : value
const reviver = (key, value) =>
(typeof value === 'string' && value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/))
? new Date(value)
: value
export const encode = toDeshydrate => JSON.stringify(toDeshydrate, replacer)
export const decode = toRehydrate => JSON.parse(toRehydrate, reviver)
And how to use it:
const persistConfig = {
key: 'myKey',
transforms: [createTransform(encode, decode)]
}```
from redux-persist.
I think in principle i agree that what goes in should be what comes back out... or at least that's the expectation. Cases where this is not true should be clearly highlighted (i like the idea of a dev warning here).
I'm new to redux persist 🙈 .. is there some example code you could point me to that shows how to transform strings back to dates on rehydrate?
Update:
Just took care of the transform in my events reducer. Not bad at all :)
...
case REHYDRATE: {
return action.payload.events.map((event) => {
return Object.assign({}, event, {
startTime: new Date(event.startTime),
endTime: new Date(event.endTime)
});
});
....
Update II:
The above workaround quickly becomes difficult to maintain once your app grows and you have more reducers with dates :(
Ended up just writing a simple transform:
const dateTransform = createTransform(null, (outboundState) => {
return traverse(outboundState).map((val) => {
if (Time.isISOStringDate(val)) {
return new Date(val);
}
return val;
});
});
persistStore(Store, {
storage: AsyncStorage,
transforms: [dateTransform]
});
from redux-persist.
@digitalmaster got the point. However instead of traversing object tree, one may just json-serialize and deserialize objects using replacer and reviver to handle dates.
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Parameters
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Syntax
Here the "codec":
const replacer = (key, value) => date instanceof Date ? value.toISOString() : value const reviver = (key, value) => (typeof value === 'string' && value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)) ? new Date(value) : value export const encode = toDeshydrate => JSON.stringify(toDeshydrate, replacer) export const decode = toRehydrate => JSON.parse(toRehydrate, reviver)
And how to use it:
const persistConfig = { key: 'myKey', transforms: [createTransform(encode, decode)] }```
This works great, after fixing a typo causing a fatal error:
In the first line, date
should be value
.
- const replacer = (key, value) => date instanceof Date ? value.toISOString() : value
+ const replacer = (key, value) => value instanceof Date ? value.toISOString() : value
Also toDeshydrate
looks like a typo of toDehydrate
(without the extra s
).
The encode
and replacer
functions aren't actually needed though, because JSON.stringify()
already transforms Dates to ISO strings.
So I was able to reduce this snippet down to just this transform
addition to my config:
const persistConfig = {
key: 'myKey',
// Transform dates back into JS Dates on rehydrate
// (see: https://github.com/rt2zz/redux-persist/issues/82)
transforms: [
createTransform(JSON.stringify, toRehydrate =>
JSON.parse(toRehydrate, (key, value) =>
typeof value === 'string' && value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
? new Date(value)
: value,
),
),
],
}
Thanks again for this, @vcarel. It fixed this issue for me.
And agree with other commenters, there could still be better warnings about this to avoid subtle bugs that take a while to track down.
from redux-persist.
Date objects will be converted to string by JSON.stringify()
which is used in the default serializer.
One great advantage of redux is that everything only has to deal with plain objects, not with functions. Here is a great answer by redux's creator to a similar question on Stackoverflow
from redux-persist.
ya, for the reasons outline above, I usually store my dates as ms integers, and convert to Date/moment as needed in the application (but outside of redux).
In so far as redux-persist goes, I think a transform is the right way to handle this, probably something like https://github.com/cognitect/transit-js which is performant and will handle dates as well as maps and symbols.
from redux-persist.
Hey, @rt2zz , I would like to ask you a bit more about creating a transform for dates. Won't transit-js cause troubles if my code expects plain js objects? AFAIK it returns maps instead. I think having proper dates is pretty important and I'm a bit surprised that there's no built-in solution or a 'default' solution from community yet. Don't mind implementing but need some advice
UPD: in case, if it might help somebody - looks like using https://www.npmjs.com/package/json-literal in transform solves the issue for now
from redux-persist.
for reference creating a transit transform would look almost exactly like: https://github.com/rt2zz/redux-persist-transform-immutable/blob/master/index.js except instead of transit-immutable-js use plain transit and instead of toJSON and fromJSON use read/write.
from redux-persist.
Don't know if this is best practice but: converting a date to string with built in methods, and then creating a new Date with that string, will give you the correct date object. So one could just store the date as a string, and create a new date object when used. Was useful in my case, since I had a lot of date object operations.
Date now = new Date(Date.now())
const dateString = now.toString()
Date then = new Date(dateString)
from redux-persist.
@chrylis That's a great ux suggestion. We could print a warning in dev mode for the default persistor. I'll have to check how that would work with immutablejs (haven't touched it in a while), but I'm all for helpful warning messages.
from redux-persist.
I definitely like the idea of checking for Date objects (in dev) and logging if found. 👍
Additionally I think a good solution for people who want to store date objects in redux state would be to have a transform that implements https://github.com/cognitect/transit-js. It would be almost identical to https://github.com/rt2zz/redux-persist-transform-immutable except using plain transit-js.
from redux-persist.
@you-fail-me plain transit-js deals with plain js objects, transit-js-immutable adds the Map etc.
json-literal
is just as well (maybe better?). I do not want to increase the lib size by making that default, but I am open to suggestions for how to work this into the api / core lib in a way that is opt-in and does not affect bundle size.
from redux-persist.
This hasn't been responded to in a long while, so I'm going to mark it as stale. Please feel free to continue the conversation and I'll reopen.
@chrylis you mentioned
At a minimum, the persistor needs to log a warning when saving a date object. This is a really nasty gotcha that violates the general API promise of getting back what you put in
That suggestion has 31 upvotes. Would you help the redux-persist project by contributing a PR that does that? I'd be happy to review it and merge it.
from redux-persist.
@dsernst @vcarel but your solution would also convert ISO-strings that were strings to start with to Date objects when rehydrating the store. so we'd need a solution that "marks" the Dates going in to be recognized and transformed when coming out.
json-literal does exactly that but uses esprima under the hood - which bloats my project up by 132KB :(
I'll take a look at transit ...
from redux-persist.
@digitalmaster got the point. However instead of traversing object tree, one may just json-serialize and deserialize objects using replacer and reviver to handle dates.
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Parameters
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Syntax
Here the "codec":
const replacer = (key, value) => date instanceof Date ? value.toISOString() : value const reviver = (key, value) => (typeof value === 'string' && value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)) ? new Date(value) : value export const encode = toDeshydrate => JSON.stringify(toDeshydrate, replacer) export const decode = toRehydrate => JSON.parse(toRehydrate, reviver)
And how to use it:
const persistConfig = { key: 'myKey', transforms: [createTransform(encode, decode)] }```
This works great, after fixing a typo causing a fatal error:
In the first line,
date
should bevalue
.- const replacer = (key, value) => date instanceof Date ? value.toISOString() : value + const replacer = (key, value) => value instanceof Date ? value.toISOString() : valueAlso
toDeshydrate
looks like a typo oftoDehydrate
(without the extras
).The
encode
andreplacer
functions aren't actually needed though, becauseJSON.stringify()
already transforms Dates to ISO strings.So I was able to reduce this snippet down to just this
transform
addition to my config:const persistConfig = { key: 'myKey', // Transform dates back into JS Dates on rehydrate // (see: https://github.com/rt2zz/redux-persist/issues/82) transforms: [ createTransform(JSON.stringify, toRehydrate => JSON.parse(toRehydrate, (key, value) => typeof value === 'string' && value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/) ? new Date(value) : value, ), ), ], }Thanks again for this, @vcarel. It fixed this issue for me.
And agree with other commenters, there could still be better warnings about this to avoid subtle bugs that take a while to track down.
Does this solution results stringify and parse running twice, since the library itself runs it once?
from redux-persist.
@digitalmaster got the point. However instead of traversing object tree, one may just json-serialize and deserialize objects using replacer and reviver to handle dates.
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Parameters
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Syntax
Here the "codec":
const replacer = (key, value) => date instanceof Date ? value.toISOString() : value const reviver = (key, value) => (typeof value === 'string' && value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)) ? new Date(value) : value export const encode = toDeshydrate => JSON.stringify(toDeshydrate, replacer) export const decode = toRehydrate => JSON.parse(toRehydrate, reviver)
And how to use it:
const persistConfig = { key: 'myKey', transforms: [createTransform(encode, decode)] }```
This works great, after fixing a typo causing a fatal error:
In the first line,date
should bevalue
.- const replacer = (key, value) => date instanceof Date ? value.toISOString() : value + const replacer = (key, value) => value instanceof Date ? value.toISOString() : valueAlso
toDeshydrate
looks like a typo oftoDehydrate
(without the extras
).
Theencode
andreplacer
functions aren't actually needed though, becauseJSON.stringify()
already transforms Dates to ISO strings.
So I was able to reduce this snippet down to just thistransform
addition to my config:const persistConfig = { key: 'myKey', // Transform dates back into JS Dates on rehydrate // (see: https://github.com/rt2zz/redux-persist/issues/82) transforms: [ createTransform(JSON.stringify, toRehydrate => JSON.parse(toRehydrate, (key, value) => typeof value === 'string' && value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/) ? new Date(value) : value, ), ), ], }Thanks again for this, @vcarel. It fixed this issue for me.
And agree with other commenters, there could still be better warnings about this to avoid subtle bugs that take a while to track down.Does this solution results stringify and parse running twice, since the library itself runs it once?
yes stringify and parse running twice!!cry
from redux-persist.
Related Issues (20)
- Do you support persist bigint when serialize? HOT 3
- Importing PersistGate results in compilation error HOT 1
- Jest snapshot is always created as null in react native
- Persist not working after page reload or refresh HOT 3
- failed to preserved initial state value at rehydrate
- PersistGate in SSR applications rendering app twice when using React v18 HOT 3
- Documentation for Migration is unnecessary complicated. HOT 1
- Getting got unexpected undefined error while changing redux state HOT 1
- getting error: A non-serializable value was detected in an action HOT 5
- createStore() is old
- Clear Redux Persist Data on App uninstall in IOS React Native HOT 1
- Support for Redux v5 and RTK v2 HOT 10
- state is null HOT 1
- data migration security and automatization
- Redux persist not working with RN 0.73 HOT 10
- Move project to new repo? HOT 13
- redux-persist failed to create sync storage. falling back to noop storage | Next js HOT 3
- Vitest - The slice reducer for key "reducer name" returned undefined during initialization
- Option to disable storage creation error for dynamic stores
- storage.getItem is not a function (it is undefined) - RN 0.73.4 HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from redux-persist.