I have a sample app called GraphQL Bookstore that creates books, publishers and authors and shows relationships between them. I am using subscriptions to show updates in real time.
For some reason my BOOK_ADDED subscription is bypassing the graphql wrapper completely. It is calling the wrapped class with the books prop set to undefined. Relevant parts of the code are shown below (you can see the full code here).
class BooksContainerBase extends React.Component {
componentWillMount() {
const { subscribeToMore } = this.props;
subscribeToMore({
document: BOOK_ADDED,
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) {
return prev;
}
const newBook = subscriptionData.data.bookAdded;
// Don't double add the book
if (!prev.books.find(book => book.id === newBook.id)) {
return Object.assign({}, prev, {
books: [...prev.books, newBook]
});
} else {
return prev;
}
}
});
}
render() {
const { books } = this.props;
return <BooksView books={books} />;
}
}
...
export const BooksContainer = graphql(BOOKS_QUERY, {
props: ({ data: { loading, error, subscribeToMore, books } }) => ({
loading,
error,
subscribeToMore,
books
})
})(LoadingStateViewer(BooksContainerBase));
Basically when a subscription notification is received by the client, the updateQuery() function is called - as expected. However, as soon as that function exits, the render() method of the wrapped class is called directly with books set to undefined. I expected that the graphql wrapper would be called, setting the props correctly before calling the render() method. What am I missing?
Thanks in advance!
Related
EventEmitter in Nestjs is wrapper around EventEmitter2 module. I whant that Server-Sent Events return Observable with EE.
import { Controller, Post, Body, Sse } from '#nestjs/common';
import { fromEvent } from 'rxjs';
import { EventEmitter2 } from '#nestjs/event-emitter';
import { OrdersService } from './orders.service';
import { CreateOrderDto } from './dto/create-order.dto';
#Controller('orders')
export class OrdersController {
constructor(private ordersService: OrdersService,
private eventEmitter2: EventEmitter2) {}
#Post()
createOrder(#Body() createOrderDto: CreateOrderDto) {
// save `Order` in Mongo
const newOrder = this.ordersService.save(createOrderDto);
// emit event with new order
this.eventEmitter2.emit('order.created', newOrder);
return newOrder;
}
#Sse('newOrders')
listenToTheNewOrders() {
// return Observable from EventEmitter2
return fromEvent(this.eventEmitter2, 'order.created');
}
}
But after subscribtion to this source from browser i've getting only errors
this.eventSource = new EventSource('http://localhost:3000/api/v1/orders/newOrders');
this.eventSource.addEventListener('open', (o) => {
console.log("The connection has been established.");
});
this.eventSource.addEventListener('error', (e) => {
console.log("Some erorro has happened");
console.log(e);
});
this.eventSource.addEventListener('message', (m) => {
const newOder = JSON.parse(m.data);
console.log(newOder);
});
It's quite likely that you forgot to format the event in the right way.
For SSE to work internally, each chunk needs to be a string of such format: data: <your_message>\n\n - whitespaces do matter here. See MDN reference.
With Nest.js, you don't need to create such message manually - you just need to return a JSON in the right structure.
So in your example:
#Sse('newOrders')
listenToTheNewOrders() {
// return Observable from EventEmitter2
return fromEvent(this.eventEmitter2, 'order.created');
}
would have to be adjusted to, for example:
#Sse('newOrders')
listenToTheNewOrders() {
// return Observable from EventEmitter2
return fromEvent(this.eventEmitter2, 'order.created')
.pipe(map((_) => ({ data: { newOrder } })));
}
the structure { data: { newOrder } } is key here. This will be later translated by Nest.js to earlier mentioned data: ${newOrder}\n\n
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.
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
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
Why isn't updateQueries getting called for me? I think I'm doing exactly what the docs say and have compared my code to other similar questions. What am I missing?
Package versions:
apollo-angular#0.13.0
apollo-client#1.0.4
My code:
//same es6 module, above my class definition
const RecentSearchesQuery = gql`
query recentSearch {
recentSearch {
id
query
lastUpdated
userId
}
}
`;
const RecentSearchesMutation = gql`
mutation recentSearchSave($query:String!) {
recentSearchSave(query: $query)
}
`;
...
//my class method
updateRecentSearches(query: string) {
const updateQueries: MutationQueryReducersMap = {
RecentSearch: (prev: Object, {mutationResult}) => {
//execution doesn't get here
debugger;
alert('updateQueries handler in RecentSearchesMutation mutation for recentSearch');
...
}
};
return new Promise((resolve, reject) => {
this.apollo.mutate<any>({
mutation: RecentSearchesMutation,
variables: {query},
updateQueries
})
.subscribe({
next: ({data}) => {
resolve();
},
error: (error: ApolloError) => {
reject(error);
}
});
});
I never did get updateQueries working but the recommended way is now to use update (also a mutation option). Using update worked for me and this article was a very helpful:
https://dev-blog.apollodata.com/apollo-clients-new-imperative-store-api-6cb69318a1e3