Best way to chain nested loops of mutations in React-Query? - javascript

Let's say I have the following piece of data, a list of puzzle objects, where each object has a puzzle_name field and a list of patterns within it:
const puzzles = [
{
puzzle_name: 'Test Data 1',
patterns: [
{pattern_name: 'test', type: 'test type', hours: 12},
{pattern_name: 'test 2', type: 'test type 2', hours: 22}
]
},
{
puzzle_name: 'Test Data 2',
patterns: [
{pattern_name: 'test', type: 'test type', hours: 12},
{pattern_name: 'test 2', type: 'test type 2', hours: 22},
{pattern_name: 'test 3', type: 'test type 3', hours: 33}
]
}
];
The goal here is to create each of the puzzles first (using just their names), get their ID once created, and create each individual pattern using the puzzle's ID. So I need to talk to two POST routes for this list of data. The first one takes a single object with the pattern_name field, so -
POST /puzzle takes {puzzle_name: string} as the body and returns {puzzle_name: string, puzzle_id: string}
The second route needs this puzzle_id in its param and a single pattern object at a time (from the patterns list above) -
So that would be POST /my-app.com/{puzzleId}/pattern, which takes a pattern object as its body and it returns the newly created object, which we don't need to worry about for the sake of this example.
Now for my attempt at this, I have the following two mutations -
const createPuzzleMutation = useMutation(({body}) => createPuzzle(body));
const createPatternMutation = useMutation(({puzzleId, body}) => createPattern(puzzleId, body));
And then I'm iterating through puzzles and creating a list of promises for the createPuzzleMutation. In the onSuccess function for each puzzle, I'm then going through the corresponding patterns list and calling the createPatternMutation using the ID of the puzzle, like so -
const puzzlesMutations = puzzles.map((puzzle) =>
createPuzzleMutation.mutateAsync(
{body: {name: puzzle.puzzle_name}},
{
onSuccess: async (data) => {
const patternsMutations = puzzle.patterns.map((pattern) => {
return createPatternMutation.mutateAsync(
{
puzzle_id: data.data.id,
body: {
...pattern
}
}
);
});
// waiting for all patterns to be created under a particular puzzle
await Promise.allSettled(patternsMutations);
}
}
)
);
// waiting for an individual puzzle to be created
await Promise.allSettled(puzzlesMutations);
It's creating both the puzzle names just fine, so it's talking to the first route, the POST /puzzle route just fine and getting the ID's for both the puzzles. But it's only calling the second route (POST /my-app.com/{puzzleId}/pattern) for the patterns list in the second puzzle. It's not calling the second POST route for the patterns array in the first puzzle.
I saw in the docs that for consecutive mutations, they'll be fired only once. Is this constraint related to my example here? Or am I missing something? Would appreciate any help.

I saw in the docs that for consecutive mutations, they'll be fired only once. Is this constraint related to my example here?
yes it is. If you want a callback that fires for every invocation of a mutation, you need to use the onSuccess callback of useMutation itself, not the callback on mutateAsync. There, you can also return a Promise, and react-query will await it before marking the mutation as "finished".
const createPuzzleMutation = useMutation(
({body}) => createPuzzle(body),
{
onSuccess: async (data) => {
const patternsMutations = puzzle.patterns.map((pattern) => {
return createPatternMutation.mutateAsync(
{
puzzle_id: data.data.id,
body: {
...pattern
}
}
);
});
// waiting for all patterns to be created under a particular puzzle
return Promise.allSettled(patternsMutations);
}
}
}
);

Related

How to assign first state data to second state in react?

When i am setting the data from one state to another state. It's saying undefined. Currently i am working on tinder like card swipe functionality with Right and left button click.
I am passing the user id from card to to button. Such as Right swipe and left swipe button.
//Scenario first
If have declared the array of object static, it's works like a charm, then it does not says any error.
////Scenario Second
If i am setting the data dynamically with API to SetState and assigning the state variable array data to another state variable, it says undefined.
I am trying to solve this issue from 3 days, but nothing worked, i am new in React js. Help would be appreciate.
Here is my code
const AllData= [
{
id: 1,
name: 'XYZ'
},
{
id: 2,
name: 'ABC'
},
{
id: 3,
name: 'ABC 2'
},
{
id: 4,
name: 'ABC 3'
},
{
id: 5,
name: 'ABC 4'
}
] //It works if set static array
const [AllData, setAllData] = useState([]); //it does not works
const GetAllUserData = async () =>{
const bodyParameters ={
session_id : SessionId
};
const {data : {data}} = await axios.post(GETALLUSER_API , bodyParameters);
setAllData(data);
}
// Setting current user from all data
const [currentUser, setCurrentUser] = useState(AllData[0])
console.log(currentUser); // undefined says
Here, AllData will be added to the state after the GetAllUserData is done executing, it is asynchronous function, so AllData will be available after some time, you have to update the currentUser you have to do like this.
useEffect(() => {
if (AllData.length) {
setCurrentUser(AllData[0]);
}
}, [AllData]);
It didn't worked because you may have returned before the setAllData could get called initialize AllData.
Being a api call it take some time to return data.
A good work around for this situation is to put a loader until it get the data and when you eventually receive the data then only render the content on the screen.

Filtering a JSON with Javascript

I am trying to filter a JSON using filter and I'm not getting the clubProducts to return as I hoped (allProducts works fine). Any help is appreciated. Thank you
const state = {
added: [],
all: [
{
id: 'bcd755a6-9a19-94e1-0a5d-426c0303454f',
name: 'Iced Coffee',
description: 'Coffee, now featuring ice.',
image: 'https://images.com',
price: 899,
fxCategory: 'Coffee'
},
{
id: 'cc919e21-9a19-94e1-ace9-426c0303454f',
name: 'The 2ndItem',
description: 'Wouldn't you like to know.',
image: 'https://images.com',
price: 499,
fxCategory: 'Club'
}
]
}
const getters = {
allProducts: state => state.all,
clubProducts: state => function () {
return state.all.filter(item => item.fxCategory == 'Club')
}
}
EDIT: Updated with latest attempt as per suggestions
You made two mistakes: you can use filter() only on an array (ie state.all in your case), and in your comparison you didn't quote the string 'Club'.
Also, your filter() can be written in a shorter way, as such:
clubProducts: state.all.filter(item => item.fxCategory == 'Club')
See documentation for more.
As mentioned by #Reyedy you can call filter function directly in 'clubProducts' field and this example is works.
But you may get an error because you use single quotes in 'description' field.
Try in state.all
description: "Wouldn't you like to know.",
instead of
description: 'Wouldn't you like to know.',
This is the only place I got an error trying to repeat your example.

Resolve Custom Types at the root in GraphQL

I feel like I'm missing something obvious. I have IDs stored as [String] that I want to be able to resolve to the full objects they represent.
Background
This is what I want to enable. The missing ingredient is the resolvers:
const bookstore = `
type Author {
id: ID!
books: [Book]
}
type Book {
id: ID!
title: String
}
type Query {
getAuthor(id: ID!): Author
}
`;
const my_query = `
query {
getAuthor(id: 1) {
books { /* <-- should resolve bookIds to actual books I can query */
title
}
}
}
`;
const REAL_AUTHOR_DATA = [
{
id: 1,
books: ['a', 'b'],
},
];
const REAL_BOOK_DATA = [
{
id: 'a',
title: 'First Book',
},
{
id: 'b',
title: 'Second Book',
},
];
Desired result
I want to be able to drop a [Book] in the SCHEMA anywhere a [String] exists in the DATA and have Books load themselves from those Strings. Something like this:
const resolve = {
Book: id => fetchToJson(`/some/external/api/${id}`),
};
What I've Tried
This resolver does nothing, the console.log doesn't even get called
const resolve = {
Book(...args) {
console.log(args);
}
}
HOWEVER, this does get some results...
const resolve = {
Book: {
id(id) {
console.log(id)
return id;
}
}
}
Where the console.log does emit 'a' and 'b'. But I obviously can't scale that up to X number of fields and that'd be ridiculous.
What my team currently does is tackle it from the parent:
const resolve = {
Author: {
books: ({ books }) => books.map(id => fetchBookById(id)),
}
}
This isn't ideal because maybe I have a type Publisher { books: [Book]} or a type User { favoriteBooks: [Book] } or a type Bookstore { newBooks: [Book] }. In each of these cases, the data under the hood is actually [String] and I do not want to have to repeat this code:
const resolve = {
X: {
books: ({ books }) => books.map(id => fetchBookById(id)),
}
};
The fact that defining the Book.id resolver lead to console.log actually firing is making me think this should be possible, but I'm not finding my answer anywhere online and this seems like it'd be a pretty common use case, but I'm not finding implementation details anywhere.
What I've Investigated
Schema Directives seems like overkill to get what I want, and I just want to be able to plug [Books] anywhere a [String] actually exists in the data without having to do [Books] #rest('/external/api') in every single place.
Schema Delegation. In my use case, making Books publicly queryable isn't really appropriate and just clutters my Public schema with unused Queries.
Thanks for reading this far. Hopefully there's a simple solution I'm overlooking. If not, then GQL why are you like this...
If it helps, you can think of this way: types describe the kind of data returned in the response, while fields describe the actual value of the data. With this in mind, only a field can have a resolver (i.e. a function to tell it what kind of value to resolve to). A resolver for a type doesn't make sense in GraphQL.
So, you can either:
1. Deal with the repetition. Even if you have ten different types that all have a books field that needs to be resolved the same way, it doesn't have to be a big deal. Obviously in a production app, you wouldn't be storing your data in a variable and your code would be potentially more complex. However, the common logic can easily be extracted into a function that can be reused across multiple resolvers:
const mapIdsToBooks = ({ books }) => books.map(id => fetchBookById(id))
const resolvers = {
Author: {
books: mapIdsToBooks,
},
Library: {
books: mapIdsToBooks,
}
}
2. Fetch all the data at the root level instead. Rather than writing a separate resolver for the books field, you can return the author along with their books inside the getAuthor resolver:
function resolve(root, args) {
const author = REAL_AUTHOR_DATA.find(row => row.id === args.id)
if (!author) {
return null
}
return {
...author,
books: author.books.map(id => fetchBookById(id)),
}
}
When dealing with databases, this is often the better approach anyway because it reduces the number of requests you make to the database. However, if you're wrapping an existing API (which is what it sounds like you're doing), you won't really gain anything by going this route.

Normalizr - is it a way to generate IDs for non-ids entity model?

I'm using normalizr util to process API response based on non-ids model. As I know, typically normalizr works with ids model, but maybe there is a some way to generate ids "on the go"?
My API response example:
```
// input data:
const inputData = {
doctors: [
{
name: Jon,
post: chief
},
{
name: Marta,
post: nurse
},
//....
}
// expected output data:
const outputData = {
entities: {
nameCards : {
uniqueID_0: { id: uniqueID_0, name: Jon, post: uniqueID_3 },
uniqueID_1: { id: uniqueID_1, name: Marta, post: uniqueID_4 }
},
positions: {
uniqueID_3: { id: uniqueID_3, post: chief },
uniqueID_4: { id: uniqueID_4, post: nurse }
}
},
result: uniqueID_0
}
```
P.S.
I heard from someone about generating IDs "by the hood" in normalizr for such cases as my, but I did found such solution.
As mentioned in this issue:
Normalizr is never going to be able to generate unique IDs for you. We
don't do any memoization or anything internally, as that would be
unnecessary for most people.
Your working solution is okay, but will fail if you receive one of
these entities again later from another API endpoint.
My recommendation would be to find something that's constant and
unique on your entities and use that as something to generate unique
IDs from.
And then, as mentioned in the docs, you need to set idAttribute to replace 'id' with another key:
const data = { id_str: '123', url: 'https://twitter.com', user: { id_str: '456', name: 'Jimmy' } };
const user = new schema.Entity('users', {}, { idAttribute: 'id_str' });
const tweet = new schema.Entity('tweets', { user: user }, {
idAttribute: 'id_str',
// Apply everything from entityB over entityA, except for "favorites"
mergeStrategy: (entityA, entityB) => ({
...entityA,
...entityB,
favorites: entityA.favorites
}),
// Remove the URL field from the entity
processStrategy: (entity) => omit(entity, 'url')
});
const normalizedData = normalize(data, tweet);
EDIT
You can always provide unique id's using external lib or by hand:
inputData.doctors = inputData.doctors.map((doc, idx) => ({
...doc,
id: `doctor_${idx}`
}))
Have a processStrategy which is basically a function and in that function assign your id's there, ie. value.id = uuid(). Visit the link below to see an example https://github.com/paularmstrong/normalizr/issues/256

How to extend/modify the object in nodejs api?

i am using the angular-fullstack yeoman generator. created a schema for a Product, and a set of api crud operations. all works well. now in the get list operations, i don't want to receive all the fields, only a subset. like a select in sql. i would also wish to alter one value. instead of the price, i need price * 1.1 .
how to do that?
here is the code for the index method (returns list of products):
// Gets a list of Products
export function index(req, res) {
Product.findAsync()
.then(respondWithResult(res))
.catch(handleError(res));
}
function respondWithResult(res, statusCode) {
statusCode = statusCode || 200;
return function(entity) {
if (entity) {
res.status(statusCode).json(entity);
}
};
}
As stated in the documentation, .find() takes two params, query and projection.
// params
Product.findAsync(query, projection)
You can use projection to "select" a subset of fields;
// example
Product.findAsync({}, { _id: 1, name: 1, description: 1 })
// result, only the three specified field will be returned
[
{ _id: 'abc123', name: 'Some name', description: 'Some description'},
{...}
]
If you want to manipulate data I think you have to use the aggregation pipeline

Categories

Resources