Skip to content
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

Is it possible not to persist data to storage if it's undefined ? #513

Closed
VasRoko opened this issue Jan 30, 2024 · 4 comments
Closed

Is it possible not to persist data to storage if it's undefined ? #513

VasRoko opened this issue Jan 30, 2024 · 4 comments

Comments

@VasRoko
Copy link

VasRoko commented Jan 30, 2024

I need a bit of help; I'm not sure if it's intended behavior or a bug where empty data in persistenceMapper function is stored in localStorage as undefined which results in entire cache being cleared.

Use case:
I have cacheContents, which are typically products fetched from the server and stored in the cache. Next, I would like to pull out tempContents that are stored in localStorage, combine them with cacheContents, and store all in the Apollo cache. However, I want to keep only tempContents in localStorage not cacheContents.

It seems that this is possible to achieve with the usage of persistenceMapper where I can define what I want to store in my localStorage. This works as expected when some data is already in localStorage; however, the issue arises when the user loads the page for the first time, and tempContents do not exist. Therefore, filtered data is set to undefined. The problem with undefined it completely wipes out everything in my cache, so the page is empty.

Code Example

  const productResponse= await apolloClient.query({
    query:  SHOP_QUERY,
    variables: { shopId: shopId },
    fetchPolicy: 'no-cache' // Not stored in cache, so we can transform it
  });

  const cacheContents = transformToCacheState({
    ...productResponse.data.products,
  }); <=== additional processing logic 

  await persistCache({
      cache: cache.restore(cacheContents), 
      storage: new LocalStorageWrapper(window.localStorage),
      debug: true,
      persistenceMapper: async (data: any) => {  
        const parsedData = JSON.parse(data);  // mapper to persist only temp items
        const filteredData = JSON.stringify(parsedData['ROOT_QUERY']['tempItems']);  <======= this is empty on initial loads
        return filteredData; <=======  sets localStorage to `undefined` and wipes out all cache contents . 
      }
  });
  cache.writeQuery({
    query: SHOP_QUERY,
    data: { shopId }
  });

Is there a way solve this?

@wodCZ
Copy link
Collaborator

wodCZ commented Jan 30, 2024

Hi @VasRoko!

The code using persistenceMapper option is on single place, which is here:

async persist(): Promise<void> {
try {
let data = this.cache.extract();
if (!this.paused && this.persistenceMapper) {
data = await this.persistenceMapper(data);
}
if (
this.maxSize != null &&
typeof data === 'string' &&
data.length > this.maxSize &&
!this.paused
) {
await this.purge();
this.paused = true;
return;
}
if (this.paused) {
return;
}
await this.storage.write(data);
this.log.info(
typeof data === 'string'
? `Persisted cache of size ${data.length} characters`
: 'Persisted cache',
);
} catch (error) {
this.log.error('Error persisting cache', error);
throw error;
}
}

As you can see, it's called with whatever the cache content is, and the returned value gets persisted. If you return undefined, the undefined gets persisted.
I believe the behaviour you're illustrating is expected in this case, although I didn't fully get what you're trying to achieve (fault is on my side, late evening).

Maybe following this test case:

const persistenceMapper = async (data: any) => {
const parsed = JSON.parse(data);
delete parsed['Post:1'];
delete parsed['ROOT_QUERY']['posts'];
return JSON.stringify(parsed);
};

You could return something as a initial state instead of undefined?

Also, I'd suggest taking a break (I'd need one too 😅). From the looks of it, you're juggling with caches on several layers (in-memory & persisted) which will be difficult to keep in sync.
It feels like your use-case is just around the line where apollo and cache-persist's capabilities end, i.e. a full-featured state management system would be a better option, than trying to mutate apollo's internal cache.

@VasRoko
Copy link
Author

VasRoko commented Feb 1, 2024

Thanks for your explanation @wodCZ ! I can see in the code, that persistenceMapper would override cache data.

It seems that having initial state instead of undefined also doesn't solve our problem as it just overrides everything we had in-memory with that initial data. So I guess, it won't be possible (or at least not easily) to combine in-memory & persisted but only store some parts of Apollo in local storage ? Alternative solutions should be considered which is fine, I just wanted to eliminate the options that we have :)

full-featured state management system would be a better option

Not sure I would agree, mutating apollo cache, has in some sense functioned as a state management system and works perfectly fine in our use case.

@wodCZ
Copy link
Collaborator

wodCZ commented Feb 2, 2024

it won't be possible (or at least not easily) to combine in-memory & persisted but only store some parts of Apollo in local storage

There's https://github.com/TallerWebSolutions/apollo-cache-instorage, a result of discussion in #2 (comment).
Also, we had a custom persistence mapper example here (and here). Maybe that would help you?

mutating apollo cache, has in some sense functioned as a state management system

Yeah, I've been there. I didn't mean to attack your decision, my suggestion was purely a friendly advice. I guess I'm biased against storing an app state next to a http layer.

@VasRoko
Copy link
Author

VasRoko commented Feb 3, 2024

There's https://github.com/TallerWebSolutions/apollo-cache-instorage, a result of discussion in #2 (comment).
Also, we had a custom persistence mapper example here (and here). Maybe that would help you?

that looks interesting, thanks for the suggestion! I'll check it out.

my suggestion was purely a friendly advice. I guess I'm biased against storing an app state next to a http layer.

Any advice is always welcomed, I am open minded and willing to hear suggestions for improvement.

Thanks again @wodCZ ! You've been a great help, really appreciate it. Feel free to close this thread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants