Adding Apollo client mutation in react bootstrap datatable - graphql

I am able to get the Apollo query component in my application. But I'm having a hard time adding the mutation. I have the mutation query written out. I want to use the row and cellvalue from the cellEditProp hook function and pass them into the mutation component. I am having a hard time figuring out where to nest or wrap the mutation component. Any tips are much appreciated.
function onSaveCell(row, cellName, cellValue) {
//Need to use this data for the mutation
}
function onBeforeSaveCell(row, cellName, cellValue) {
console.log(cellName, cellValue, row
);
return true;
}
const cellEditProp = {
mode: 'click',
blurToSave: true,
beforeSaveCell: onBeforeSaveCell, // a hook for before saving cell
afterSaveCell: onSaveCell // a hook for after saving cell
};
const APPROVALCHAIN_QUERY = gql`
{
vApprovalChainApproverCountList{
applicationId
applicationName
collectionName
licenseType
cost
approvers
}
}
`;
const ADDCOST_MUTATION = gql`
mutation updateCostlicense($costLicense: ApplicaitonCostInput!){
updateCostLicense(costLicense: $costLicense){
applicationId
cost
licenseType
}
}
`;
class ApprovalRecord2 extends Component {
render() {
return (
<Query
query={APPROVALCHAIN_QUERY}
>
{({ loading, error, data }) => {
if (loading)
return <p>Loading...</p>;
if (error)
return <p>{error.message}</p>;
const chain = JSON.parse(JSON.stringify(data.vApprovalChainApproverCountList));
console.log(chain);
return (
<div>
<h1>ApprovalRecord2</h1>
<p>Add/Remove Approvers</p>
<BootstrapTable data={chain} striped hover pagination search options={options} cellEdit={cellEditProp} version='4'>
<TableHeaderColumn isKey dataField='applicationId' dataSort={true}>ID</TableHeaderColumn>
<TableHeaderColumn dataField='applicationName' dataSort={true}>Application</TableHeaderColumn>
<TableHeaderColumn dataField='collectionName' dataSort={true}>Collection</TableHeaderColumn>
<TableHeaderColumn dataField='licenseType' dataSort={true}>License</TableHeaderColumn>
<TableHeaderColumn dataField='cost' dataSort={true}>Cost</TableHeaderColumn>
<TableHeaderColumn dataField='approvers' dataSort={true}>Approvers</TableHeaderColumn>
</BootstrapTable>
)}
</div>
);
}}
</Query>
);
}
}
export default ApprovalRecord2;

You can pass/inject (multiple, named) mutations (and queries) as props to component with graphql()

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.

Async validation with useLazyQuery hook

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.

Apollo useQuery hook on component mount

I'm new to hooks and trying to use them more
How can I get data (with Apollo) when a component mount ?
I'm trying to use useQuery inside a useEffect, my code so far looks like this
const MyComponent = () => {
const getME = () => {
const { loading, error, data } = useQuery(ME);
setMe(data.me) // useState hook
console.log('query me: ', me);
};
useEffect(getME);
return (<>
...
</>)
}
but this gives me an error
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
edit: this is the query
import { gql } from '#apollo/client';
export const ME = gql`
query me {
profile {
firstName
lastName
}
}
`;
Here is an example on how you should use the useQuery hook and then stock the data in the state
const { loading, data, error } = useQuery(SOME_QUERY)
const [state, setState] = React.useState([])
useEffect(() => {
// do some checking here to ensure data exist
if (data) {
// mutate data if you need to
setState(data)
}
}, [data])`enter code here`
from https://github.com/trojanowski/react-apollo-hooks/issues/158

Loading text shown on fetching pagination requests

I have a react component using react-apollo fetching a list. The server is using relay style connection.
My question is when I fetch for next page, it shows the "Loading..." and then the page cursor moves back to the top, shown in the following.
https://imgur.com/a/ImfQPVJ
I want to have a better UX that no "Loading..." text shown, and after fetching, just append the newly fetched result to the back of the list.
This is the code (remove non-relevant code):
class Links extends Component {
fetchNextPage = (pageInfo, fetchMore) => ev => {
const { linksOrder } = this.props;
const after = pageInfo.endCursor;
const queryVars = getQueryVarsFromParam(linksOrder, after);
fetchMore({
variables: queryVars,
updateQuery: (previousResult, { fetchMoreResult }) => {
const { allLinks: { nodes: newLinks, pageInfo: newPageInfo }} = fetchMoreResult;
// Handle no new result
if (newLinks.length === 0) return previousResult;
// With new result, we append to the previous result list
let finalResult = previousResult;
finalResult.allLinks.pageInfo = newPageInfo;
finalResult.allLinks.nodes = finalResult.allLinks.nodes.concat(newLinks);
return finalResult;
}
})
}
render() {
const { linksOrder, classes } = this.props;
const { linkGqlCursorAfter: after } = this.state;
const queryVars = getQueryVarsFromParam(linksOrder, after);
return(<Query query={LINKS_QUERY_GQL} variables={queryVars}>
{({ loading, error, data, fetchMore }) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
const { allLinks: { nodes: links, pageInfo }} = data;
return(
<React.Fragment>
<Grid container spacing={8} className={ classes.linksList } >
{ links.map((link, ind) => (
<Grid item key={link.id}><Link ind={ind} link={link}/></Grid>
)) }
{ pageInfo.hasNextPage ? (
<Grid item key={ "fetchNextPage" } style={{ alignSelf: "center" }}>
<Fab onClick={ this.fetchNextPage(pageInfo, fetchMore) }
size="small" color="secondary"><AddIcon /></Fab>
</Grid>)
: null }
</Grid>
</React.Fragment>
)
} }
</Query>)
}
}
How could I achieve that? Another approach I could think of is not to use <Query> tag at all, and retrieve the Apollo client itself via HOC in onClick hander, fetch the result, and add the result back to the links object by updating the component state.
Then, this begs the question of why we want to use <Query> <Mutation>, when we can always get the Apollo client and handle the query interaction better ourselves?
Since you already have the data you fetched from previous pages in your cache you can render that conditionally in your if (loading) while waiting for new data to be fetched.
if(loading) {
return data.allLinks ?
<>
<LinksComponent {/*pass in the old data here...*/}/>
<Spinner/>
</> : <Spinner/>
}
If you already have data.allLinks you will display that data in a LinksComponent even when new data is being fetched. The Spinner component will be displayed under the LinksComponent while Loading is true. If you don't have any data fetched, you will just display the Spinner component.

Relay Modern node does not contain fragment properties

I have the following setup in my React Project:
export default class OverviewScreen extends React.Component<any, any> {
public render() {
return (
<QueryRenderer
environment={environment}
query={OverviewScreenQuery}
render={this.queryRender}/>
);
}
protected queryRender({error, props}): JSX.Element {
if (error) {
return <div>{error.message}</div>;
} else if (props) {
return (
<div>
<div>
<ActivityOfferList viewer={props.viewer} title="Titel"/>
<ActivityTypeListsFragment viewer={props.viewer}/>
</div>
</div>
);
}
return <div>Loading...</div>;
}
}
const OverviewScreenQuery = graphql`
query OverviewScreenQuery {
viewer {
...HorizontalOfferList_viewer
...ActivityTypeLists_viewer
}
}`;
class ActivityTypeLists extends React.Component<IHorizontalOfferListProps, any> {
public render() {
return (
<div>
{this.props.viewer.allActivityTypes.edges.map((typeEdge) => {
let typeNode = typeEdge.node;
return this.getCardListForActivityType(typeNode);
})}
</div>
);
}
private getCardListForActivityType(typeNode: any) {
console.log(typeNode);
return (
<CardList key={typeNode.__id} title={typeNode.title}>
{typeNode.activities.edges.map(({node}) => {
return (
<RelayPicturedTypeActivityCard key={node.__id} offer={node} activityType={typeNode}/>
);
})}
</CardList>
);
}
}
export const ActivityTypeListsFragment = createFragmentContainer(ActivityTypeLists, graphql`
fragment ActivityTypeLists_viewer on Viewer {
allActivityTypes(first: 5) {
edges {
node {
...PicturedTypeActivityCard_offer
}
}
}
}
`);
export class PicturedTypeActivityCard extends React.Component<any, any> {
public render() {
return (
<PicturedCard title={this.props.offer.title} subtitle={this.props.activityType.title} width={3}/>
);
}
}
export const RelayPicturedTypeActivityCard = createFragmentContainer(PicturedTypeActivityCard, graphql`
fragment PicturedTypeActivityCard_offer on ActivityType {
title
activities(first: 4) {
edges {
node {
id
title
}
}
}
}
`);
Which should work and give me the correct result from the graphcool relay endpoint.
The Network call to the relay endpoint is indeed correct and I receive all the ActivityTypes and their activities and titles from my endpoint.
But somehow in the function getCardListForActivityType() the typeNode only contains the __id of the node as data and no title at all:
If I insert title and activities directly instead of using
...PicturedTypeActivityCard_offer
then the data also gets passed down correctly. So something with the Fragment must be off.
Why is it that the network call is complete and uses the fragment correctly to fetch the data, but the node object never gets the fetched data?
This is indeed correct behavior.
Your components must, individually, specify all their own data dependencies, Relay will only pass to the component the data it asked for. Since your component is not asking any data, it's receiving an empty object.
That __id you see is used internally by Relay and you should not rely on it (that is why it has the __ prefix).
Basically, the prop viewer on ActivityTypeLists component will have exactly the same format than the query requested on the ActivityTypeLists_viewer fragment, without any other fragments from other components that you are referencing there.
This is known as data masking, see more in the following links:
https://facebook.github.io/relay/docs/en/thinking-in-relay.html#data-masking
https://facebook.github.io/relay/docs/en/graphql-in-relay.html#relaymask-boolean

Resources