How to restore cache to its initial state - reactjs

I use SSR to rehydrate the cache with initial state from the server
const cache = new InMemoryCache().restore(window.__APOLLO_STATE__)
Few mutations later, I need to hard reset the cache to its initial state. I naively tried to keep the initial store in component state and pass it to client.restore
componentDidMount () {
this.setState({
initState: this.props.client.extract()
})
}
// When I need to reset, I'd call this method...
handleCacheReset () {
this.props.client.restore( this.state.initState )
}
but it didn't work. Since I'm also using using apollo-link-state, I thought if I keep the cache data as defaults, maybe then I can write them when client.resetStore is called
const cache = new InMemoryCache().restore(window.__APOLLO_STATE__)
const link = withClientState({ cache })
const client = new ApolloClient({
link, cache,
defaults: cache.data.data
})
client.onResetStore(link.writeDefaults)
// ...when it's time to reset the store
client.resetStore()
But I only get Missing field ... in {} errors and no data gets written to the store. Worse, I can't use client.writeData since I only have the initial window.__APOLLO_STATE__. If only I could read the entire cache data, then it might be plausible, but there doesn't seem to be a readData method.
Is there a way to programmatically restore the cache to its original state?
So far the only workaround I see is re-creating the entire ApolloClient and forcefully re-rendering the tree, which is expensive and inefficient. Thoughts?
P.S. Asked this on GitHub, but they closed it with no answer.

Is this on web or mobile? I haven't been able to solve this myself, but in thinking about apollo-cache-persist, you could theoretically save states of your cache to local storage and restore them to your cache at a later time.

this.props.client.extract() returns directly the cache object not a copy :
https://github.com/apollographql/apollo-client/blob/master/packages/apollo-cache-inmemory/src/depTrackingCache.ts#L23
If you want to keep the original state, you can do :
initState: {...this.props.client.extract()}

Related

react-query: is there a callback that get trigger regardless of whether getQuery is cached or not?

I want to do some side effects like setState and update context after the data is fetched. However, the onSuccess will not be executed when the data is in cache. Also useEffect doesn't work because if the data is cached, it doesn't change from undefined to the real data. Therefore it doesn't get trigger either. What's the best way of doing this? Thanks
My usecase is to extract some values from the data returned from useQuery and set a new state on those.
usually, they’re shouldn’t be a need to be a need to copy state from react-query into local state. This will just lead to duplication of the source of truth. It is best to keep them separated, so that you can also profit from background updates.
If you want to transform the data or subscribe to parts of the data, use the select option of useQuery:
const { data } = useQuery(key, fn, { select: data => data.map(...) })
Alternatively, you can compute some new data depending on the returned data with useMemo, e.g.:
const { data } = useQuery(...)
const articles = useMemo(() => data?.map(...), [data])
// work with articles from here on
You can also put that nicely in a custom hook.

Redux Offline issue with rehydrating reducer to null

I am using redux-offline, below is how I am creating my store
let customConfig = {
...offlineConfig,
persistOptions:{
key: 'root',
transforms: [immutableTransform()],
},
returnPromises : false,
persistCallback : () => {this.setState({rehydrated : true})}
};
const { middleware, enhanceReducer, enhanceStore } = createOffline(customConfig);
let middlewares = applyMiddleware(offlineCommitHandler,thunk,middleware,);
store = createStore(enhanceReducer(IndexReducer),undefined,compose(enhanceStore,middlewares,persistAutoRehydrate({log:true})));
I have multiple reducers.
The issue occurs only in rehydrating one reducer, for ex : reducerA
I placed a debugger in autoRehydrate
When opening the app first time it merges the data for reducerA
When opening the app second time inbound state for reducerA is
null.
I never did offline config but I ran into similiar problems with redux-persist.
So first of all you want to make sure that your debugger and your device/emulator are not drifting away in time. This would cause me problems all the time, simply restarting the emulator and chrome tab which I was using for debugging would solve it.
Second, you want to check if you don't have any errors in your reducer. In my case, I was not following the immutability pattern and that would sometimes cause redux not to persist my state, as it did not see any difference between the old state and the new state.
For anyone looking for answer to similar issue.
My issue was that the data i was trying to store was hitting the max storage limit for asyncStorage .
https://github.com/rt2zz/redux-persist/issues/199
I had to shift to using filestorage instead of asyncStorage to get redux persist store the data

Global variables in React

I know Redux solves this but I came up with an idea.
Imagine I have an app that gets some JSON on start. Based on this JSON I'm setting up the environment, so let's assume the app starts and it downloads an array of list items.
Of course as I'm not using Redux (the app itself is quite simple and Redux feels like a huge overkill here) if I want to use these list items outside of my component I have to pass them down as props and then pass them as props again as deep as I want to use them.
Why can't I do something like this:
fetch(listItems)
.then(response => response.json())
.then(json => {
window.consts = json.list;
This way I can access my list anywhere in my app and even outside of React. Is it considered an anti-pattern? Of course the list items WON'T be changed EVER, so there is no interaction or change of state.
What I usually do when I have some static (but requested via API) data is a little service that acts kind like a global but is under a regular import:
// get-timezones.js
import { get } from '../services/request'
let fetching = false
let timez = null
export default () => {
// if we already got timezones, return it
if (timez) {
return new Promise((resolve) => resolve(timez))
}
// if we already fired a request, return its promise
if (fetching) {
return fetching
}
// first run, return request promise
// and populate timezones for caching
fetching = get('timezones').then((data) => {
timez = data
return timez
})
return fetching
}
And then in the view react component:
// some-view.js
getTimezones().then((timezones) => {
this.setState({ timezones })
})
This works in a way it will always return a promise but the first time it is called it will do the request to the API and get the data. Subsequent requests will use a cached variable (kinda like a global).
Your approach may have a few issues:
If react renders before this window.consts is populated you won't
be able to access it, react won't know it should re-render.
You seem to be doing this request even when the data won't be used.
The only downside of my approach is setting state asynchronously, it may lead to errors if the component is not mounted anymore.
From the React point of view:
You can pass the list from top level via Context and you can see docs here.
Sample of using it is simple and exists in many libraries, such as Material UI components using it to inject theme across all components.
From engineering concept of everything is a trade of:
If you feel that it's gonna take so much time, and you are not going to change it ever, so keep it simple, set it to window and document it. (For your self to not forget it and letting other people know why you did this.)
If you're absolutely certain they won't ever change, I think it's quite ok to store them in a global, especially if you need to access the data outside of React. You may want to use a different name, maybe something like "appNameConfig"..
Otherwise, React has a feature called Context, which can also be used for "deep provision" - Reference

React Load Data From DB & Store in State Only Once

So here's my situation. I have a component that loads some data from the database, like so:
const task= fetch(`http://example.com`)
.then(result => result.json())
.then(result => {
this.setState({
items: result
})
});
addTask(task);
Now, if I add this code to the componentWillMount method, it does work however, every time I load the component, this method will fire and I don't want it calling the database every time. I want it called once on first load and then stored in the state and never called again.
And because I am rendering a list of items, I have to manually set this items variable in the constructor to avoid null errors:
constructor() {
super();
this.state = {
items: [],
}
}
My question is, how can I load this data only once and then set it to the items state variable. I have tried adding all this code to the reducer of the component (I am using redux), but it doesn't work.
There are several ways of achieving what you are trying to do, but every each of them brings up more questions to answer I think.
First question is, lets say you stored your data locally after your first fetch, whats gonna happen if the data on the server changes? You can somehow notify the client side that data is change and then fetch the data again to sync local data. Or maybe you can periodically check for the new data. There are lots of ways to go.
This situation is exists if you load the data on a parent component and pass the data to the component that is gonna use it or use persistent redux or similar solution.
You can go with a 3 library solution like Realm, Firebase or similar. These systems have persistent or local storage options that will reduce the download when you make call to data. But these solutions bring up the need for changing your back-end logic.
Like I said before there is no single way to handle these situations and it really depends on your project and preference.

react meteor data container doesn't update child when props change

I have been struggling with this issue for quite some time and have failed to find any answers.
I use react-meteor-data to manage my data with react in my meteor application. It is working fine when dealing with data for mongo but I can't make it reactive with props.
Here in App.js, I call my container which I want to be reactive and rerender when the state of App change.
<MyContainer someState={this.state.MyState} />
In MyContainer.js I have a createContainer from react-meteor-data
export default createContainer(params => {
Meteor.subscribe('someCollection');
return {
someCollection: SomeCollection.find({}).fetch(),
stateFromParent: params.someState
};
}, MyContainer);
This worked fine when rendering the component for the first time, MyContainer correctly get MyState.
The thing is, when the MyState from App change, I can see in Chrome Dev React tool that it is indeed updated for the createContainer( ReactMeteorDataComponent has a prop with the right updated state) but the createContainer function is not run, thus the props do not update for MyContainer.
So the props are updated from ReactMeteorDataComponent but not for MyContainer who keeps indefinitely the data. It's like createContainer doesn't consider the update of its prop has a change and thus doesn't run its function.
I really think I'm missing something since that seems pretty basic stuff, thank you for your help.
The OP did not mention how the state was changed, so the original example is incomplete. Therefore, I will try to explain the gist of how the container creation works, in hope that understanding it will be useful.
How does it work?
It uses meteor's Tracker to auto-update the wrapped component when its computation is invalidated (i.e, when one of the reactive data sources, such as reactive variables, subscription handles or fetched MiniMongo cursors, has a new value). To learn more about Tracker, consult the Tracker manual. This is an in-depth resource, and is not necessary to understand how the basics work.
It does so in a way that is different from the way you normally approach reactivity tracking in Meteor, since it also needs to re-run the computation whenever the container's props are changed.
The source code is not very long or complex and can be found on GitHub (currently here).
Tracker.autorun((c) => {
if (c.firstRun) {
//...
data = component.getMeteorData();
} else {
// Stop this computation instead of using the re-run.
// We use a brand-new autorun for each call to getMeteorData
// to capture dependencies on any reactive data sources that
// are accessed. The reason we can't use a single autorun
// for the lifetime of the component is that Tracker only
// re-runs autoruns at flush time, while we need to be able to
// re-call getMeteorData synchronously whenever we want, e.g.
// from componentWillUpdate.
c.stop();
// Calling forceUpdate() triggers componentWillUpdate which
// recalculates getMeteorData() and re-renders the component.
component.forceUpdate();
}
})
Whenever the computation is invalidated (and therefore rerun), it stops the computation and forces a re-render of the container, which will re-create a new computation and have the updated data.
The high-level container functionality is here (some parts were removed for brevity):
export const ReactMeteorData = {
componentWillMount() {
this.data = {};
this._meteorDataManager = new MeteorDataManager(this); // (1)
const newData = this._meteorDataManager.calculateData(); // (2)
this._meteorDataManager.updateData(newData); // (3)
},
componentWillUpdate(nextProps, nextState) {
// backup current state and props, assign next ones to components
let newData = this._meteorDataManager.calculateData(); // (2)
this._meteorDataManager.updateData(newData); // (3)
// restore backed up data
},
componentWillUnmount() {
this._meteorDataManager.dispose(); // (4)
},
};
The main points are:
- Before being mounted, a new data manager is created (1). It is in charge of running the computation and populating this.data according to data changes.
- At first and whenever the component should update, the computation is run (2) and the data is updated (3). The update happens whenever the component receives new state or props (in this type of container, it should only be props), and, as we saw earlier, also when the Tracker computation is invalidated, due to the call to component.forceUpdate().
The wrapped component receives the parent's props, as well as the Tracker computation's data as props:
return <WrappedComponent {...this.props} {...this.data} />;
Any more points as to how it should be used?
The react-meteor-data has a short section in the meteor guide.
Generally, the simple example in the guide (as well as the OP's example) should work just fine, as long as the state is set appropriately, using setState() (see the "how does it work?" section above).
Also, there is no need to re-map the container state to props sent to the child, as they are passed along (unless there is a very good reason for doing so).
Do consider the point in the preventing re-renders section if you encounter any performance issues.
From the guide:
export default ListPageContainer = withTracker(({ id }) => {
const handle = Meteor.subscribe('todos.inList', id);
const loading = !handle.ready();
const list = Lists.findOne(id);
const listExists = !loading && !!list;
return {
loading,
list,
listExists,
todos: listExists ? list.todos().fetch() : [],
};
})(ListPage);
in this example, note that the container expects an id prop, and it will also be made available to the wrapped component, as well as loading, list, etc (which come from the container's computation in the example).

Resources