Coder Social home page Coder Social logo

Comments (17)

chrylis avatar chrylis commented on May 14, 2024 73

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.

rt2zz avatar rt2zz commented on May 14, 2024 24

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.

vcarel avatar vcarel commented on May 14, 2024 24

@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.

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.

digitalmaster avatar digitalmaster commented on May 14, 2024 10

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.

dsernst avatar dsernst commented on May 14, 2024 5

@vcarels

@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.

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.

marvinhagemeister avatar marvinhagemeister commented on May 14, 2024 4

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.

rt2zz avatar rt2zz commented on May 14, 2024 3

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.

you-fail-me avatar you-fail-me commented on May 14, 2024 2

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.

rt2zz avatar rt2zz commented on May 14, 2024

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.

JonesN3 avatar JonesN3 commented on May 14, 2024

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.

marvinhagemeister avatar marvinhagemeister commented on May 14, 2024

@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.

rt2zz avatar rt2zz commented on May 14, 2024

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.

rt2zz avatar rt2zz commented on May 14, 2024

@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.

aguynamedben avatar aguynamedben commented on May 14, 2024

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.

pkyeck avatar pkyeck commented on May 14, 2024

@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.

aditya1711 avatar aditya1711 commented on May 14, 2024

@vcarels

@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.

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.

Does this solution results stringify and parse running twice, since the library itself runs it once?

from redux-persist.

fanxueqin avatar fanxueqin commented on May 14, 2024

@vcarels

@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.

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.

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.