Async validation with useLazyQuery hook - apollo-client

How to handle field level async validation in react-hook-form by using useLazyQury hook of Apollo Client?
As far I understand the useLazyQuery hook is the only way to initialize a GraphQL request on some action (like onClick). Unfortunately for me this hook returns void and forces me to use data variable to get value. But. The react-hook-form requires to return a true/false value in asyncValidation. How to handle such case?
const AsyncValidation = () => {
const [nicknameUniqueness, data ] = useLazyQuery(NICKNAME_UNIQUENESS_QUERY)
return (
<input
{...register('nickname', {
validate: {
asyncValidate: (value) => {
nicknameUniqueness({ variables: { nickname: value } }) // returns void by Apollo documentation
// How to get up-to-dated data here?
return data?.nicknameUniqueness.isUnique
}
}
})}
/>
)
}

I think you have to use useQuery here instead of useLazyQuery. You just have to set skip to true in your options object and then you can use refetch to lazy load/validate your nickname as refetch will return the ApolloQueryResult. One important thing is to use an async function here for your asyncValidate function so that you can await the result of that query call.
const AsyncValidation = () => {
const { refetch: nicknameUniqueness } = useQuery(NICKNAME_UNIQUENESS_QUERY, {
skip: true
});
return (
<input
{...register("nickname", {
validate: {
asyncValidate: async (value) => {
const { data } = await nicknameUniqueness({
variables: { nickname: value }
});
return data?.nicknameUniqueness.isUnique;
}
}
})}
/>
);
};
I made a small example to load data async using useQuery.

Related

Cannot destructure property of {intermediate value} as it is undefined

I have just started using graphql for the first time as I have integrated my NEXTJS app with strapi. But I have received this error message Cannot destructure property 'data' of '(intermediate value)' as it is undefined.
I followed this tutorial - enter link description here
Just modified it to what I wanted. This is my graphql:
query {
posts {
data {
attributes {
heading
}
}
}
}
And this is my vs code:
export async function getStaticProps() {
const client = new ApolloClient({
url: 'http://localhost:1337/graphql/',
cache: new InMemoryCache(),
})
const { data } = await client.query({
query: gql`
query {
posts {
data {
attributes {
heading
}
}
}
}
`,
})
return {
props: {
posts: data.posts,
},
}
}
FULL CODE:
import { ApolloClient, InMemoryCache, gql } from '#apollo/client'
export default function Blog({ posts }) {
console.log('posts', posts)
return (
<div>
{posts.map(post => {
return (
<div>
<p>{posts.heading}</p>
</div>
)
})}
</div>
)
}
export async function getStaticProps() {
const client = new ApolloClient({
url: 'http://localhost:1337/graphql/',
cache: new InMemoryCache(),
})
const { data } = await client.query({
query: gql`
query {
posts {
data {
attributes {
heading
}
}
}
}
`,
})
return {
props: {
posts: data.posts,
},
}
}
I really don't know where to begin with this.
Firstly check whether or not you are receiving empty data from API.
If its array, check its length or use methods like Array.isArray(myArray).
If its object, make a function like this to check objects.
function isObjectEmpty(obj) {
return (
!!obj && // 👈 null and undefined check
Object.keys(obj).length === 0 &&
obj.constructor === Object
)
}
export default isObjectEmpty
if the data is empty return notFound prop as true to show your 404 page.
// This function gets called at build time
export async function getStaticProps({ params, preview = false }) {
// fetch posts
// check validity data
return isObjectEmpty(pageData)
? { notFound: true }
: {
props: {
posts
}
}
}
Secondly add a failsafe mechanism like the use of optional-chaining to securely access nested values/properties.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
export default function Blog({ posts }) {
console.log('posts', posts)
return (
<div>
{posts?.length && posts?.map(post => {
return (
<div>
<p>{posts?.heading}</p>
</div>
)
})}
</div>
)
}
I was running into the same error while testing using Jest.
It turns out I was mocking all of graphql, but I had to specifically mock the return value.

Nested dispatch function does not get update props

app.js
const mapStateToProps = (state) => {
return {home:state}
}
const mapDispatchToProps = (dispatch) => {
return {
guestLogin: (data)=>{dispatch(guestLogin(data)).then(()=>{
dispatch(initiateTrans(stateProps.home))
})},
};
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
return Object.assign({}, ownProps, stateProps, dispatchProps,{
initiateTrans: () => dispatchProps.initiateTrans(stateProps.home),
})
}
Action.js
export const guestLogin= (state)=>{
var data={
'email':state.email,
'name':state.name,
'phone_number':state.ph_number,
'phone_code':state.country_code
}
return function(dispatch) {
return dataservice.guestSignup(data).then(res => {
dispatch(afterLoggedGuest(res))
}).catch(error => {
throw(error);
});
}
}
function afterLoggedGuest(result) {
return {type: guestLoginChange, result};
}
export const initiateTrans= (updatedState)=>{
return function(dispatch) {
return dataservice.initiateTransaction(updatedState).then(res => {
console.log("initiateTransaction",res)
}).catch(error => {
throw(error);
});
}
}
Reducer.js
if(action.type === guestLoginChange){
return {
...state,guestData: {
...state.guestData,
Authorization: action.result.authentication ,
auth_token: action.result.auth_token ,
platform: action.result.platform
} ,
}
}
I am having two api requests.. After first api request success i want to update state value then pass that updated state to another api request..
I tried to get the updted props
how to dispatch the initiateTrans with update props
I need to update value at api request success in call back i need to call one more request with updated state value
currently i am not able to get the update props value
I think this is a good use case for thunk (redux-thunk), which is a middleware that allows you to execute multiple dispatches in an action.
You will need to apply the middleware when you configure the initial store (see docs on link above). But then in your actions, you can wrap the code with a dispatch return statement, which gives you access to multiple calls. For example:
export const guestLogin= (state)=>{
return dispatch => {
var data={...} // some data in here
return dataservice.guestSignup(data).then(res => {
dispatch(afterLoggedGuest(res))
}).catch(error => {
throw(error);
// could dispatch here as well...
});
}
}

How to chain two GraphQL queries in sequence using Apollo Client

I am using Apollo Client for the frontend and Graphcool for the backend. There are two queries firstQuery and secondQuery that I want them to be called in sequence when the page opens. Here is the sample code (the definition of TestPage component is not listed here):
export default compose(
graphql(firstQuery, {
name: 'firstQuery'
}),
graphql(secondQuery, {
name: 'secondQuery' ,
options: (ownProps) => ({
variables: {
var1: *getValueFromFirstQuery*
}
})
})
)(withRouter(TestPage))
I need to get var1 in secondQuery from the result of firstQuery. How can I do that with Apollo Client and compose? Or is there any other way to do it? Thanks in advance.
The props added by your firstQuery component will be available to the component below (inside) it, so you can do something like:
export default compose(
graphql(firstQuery, {
name: 'firstQuery'
}),
graphql(secondQuery, {
name: 'secondQuery',
skip: ({ firstQuery }) => !firstQuery.data,
options: ({firstQuery}) => ({
variables: {
var1: firstQuery.data.someQuery.someValue
}
})
})
)(withRouter(TestPage))
Notice that we use skip to skip the second query unless we actually have data from the first query to work with.
Using the Query Component
If you're using the Query component, you can also utilize the skip property, although you also have the option to return something else (like null or a loading indicator) inside the first render props function:
<Query query={firstQuery}>
{({ data: { someQuery: { someValue } = {} } = {} }) => (
<Query
query={secondQuery}
variables={{var1: someValue}}
skip={someValue === undefined}
>
{({ data: secondQueryData }) => (
// your component here
)}
</Query>
Using the useQuery Hook
You can also use skip with the useQuery hook:
const { data: { someQuery: { someValue } = {} } = {} } = useQuery(firstQuery)
const variables = { var1: someValue }
const skip = someValue === undefined
const { data: secondQueryData } = useQuery(secondQuery, { variables, skip })
Mutations
Unlike queries, mutations involve specifically calling a function in order to trigger the request. This function returns a Promise that will resolve with the results of the mutation. That means, when working with mutations, you can simply chain the resulting Promises:
const [doA] = useMutation(MUTATION_A)
const [doB] = useMutation(MUTATION_B)
// elsewhere
const { data: { someValue } } = await doA()
const { data: { someResult } } = await doB({ variables: { someValue } })
For anyone using react apollo hooks the same approach works.
You can use two useQuery hooks and pass in the result of the first query into the skip option of the second,
example code:
const AlertToolbar = ({ alertUid }: AlertToolbarProps) => {
const authenticationToken = useSelectAuthenticationToken()
const { data: data1 } = useQuery<DataResponse>(query, {
skip: !authenticationToken,
variables: {
alertUid,
},
context: makeContext(authenticationToken),
})
const { data: data2, error: error2 } = useQuery<DataResponse2>(query2, {
skip:
!authenticationToken ||
!data1 ||
!data1.alertOverview ||
!data1.alertOverview.deviceId,
variables: {
deviceId:
data1 && data1.alertOverview ? data1.alertOverview.deviceId : null,
},
context: makeContext(authenticationToken),
})
if (error2 || !data2 || !data2.deviceById || !data2.deviceById.id) {
return null
}
const { deviceById: device } = data2
return (
<Toolbar>
...
// do some stuff here with data12

Apollo GraphQL server: filter (or sort) by a field that is resolved separately

I might be facing a design limitation of Apollo GraphQL server and I'd like to ask if there is a workaround.
My schema contains type Thing, that has field flag. I'd like to be able to filter things by the value of flag, but there is appears to be impossible if this field is resolved separately. The same problem would arise if I wanted to sort things. Here’s an example:
type Thing {
id: String!
flag Boolean!
}
type Query {
things(onlyWhereFlagIsTrue: Boolean): [Thing!]!
}
const resolvers = {
Thing: {
flag: async ({id}) => {
const value = await getFlagForThing(id);
return value;
}
},
Query: {
async things(obj, {onlyWhereFlagIsTrue = false}) {
let result = await getThingsWithoutFlags();
if (onlyWhereFlagIsTrue) {
// ↓ this does not work, because flag is still undefined
result = _.filter(result, ['flag', true]);
}
return result;
}
}
}
Is there any way of filtering things after all the async fields are resolved? I know I can call getFlagForThing(id) inside things resolver, but won't that be just repeating myself? The logic behind resolving flag can be a bit more complex than just calling one function.
UPD: This is the best solution I could find so far. Pretty ugly and hard to scale to other fields:
const resolvers = {
Thing: {
flag: async ({id, flag}) => {
// need to check if flag has already been resolved
// to avoid calling getThingsWithoutFlags() twice
if (!_.isUndefined(flag)) {
return flag;
}
const value = await getFlagForThing(id);
return value;
}
},
Query: {
async things(obj, {onlyWhereFlagIsTrue = false}) {
let result = await getThingsWithoutFlags();
if (onlyWhereFlagIsTrue) {
// asynchroniously resolving flags when needed
const promises = _.map(result, ({id}) =>
getFlagForThing(id)
);
const flags = await Promise.all(promises);
for (let i = 0; i < flags.length; i += 1) {
result[i].flag = flags[i];
}
// ↓ this line works now
result = _.filter(result, ['flag', true]);
}
return result;
}
},
};
I think that the issue here is not really a limitation of Apollo server, and more to do with the fact that you have a primitive field with a resolver. Generally, it's best to use resolvers for fields only when that field is going to return a separate type:
Thing {
id: ID!
flag: Boolean!
otherThings: OtherThing
}
Query {
things(onlyWhereFlag: Boolean): [Thing!]!
}
In this example, it would be fine to have a separate resolver for otherThings, but if a field is a primitive, then I would just resolve that field along with Thing.
Using your original schema:
const filterByKeyValuePair = ([key, value]) => obj => obj[key] === value;
const resolvers = {
Query: {
async things(parent, { onlyWhereFlag }) {
const things = await Promise.all(
(await getThings()).map(
thing =>
new Promise(async resolve =>
resolve({
...thing,
flag: await getFlagForThing(thing)
})
)
)
);
if (onlyWhereFlag) {
return things.filter(filterByKeyValuePair(['flag', true]));
} else {
return things;
}
}
}
};
What if flag wasn't a primitive? Well, if you want to filter by it, then you would have a couple of different options. These options really depend on how you are fetching the "flag" data. I'd be happy to elaborate if you can provide more details about your schema and data models.

InfiniteLoader and react-redux

Component InfiniteLoader from react-virtualised requires function passed as property loadMoreRows to have signature like { startIndex: number, stopIndex: number }): Promise.
I'm using redux in my project, so loadMoreRows is a redux action creator like this:
const fetchEntities(start, stop) {
return fetch(`${myUrl}&start=${start}?stop=${stop}`)
}
const loadMoreRows = ({ startIndex, stopIndex }) => {
return (dispatch, getState) => {
return function(dispatch) {
return fetchEntities(startIndex, stopIndex).then(
items => dispatch(simpleAction(items)),
error => console.log(error)
)
}
}
}
after that, this action is connected to react component containing InfiniteLoader using connect function from react-redux.
So I'm not sure, how can I satisfy signature requirement, as redux action creators don't return any value/
eyeinthebrick is correct. A Promise is not a required return value.
When you "connect" a Redux action-creator, invoking it (dispatching it) actually returns a Promise. So for example I think you could do something more like this...
function fetchEntities (start, stop) {
return fetch(`${myUrl}&start=${start}?stop=${stop}`)
}
const loadMoreRows = ({ startIndex, stopIndex }) => {
return async (dispatch, getState) => {
try {
const items = await fetchEntities(startIndex, stopIndex)
await dispatch(simpleAction(items))
} catch (error) {
console.log(error)
}
}
}
At which point InfiniteLoader can just await the returned Redux promise.

Resources