How to store results/metadata?
Closed this issue · 6 comments
After spending some time looking at the code & examples, I cannot seem to find a easy way of saving query results in the state. Say I have 2 listings of the same entity with slightly different queries. How would each receive it's own subset of entities or result metadata like the total count?
Using normalizer in transform I could potentially return a results entity where I somehow store the results by queryKey. Seems wrong to store an entity for that + I would need to figure out the queryKey from within the transform.
I also experimented with a queryResults reducer but I find myself having to duplicate normalizations. Looked at a middleware that is placed before redux-query or using the meta key on queries. Nothing seems to be able to talk to each other. Am I missing something obvious?
Hi @episode17. It's difficult for me to help without more information or example code.
I think you might be focusing too much on the queries reducer and query keys. I would encourage only using the entities
reducer for reading network-sourced data. And I'd recommend indexing the entities by a unique ID (e.g. the primary key in the DB), not the query key.
Usually I recommend avoiding computed data unless it's really expensive to compute. If you would like to store computed data, you could just have that be stored under a separate entity key. Or alternatively, not store it at all and recompute it with a selector function.
@ryanashcraft thanks for the quick reply!
Before redux-query I typically had the entities reducer where I simply merged in the result of normalize. The difference is that I used to save the result of the normalization into a separate reducer with a unique key only known by the component that requested the data (perhaps with something similar to a queryKey). Here I can't figure out that last part, where the consuming component is able to select only the entities hes concerned about. (We have many components that query the same entity with slight differences in sorting, filtering, limits, etc)
Let's say I have the following query creator
function createGetThingsQuery(orderBy, limit) {
return {
url: `${API_URL}/things`,
transform: response => {
const normalized = normalize(response, thingsSchema);
// Header: X-Total-Count <- How do I get this?
console.log(normalized.result); // [1, 2, 3] <- How do I get this?
return normalized.entities;
},
body: { orderBy, limit },
update: {
things: shallowMerge,
tags: shallowMerge
}
}
}
I map two queries, one with orderBy=date&limit=10, the other one with orderBy=likes&limit=5. I'd be fine not storing the results in state but I don't see a way to pass it down for usage in a selector or the component.
function mapStateToProps(state, props) {
return {
things1: selectThings(state, [] /* query1: normalized.result */),
things2: selectThings(state, [] /* query2: normalized.result */)
};
}
From my understanding I'd probably structure it like this:
{
entities: {
things: {
orderBy: {
date: [1, 2, 3],
likes: [3, 2, 1]
}
},
thingsById: {
1: {
id: "1",
name: " thing 1"
},
2: {
id: "2",
name: " thing 2"
}
3: {
id: "3",
name: " thing 3"
}
},
tagsById: {
3: {
id: "3",
name: "tech"
}
}
}
}
And the selector would look like:
// Note: this is not optimized for shouldComponentUpdate/PureComponent as it returns a
// different array with every call, which happens on every redux dispatch. Important to
// optimize this either by precomputing these things or memoizing it using `reselect`.
function selectThings(state, props) {
if (state.things && props.orderBy in state.things) {
return state.things[props.orderBy].map(id => state.thingsById[id]);
} else {
return [];
}
}
function mapStateToProps(state, props) {
// Use the queries reducer for an `isLoading` prop
return {
things: selectThings(state, props),
};
}
Somewhere the component is rendered with:
<Things
orderBy="date"
/>
And another component with a different orderBy
:
<Things
orderBy="likes"
/>
How does this seem to you? Does this help?
@ryanashcraft Thanks for your reply. I understand where youre going here. But how would you handle queries with offsets & limits on the same entity? Would you create keys under entity composed of all the filter settings ex:
const state = {
entities: {
things: {
filteredBy: {
'{ offset: 5, limit: 4, tags: "1,2,3"}': [1, 2, 3, 4],
'{ offset: 20, limit: 2, tags: "1,3"}': [6, 7]
}
},
thingsById: {
1: { /* ... */ },
2: { /* ... */ }
}
}
}
Additionally, say I need the total count for a pagination. The api returns a total count header. How can I retrieve this value? How to store it?
@episode17 I'd recommend storing it in a data structure that's convenient for you. If you need direct random access by offset
, limit
, and tags
, then your solution would probably be OK. If it were me I'd probably use a sparse array or some other similar representation that abstracts the offset/limit information.
If I'm understanding your example correctly – the user has requested for things filtered by three tags. Here's how you could represent that with a sparse array. You want to make sure that the "1,2,3" key under filteredBy.tags
is sorted or somehow consistently generated.
const state = {
entities: {
things: {
filteredBy: {
tags: {
"1,2,3": [null, null, null, null, null, 1, 2, 3, 4, null, null, null, null, null, null, null, null, null, null, null, 6, 7],
}
}
},
thingsById: {
1: { /* ... */ },
2: { /* ... */ }
}
}
}
At the end of the day, what's most important is that you're storing things in a way that's efficient and convenient way. Check out the Redux docs on normalizing state, which may be more helpful.
As for the total count header – currently, the response headers are not passed to the transform
function, so there's not really a great way to do that today. Should be pretty a relatively small change to redux-query to get that working, though!
@episode17 Hopefully you got this figured out!