I'm using React Query and I want to update cache after the mutation, but I want to update multiple keys in one go.
I have "invoices" and ["invoice", 1] and I update it as follows:
queryClient.setQueryData("invoices", ({invoices}) => {
return {
invoices: [
...filter(invoices, invoice => invoice.id !== activeInvoice),
{
...find(invoices, invoice => invoice.id === activeInvoice),
customer: data?.updateInvoiceCustomer?.customer
}
]
}
})
queryClient.setQueryData(["invoice", activeInvoice], ({invoice}) => {
return {
invoice: {
...invoice,
customer: data?.updateInvoiceCustomer?.customer
}
}
})
Thus, now I do it per key. Is there any way to do it in one go and to use something like this:
queryClient.setQueriesData(["invoices", ["invoice", activeInvoice]], ({invoices, invoice}) => {
return {
invoices: [
...filter(invoices, invoice => invoice.id !== activeInvoice),
{
...find(invoices, invoice => invoice.id === activeInvoice),
customer: data?.updateInvoiceCustomer?.customer
}
],
invoice: {
invoice: {
...invoice,
customer: data?.updateInvoiceCustomer?.customer
}
}
}
})
Thanks
The API is a bit different, to select both queries.
First option, you would have to change the keys to be ['invoices'] and ['invoices', id], then you can "match them partially", just by providing ['invoices'] to setQueriesData.
But even then you would have to check which one is which inside the updater:
queryClient.setQueriesData(["invoices"], (data) => {
if (Array.isArray(data)) {
// updating the list
} else {
// updating the resource
if (data.id === id) {
// the filter matches not only ['invoices', id] but all ids
}
}
})
To be honest, it doesn't look very ergonomic.
Second option, you can make it a bit more readable by using a predicate as well instead
queryClient.setQueriesData({
predicate: function ({ queryKey }) {
if (queryKey[0] === 'invoices') {
return true
}
if (queryKey[0] === 'invoice' && queryKey[1] === id) {
return true
}
return false
}
}, (data) => {
if (Array.isArray(data)) {
// updating the list
} else {
// updating the resource
}
})
But to be honest, your original code still looks more readble to me
Related
I'm trying to find the right way to create and consequently update inside a map function.
These are the steps I need:
Map function "reads" the array of elements ids
Create new record on "leads_status" table
Using the new record id (from "leads_status") "leads" table is updated using "leads_status.id" as foreign key related to "leads.id_ls"
This is the code I tried.
const [create, { isLoading: isLoadingCreate, error: errorCreate }] = useCreate();
const [record, setRecord] = React.useState(null);
leadsIDS.map((value, index) => {
create('leads_status', {
data: {
id_lead: value,
id_status: 5
}
}, {
onSuccess: ({ id }) => {
setRecord([id, value]);
},
onError: () => {
console.log();
}
});
update('leads', {
id: record[1],
data: {
id_ls: record[0]
}
}, {
enabled: !isLoadingCreate && record !== null
}, {
onSuccess: () => {
console.log(record);
},
onError: error => notify('Error', { type: 'warning' })
})
})
I tried also to put the "update" function inside the "create --> onSuccess" but also there the code is not working as I want.
In "leads_status" table records are always created for each element in "leadsIDS" array but in "leads" table only 1 records is updating.
Where am I wrong?
The useCreate and useUpdate hooks are designed for single actions. If you want to chain several actions, I suggest you use the useDataProvider hook, instead, which lets you manipulate Promises.
const dataProvider = useDataProvider();
const notify = useNotify();
try {
await Promise.all(leadsIDS.map(async (value, index) => {
const { data: leadStatus } = await dataProvider.create('leads_status', {
data: {
id_lead: value,
id_status: 5
}
});
await dataProvider.update('leads', {
id: value,
data: { id_ls: leadStatus.id }
});
}));
} catch (e) {
notify('Error', { type: 'warning' });
}
i want that new updates get continuous added to "kanbanboard" but instead the old value get signed over. enter image description here
ACTION-Function
export const editExpense = (id, updates) => ({
type: "EDIT_EXPENSE",
id,
updates
});
REDUCER-FUNCTION
case "EDIT_EXPENSE":
return state.map((expense) => {
if (expense.id === action.id) {
return {
...expense,
kanbanboard:[{...action.updates}]
};
} else {
return expense;
};
});
Thank you for helping.
I cant see the entire reducer but it looks like you are explicitly overriding the old value. Change it to:
case "EDIT_EXPENSE":
return state.map((expense) => {
if (expense.id === action.id) {
return {
...expense,
kanbanboard:[
...expense.kanbanboard, // you have to add your old state here
{
...action.updates
}]
};
} else {
return expense;
};
});
I am using react-admin and I need to control directly the store from one resource, in my case, the orders resource.
Everytime I run the GET_LISTit appends the new records in the list from the store, but, I would like to get a new list from the server and discard the old ones. Here`s where I retrieve the records:
dataProvider(GET_LIST, 'orders', {
filter: { updatedAt: filterDate }, // Get date from Filter.
sort: { field: 'updatedAt', order: 'DESC' },
pagination: { page: 1, perPage: 999 },
}).then(response => response.data)
So, I decided to manipulate the store directly and after some digging I saw this answer and this code from the source:
const dataReducer: Reducer<RecordSetWithDate> = (
previousState = initialState,
{ payload, meta }
) => {
if (meta && meta.optimistic) {
if (meta.fetch === UPDATE) {
const updatedRecord = {
...previousState[payload.id],
...payload.data,
};
return addRecords([updatedRecord], previousState);
}
if (meta.fetch === UPDATE_MANY) {
const updatedRecords = payload.ids.map(id => ({
...previousState[id],
...payload.data,
}));
return addRecords(updatedRecords, previousState);
}
if (meta.fetch === DELETE) {
return removeRecords([payload.id], previousState);
}
if (meta.fetch === DELETE_MANY) {
return removeRecords(payload.ids, previousState);
}
}
if (!meta || !meta.fetchResponse || meta.fetchStatus !== FETCH_END) {
return previousState;
}
switch (meta.fetchResponse) {
case GET_LIST:
case GET_MANY:
case GET_MANY_REFERENCE:
return addRecords(payload.data, previousState);
case GET_ONE:
case UPDATE:
case CREATE:
return addRecords([payload.data], previousState);
default:
return previousState;
}
};
So, based on that, I created a custom action to delete the old ids from my list and add the new ones retrieved from the data source:
import {GET_LIST, DELETE_MANY, FETCH_END } from 'react-admin';
export const UPDATE_ORDER_ADMIN = 'UPDATE_ORDER_ADMIN';
export const update_orders_admin = (data, oldIDS) => ({
type: UPDATE_ORDER_ADMIN,
payload: { data, ids: oldIDS },
meta: {
resource: 'orders',
optimistic: true,
fetch: DELETE_MANY,
fetchResponse: GET_LIST,
fetchStatus: FETCH_END,
},
});
And I am using this custom action after retrieve data from the backend:
dataProvider(GET_LIST, 'orders', {
filter: { updatedAt: filterDate }, // Get date from Filter.
sort: { field: 'updatedAt', order: 'DESC' },
pagination: { page: 1, perPage: 999 },
}).then(response => response.data)
.then(data => {
const ids = orders ? Object.keys(orders) : [];
update_orders_admin(data, ids);
this.setState({ isLoading: false })
return null;
});
However, the system is calling the DELETE action from backend, trying to delete the records from the database, while, what I would like is just delete these records from my view.
Any thoughts?
In your custom action you have the fetch set as DELETE_MANY which will do a loop over every id performing DELETE operation. Not sure if you implementation will work, but the current error is about that. You could try to remove the fetch ans see what happens, but I think without it he will not fetch records. If I'm not mistaken RA only adds new ids to data, however if data changed in the meantime I don't think it will replace the changed data for that you need to reimplement the data provider to change the update data behaviour which is similar to what you're trying.
I am trying to update multiple docs in mongo.
I have an array, and I want to update multiple mongo docs with a specific object from that array.
let items = [{ order: 0 }, { order: 1 }, { order: 2 }]
I have tried this for updating mongo:
items.map((item) => {
db.items.findOneAndUpdate(
{ _id: item._id },
{ $set: { order: item.order } }
)
})
If I check items, it has actually updated the array:
console.log(items)
But when I look in my database, MongoDB has not updated.
Any suggestions what I'm doing wrong?
Thanks!
map is a synchronous function and you are using it to for an asynchronous update with findOneAndUpdate, akin to mixing water and oil.
You either need to use async/await or Promise.all with the map function. For example, with the above, you can create an array of promises with the map function and resolve with Promise.all
const updatePromises = items.map(({ _id, order }) => (
Item.findOneAndUpdate({ _id }, {'$set': { order } })
))
Promise.all(updatePromises).then(console.log).catch(console.error)
Using async/await
(async () => {
try {
for(let i=0; i<items.length; i++) {
await Item.findOneAndUpdate(
{ _id: item._id },
{ $set: { order: item.order } }
)
}
} catch (err) {
console.error(err)
}
})()
I am trying to use lodash to filter objects in a react app.
const filteredContacts = _(contacts)
.map((contact, id) => ({_id: id, ...contact}))
.filter(contact => !(this.state.filter && contact.lastName.toLowerCase().indexOf(this.state.filter) == -1 && contact.firstName.toLowerCase().indexOf(this.state.filter) == -1))
.filter(c => {
_(c.courses)
.map((course, id) => ({_id: id, ...course}))
.each(course => {
console.log(course._id)
return (course._id == this.state.courseId)
})
})
This is pulling from a firebase database, so there are nodes which are just a key, no value, that then have children. In this case I have capitalized where those are below.
contacts > USERID > courses > COURSEID > coursedata
or
contacts: {
-uf39uhef2: {
name: blah,
courses: {
-dfh92ehfdsfhw: {
coursename: name
}
}
}
}
I know that my second filter is all kinds of wrong - I just pasted the last iteration of a million tries. this.state.courseId is the course ID I want to filter by - how can I accomplish this?
I want to return all users that have a course ID matching the one set in state.
In the filter check if the contact has courses, and if coureses[this.state.courseId] is truthy:
const filteredContacts = _(contacts)
.map((contact, id) => ({_id: id, ...contact}))
.filter(({ courses }) => _.has(courses, this.state.courseId));
const contacts = {
'-uf39uhef2': {
name: 'blah',
courses: {
'-dfh92ehfdsfhw': {
coursename: 'name'
}
}
},
'-willBeFiltered': {
name: 'willBeFiltered',
courses: {
'-willBeFiltered': {
coursename: 'name'
}
}
}
};
const courseId = '-dfh92ehfdsfhw'; // courseId = this.state.courseId
const filteredContacts = _(contacts)
.map((contact, id) => ({_id: id, ...contact}))
.filter(({ courses }) => _.has(courses, courseId));
console.log(filteredContacts);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>
You can also do this without lodash:
const filteredContacts = Object.keys(contacts)
.map((id) => ({_id: id, ...contacts[id]}))
.filter(({ courses }) => courses && courses[this.state.courseId]);
just use _.pickBy to to get needed users
_.pickBy(contacts, (user) => {
return _.chain(user)
.get('courses')
.keys()
.includes(courseId)
.value();
});
another way
_.pickBy(contacts, (user) => {
return _.has(user, ['courses', courseId]);
})