Handling dependent react queries - javascript

I have two react queries in the same component
const { data: sdrData, status: sdrDataLoading } = useQuery(
[queryInfo[0]?.dcsSysId, data[0]?.dcsStructSysId, data[cardIndex]?.dcsStructNodeId],
() => getSDR(queryInfo[0]?.dcsSysId, data[0]?.dcsStructSysId, data[cardIndex]?.dcsStructNodeId),
);
const { isIdle, data: sdrTemplateData, status: sdrTemplateDataLoading } = useQuery(
[sdrData[0]?.dcsSdrSysId, queryInfo[0]?.fldTemplateSysId],
() =>
SDRTemplateValues(sdrData[0]?.dcsSdrSysId, queryInfo[0]?.fldTemplateSysId, {
// The query will not execute until the userId exists
enabled: !!sdrData[0]?.dcsSdrSysId,
retry: true,
}),
);
My second query is depended on the first I need to access sdrData[0] for the first arg in my query however when I do this the query is undefined initially and it fails. Is there a good way to handle this. I saw you can set it equal to a variable, but I'm still faced with the same problem.
I need a way to tell the first query to wait until the second query is finished before it tried to access the arguments. I thought you could set enabled like I did, but that didn't work either.

I was able to solve this by adding the data parameter I need to the beginning of the argument array like this.
const { data: sdrData, status: sdrDataLoading } = useQuery(
queryInfo[0]?.dcsSysId && [
queryInfo[0]?.dcsSysId,
data[0]?.dcsStructSysId,
data[cardIndex]?.dcsStructNodeId,
],
() => getSDR(queryInfo[0]?.dcsSysId, data[0]?.dcsStructSysId, data[cardIndex]?.dcsStructNodeId),
);
const { data: sdrTemplateData, status: sdrTemplateDataLoading } = useQuery(
sdrData?.[0]?.dcsSdrSysId && [sdrData?.[0]?.dcsSdrSysId, queryInfo[0]?.fldTemplateSysId],
() => SDRTemplateValues(sdrData?.[0]?.dcsSdrSysId, queryInfo[0]?.fldTemplateSysId),
);

You are on the right path here.
You just need to check if sdrData is undefined. You are trying to access the first element (sdrData[0]) but initially it's undefined.
...
{
enabled: !!(sdrData && sdrData[0]?.dcsSdrSysId
}
...

Related

Rendered more hooks than during the previous render after apollo useQuery

I'm tying to find a way to correct the Hook rendering error. I have a total of 3 useQuery hooks being rendered :
const {
data: OSData,
error: OSError,
loading: OSLoading,
} = useQuery(OSData, {
variables: {
NUMBER: UniqueList,
},
})
const {
data: RamData,
error: RamERROR,
loading: RamLOADING,
} = useQuery(GET_Ram)
const {
data: Hardware,
error: HardwareERROR,
loading: HardwareLOADING,
} = useQuery(GET_Hardware)
The variable 'NUMBER' is based on a list 'UniqueList' that is made from the GET_Ram and GET_Hardware queries so the OSData query needs to be called later or there's an undefined variable. However, calling the OSData Query later in the code gives me a render error.
Any idea on how I could accomplish this?
Thank you!
an answer I found is using lazy query.
const SomeData [{
called, loading, data
}] = useLazyQuery(OSData)
})
if (called && loading) return <p>Loading ...</p>
if (HardwareLOADING || RamLOADING) return <p> loading</p>
if (HardwareERROR || RamERROR) return <p>error</p>
//perform all the needed calculations for the variable here
and in the return statement you can call the query and provide the variable. Here I use a button.
<div>
<button onClick={() => SomeData({ variables: { NUMBER: uniqueList } })}>
Load{' '}
</button>
</div>
Hope this helps someone

Trigger cache to make sure data is right problem - swr hook - next.js ( with typescript )

In my next.js app i'm using the swr hook since it provides cache and real time updates, and that is quite great for my project ( facebook clone ), but, there's a problem.
The problem, is that, in my publications, i fetch them along with getStaticProps, and i just map the array and everything great, but, when i do an action, like, liking a post, or commenting a post, i mutate the cache, and i thought that what that does, is to ask the server if the information that is in the cache is right.
But, what it really does, is that, it makes another API call, and, the problem with that, is that, if i like a publication, after the call to the API to make sure everything is right in the cache, if there are 30 new publications, they will appear in the screen, and i don't want that, i want the pubs the user on screen requested at the beggining, imagine comenting on a post, then there are 50 new post so you lost the post where you commented...
Let me show you a litle bit of my code.
First, let me show you my posts interfaces
// Publication representation
export type theLikes = {
identifier: string;
};
export type theComment = {
_id?: string;
body: string;
name: string;
perfil?: string;
identifier: string;
createdAt: string;
likesComments?: theLikes[];
};
export interface Ipublication {
_id?: string;
body: string;
photo: string;
creator: {
name: string;
perfil?: string;
identifier: string;
};
likes?: theLikes[];
comments?: theComment[];
createdAt: string;
}
export type thePublication = {
data: Ipublication[];
};
This is where i'm making the call to get all posts
const PublicationsHome = ({ data: allPubs }) => {
// All pubs
const { data: Publications }: thePublication = useSWR(
`${process.env.URL}/api/publication`,
{
initialData: allPubs,
revalidateOnFocus: false
}
);
return (
<PublicationsHomeHero>
{/* Show pub */}
{Publications.map(publication => {
return <Pubs key={publication._id} publication={publication} />;
})}
</PublicationsHomeHero>
</>
);
};
export const getStaticProps: GetStaticProps = async () => {
const { data } = await axios.get(`${process.env.URL}/api/publication`);
return {
props: data
};
};
export default PublicationsHome;
And, for example, this is how i create a comment, i update cache, make the call to the API, then mutate to see if data is right
// Create comment
const handleSubmit = async (e: FormEvent<HTMLFormElement>): Promise<void> => {
e.preventDefault();
try {
mutate(
`${process.env.URL}/api/publication`,
(allPubs: Ipublication[]) => {
const currentPub = allPubs.find(f => f === publication);
const updatePub = allPubs.map(pub =>
pub._id === currentPub._id
? {
...currentPub,
comments: [
{
body: commentBody,
createdAt: new Date().toISOString(),
identifier: userAuth.user.id,
name: userAuth.user.name
},
...currentPub.comments
]
}
: pub
);
return updatePub;
},
false
);
await createComment(
{ identifier: userAuth.user.id, body: commentBody },
publication._id
);
mutate(`${process.env.URL}/api/publication`);
} catch (err) {
mutate(`${process.env.URL}/api/publication`);
}
};
Now, after creating the comment, as i already mentioned, it makes another call to the API, and if there are new posts or whatever, it will appear in the screen, and i just want to keep the posts i have or add new ones if i'm the one that created them.
So, let's say that i will like a post
Everything is great and fast, but, after making sure data is right, another post will appear because another user created it
Is there a way to make sure data is right without making another call to the API that will add more posts to the screen ?
I'm new to this swr hook, so, hope you can help me and thanks for your time !
Upate
There's a way to update cache without needing to refetch
many POST APIs will just return the updated data directly, so we don’t need to revalidate again. Here’s an example showing the “local mutate - request - update” usage:
mutate('/api/user', newUser, false) // use `false` to mutate without revalidation
mutate('/api/user', updateUser(newUser)) // `updateUser` is a Promise of the request,
// which returns the updated document
But, i don't kwow how should i change my code to implement this, any ideas !?
If you want to update the cache, and make sure everything is right, withouth having to make another call to the API, this seems like working
Change this
await createComment(
{ identifier: userAuth.user.id, body: commentBody },
publication._id
);
mutate(`${process.env.URL}/api/publication`);
For this
mutate(
`${process.env.URL}/api/publication`,
async (allPublications: Ipublication[]) => {
const { data } = await like(
{ identifier: userAuth.user.id },
publication._id
);
const updatePub = allPublications.map(pub =>
pub._id === data._id ? { ...data, likes: data.likes } : pub
);
return updatePub;
},
false
);
What you are doing there, is to update cache with the data that you'll receive data from the API's action, and you put it in the cache, but, you have to put false as well so it doesn't revalidate again, i tried it out and it's working, don't know if i'll have problems with it in the future, but for knowm it works great !

React Hooks: Handling Objects as dependency in useEffects

UPDATE: Yes for use case 1, if I extract search.value outside the useEffect and use it as a dependency it works.
But I have an updated Use case below
Use Case 2: I want to pass a searchHits Object to the server. The server in turn return it back to me with an updated value in response.
If I try using the searchHits Object I still get the infinite loop
state: {
visible: true,
loading: false,
search: {
value: “”,
searchHits: {....},
highlight: false,
}
}
let val = search.value
let hits = search.searchHits
useEffect( () => {
axios.post(`/search=${state.search.value}`, {hits: hits}).then( resp => {
…do something or ..do nothing
state.setState( prevState => {
return {
…prevState,
search: {... prevState.search, hits: resp.hit}
}
})
})
}, [val, hits])
Use Case 1: I want to search for a string and then highlight when I get results
e.g.
state: {
visible: true,
loading: false,
search: {
value: “”,
highlight: false,
}
}
useEffect( () => {
axios.get(`/search=${state.search.value}`).then( resp => {
…do something or ..do nothing
state.setState( prevState => {
return {
…prevState,
search: {... prevState.search, highlight: true}
}
})
})
}, [state.search])
In useEffect I make the API call using search.value.
eslint complains that there is a dependency on state.search , it does not recognize state.search.value. Even if you pass state.search.value it complains about state.search
Now if you pass state.search as dependecy it goes in an infinite loop because after the api call we are updating the highlights flag inside search.
Which will trigger another state update and a recursive loop.
One way to avoid this is to not have nested Objects in state or move the highlights flag outside search, but I am trying to not go that route give the sheer dependecies I have.
I would rather have an Object in state called search the way it is. Is there any way to better approach this.
If I want to keep my state Object as above how do I handle the infinite loop
Just a eslint stuff bug may be. You have retracted some code by saying //do something and have hidden he code. Are you sure that it doesn't have anything to do with search object?
Also, try to extract the variable out before useEffect().
const searchValue = state.search.value;
useEffect(()=>{// axios call here},[searchValue])
If your search value is an object, react does shallow comparison and it might not give desired result. Re-rendering on a set of object dependencies isn't ideal. Extract the variables.
React does shallow comparison of dependencies specified in useEffect
eg.,
const {searchParam1, searchParam2} = search.value;
useEffect(() => {
//logic goes here
}, [searchParam1, searchParam2]);
Additionally, you can add dev dependency for eslint-plugin-react-hooks, to identify common errors with hooks

Feathers-mongoose : Get by custom attribute in feathers-mongoose

I have a very basic feathers service which stores data in mongoose using the feathers-mongoose package. The issue is with the get functionality. My model is as follows:
module.exports = function (app) {
const mongooseClient = app.get('mongooseClient');
const { Schema } = mongooseClient;
const messages = new Schema({
message: { type: String, required: true }
}, {
timestamps: true
});
return mongooseClient.model('messages', messages);
};
When the a user runs a GET command :
curl http://localhost:3030/messages/test
I have the following requirements
This essentially tries to convert test to ObjectID. What i would
like it to do is to run a query against the message attribute
{message : "test"} , i am not sure how i can achieve this. There is
not enough documentation for to understand to write or change this
in the hooks. Can some one please help
I want to return a custom error code (http) when a row is not found or does not match some of my criterias. How can i achive this?
Thanks
In a Feathers before hook you can set context.result in which case the original database call will be skipped. So the flow is
In a before get hook, try to find the message by name
If it exists set context.result to what was found
Otherwise do nothing which will return the original get by id
This is how it looks:
async context => {
const messages = context.service.find({
...context.params,
query: {
$limit: 1,
name: context.id
}
});
if (messages.total > 0) {
context.result = messages.data[0];
}
return context;
}
How to create custom errors and set the error code is documented in the Errors API.

MeteorJS: How to get title data via server method by given id array

What is the 'meteor'-way to get a document title by a given ID?
Collection (Articles)
{
'_id' : 'Dn59y87PGhkJXpaiZ',
'title' : 'Sample Article',
'slug' : 'sample-article'
}
client
render() {
const data = [
{ _id: 'Dn59y87PGhkJXpaiZ' },
{ _id: 'kJXpaiZDn59y87PGh' }
{ _id: 'y87PGhkJXpaiZDn59' }
]
return (
<List>
{
data.map(r => {
return <List.Item>r._id</List.Item>
})
}
)
}
With this I will get this output:
<List>
<List.Item>Dn59y87PGhkJXpaiZ</List.Item>
<List.Item>kJXpaiZDn59y87PGh</List.Item>
<List.Item>y87PGhkJXpaiZDn59</List.Item>
</List>
Now I want to display the title instead of the id. So normally I would do
data.map(r => {
const title = Articles.findOne({ _id: r._id }).title
return <List.Item>title</List.Item>
})
But the problem is, that data is a dynamic dataset and I can't/don't want to publish the complete Articles collection. Right now there is no subscription, so I don't get any results for the title.
So I think I have to do a server side call.
Meteor.call('getTitle', r._id, function(err, res) {
console.log(res)
})
But then I'll get the result in the callback function. So how do I get these into the list? Also I want to avoid multiple method calls. I think it would be better to send data and get all titles on server side and then build the list.
If you can/want to use a non async call, don't pass a callback to the Meteor.call() method:
data.map(r => {
const title = Meteor.call('getTitle',r._id);
return <List.Item>title</List.Item>
})
As stated in the docs:
If you do not pass a callback on the server, the method invocation will block until the method is complete. It will eventually return the return value of the method, or it will throw an exception if the method threw an exception.
To fetch and render the data meteor way you have to use the package called react-meteor-data to create createContainer.
For example if you were to use it then you would be able to pass it directly to the component as props.
export default createContainer((props) => {
Meteor.subscribe('questions');
return {
questions: Questions.findOne({_id: props.match.params.id})
};
}, QuestionDo);

Categories