I am composing 2 queries in a hoc with react-apollo. Here is my code:
let queries = compose(
graphql(gql`
query getUnitSubcategories($input: UnitSubcategorySearchInput) {
allUnitSubcategories(input:$input) {
edges {
node {
unitSubcategoryId
}
}
}
}
`, {
name: 'subcategories',
options: {
variables: {
input: {
activeFlag: true,
}
}
}
}),
graphql(gql`
query getFinancialTransactions($input: FinancialTransactionLineSearchInput) {
allFinancialTransactionLines(input: $input) {
pageInfo {
total
}
edges {
node {
financialTransaction {
financialTransactionId
}
}
}
}
}
`, {
name: 'financialTransactions',
options: {
variables: {
input: {
unitSubcategories: [
....
]
}
}
}
})
);
You can see that I am getting the unitSubcategories in the first query and they need to be passed to the second query. So I don't want to kick off the second query until I have a result from my first query. What is the best way to do this?
You'll want to use the skip config. See https://www.apollographql.com/docs/react/basics/queries.html#graphql-skip
In your example, you might want to define the skip config in the second query something like this:
{
name: 'financialTransactions',
skip: ({ unitSubcategories }) => !unitSubcategories,
options: ({ unitSubcategories }) => ({
variables: {
input: {
unitSubcategories
}
}
})
}
where you only want the second query to run after you've received the result from the first query. And then use options as a function so you can compute them from props.
Related
I was struggling with a test issue for my custom useLazyQuery hook. My first test is passing but the second one is failing. What am doing wrong for the second test?
Here is the useLazyFetchCoin.tsx
export const useLazyFetchCoin = () => {
const [coins, setCoins] = useState<ICoin[]>([]);
useEffect(() => {
const coins = localStorage.getItem('coinsInfo');
if (coins) {
setCoins(JSON.parse(coins));
}
}, []);
const [getData, { loading, error }] = useLazyQuery(GET_COIN_PRICE_QUERY, {
fetchPolicy: 'network-only',
notifyOnNetworkStatusChange: true,
onCompleted: (data) => {
const hasSameCoin = coins.some((f) => f.id === data.markets[0]?.id);
if (data.markets.length && !hasSameCoin) {
const allCoins = [...coins, data.markets[0]];
setCoins(allCoins);
localStorage.setItem('coinsInfo', JSON.stringify(allCoins));
} else if (data.markets.length <= 0) {
alertNotification('Coin not found !', Notification.ERROR);
}
if (hasSameCoin) {
alertNotification('This coin already exists on your list !', Notification.WARNING);
}
}
});
return { coins, setCoins, getData, loading, error };
};
Here is the test file
describe('useLazyFetchCoin custom hook', () => {
const QueryMock = [
{
request: {
query: GET_COIN_PRICE_QUERY,
variables: { code: 'BNB' }
},
result: {
data: {
markets: [
{
id: 'binance_bnb_eur',
baseSymbol: 'BNB',
ticker: {
lastPrice: '414.90000000'
}
}
]
}
}
}
];
const QueryWrongCodeMock = [
{
request: {
query: GET_COIN_PRICE_QUERY,
variables: { code: 'asd' }
},
result: {
data: {
markets: []
}
}
}
];
function getHookWrapper(mocks: any, code: string) {
const wrapper = ({ children }: any) => (
<MockedProvider mocks={mocks} addTypename={false}>
{children}
</MockedProvider>
);
const { result, waitForNextUpdate } = renderHook(() => useLazyFetchCoin(), {
wrapper
});
expect(typeof result.current.coins).toBe('object');
expect(result.current.loading).toBeFalsy();
expect(result.current.error).toBeUndefined();
// call the lazy function
act(() => {
result.current.getData({
variables: { code }
});
});
return { result, waitForNextUpdate };
}
it('should return an array of coins', async () => {
// Working correctly
const { result, waitForNextUpdate } = getHookWrapper(QueryMock, 'BNB');
await waitForNextUpdate();
expect(result.current.loading).toBeFalsy();
expect(result.current.coins[0]).toEqual({
id: 'binance_bnb_eur',
baseSymbol: 'BNB',
ticker: {
lastPrice: '414.90000000'
}
});
});
it('should return an empty array when requesting a wrong code', async () => {
// Not working
const { result, waitForNextUpdate } = getHookWrapper(QueryWrongCodeMock, 'asd');
await waitForNextUpdate();
expect(result.current.loading).toBeFalsy();
expect(result.current.coins[0]).toEqual([]);
});
});
I got this error message for the second test.
Expected: []
Received: {"baseSymbol": "BNB", "id": "binance_bnb_eur", "ticker": {"lastPrice": "414.90000000"}}
I don't get it because I'm using different queries for each test.
Also, the second test should receive an empty array when you pass a wrong code such as 'asd'.
How can write a proper test for it?
I fixed the problem. When I was changing the order test, It worked correctly.
I added a clear mock function for it.
clear mock
The Apollo GraphQL team says that readQuery and writeQuery are good for 95% of the use cases. I am using useMutation and update and want to remove an item from a cache without having to call refetchQueries. My code is as follows:
const [deleteSpeaker] = useMutation(DELETE_SPEAKER, {
update(cache, { data: {deleteSpeaker}}) {
const { speakers} = cache.readQuery({query: GET_SPEAKERS});
cache.writeQuery({
query: GET_SPEAKERS,
data: { speakers: speakers.filter(speaker => speaker.id !== deleteSpeaker.id) }
});
},
});
What gets returned from readQuery leads me to think I should be filtering for speakers.datalist but when I do that, the cache does not update.
What is the correct way to update cache to reflect a removed record from the GET_SPEAKERS query.
export const DELETE_SPEAKER = gql`
mutation DeleteSpeaker($speakerId: Int!) {
deleteSpeaker(speakerId: $speakerId) {
id
first
last
favorite
}
}
`;
and GET_SPEAKERS
export const GET_SPEAKERS = gql`
query {
speakers {
datalist {
id
first
last
favorite
company
}
}
}
`;
reading apollo docs, this should be something lke:
const [deleteSpeaker] = useMutation(DELETE_SPEAKER, {
update(cache, { data: {deleteSpeaker}}) {
cache.modify({
id: cache.identify(deleteSpeaker.id),
fields: {
comments(existingSpeakerRefs, { readField }) {
return existingSpeakerRefs.filter(
speaker => deleteSpeaker.id !== readField('id', speakerRef)
);
},
},
});
},
});
how can i work with resolvers for mutations after i create type Mutations in graphql-yoga?
i've tried to create resolvers for mutations, but when i run in graph playground, i the code return error.
and here's my code:
const { GraphQLServer } = require('graphql-yoga')
// 1
const typeDefs = `
type Query {
users: [User!]!
user(id: ID!): User
}
type Mutation {
createUser(name: String!): User!
}
type User {
id: ID!
name: String!
}
`
// 2
const resolvers = {
Query: {
users: () => User,
},
Mutation: {
// WHAT SHOULD I WRITE IN HERE?
}
}
// 3
const server = new GraphQLServer({
typeDefs,
resolvers,
})
server.start(() => console.log(`Server is running on http://localhost:4000`))
if someone know how can i do for resolvers mutation, can shared with me?
thanks
Resolver for createUser can be defined as follows:
const resolvers = {
Query: {
// Query resolvers
},
Mutation: {
createUser: (parent, args) => {
// Business logic. Maybe save record in database
// Return created user. I am returning dummy data for now, so that you can test it in playground
return {id: 1, name: "John}
}
}
}
Finally it works for me.
i used this:
const resolvers = {
Query: {
users: () => User
},
Mutation: {
createUser: (source, {input}) => {
let newUser = [];
newUser.id = id;
newUser.name = input.name;
User.push(newUser);
return newUser;
}
}
}
I'm using Ember API with a JSON API backend. The API accepts filters like this:
/users?filter[simple][name]=John
Right now, whenever I want to make a query, I'm doing this:
this.store.query('users', {
filter: {
simple: {
name: 'John'
}
}
});
It would be nice to avoid this verbosity by adding a helper function that works like this:
this.store.simpleQuery('users', { name: 'John' });
That function would pass its arguments directly to query(), just wrapping the query in { filter: { simple: ... } }.
Is this possible? How do I do this?
Well what is stopping you from creating your own method in the adapter to do exactly that?
// ... your adapter code
simpleQuery: function(modelName, query) {
return this.store.query('users', {
filter: {
simple: {
name: 'John'
}
}
});
}
// ...
You need to extend the default store. Add the following to app/services/store.js
import DS from 'ember-data';
export default DS.Store.extend({
simpleQuery(modelName, query) {
return this.query(modelName, {
filter: { simple: query }
});
},
simpleQueryRecord(modelName, query) {
return this.queryRecord(modelName, {
filter: { simple: query }
});
}
});
And you'll be able to do this:
this.store.simpleQuery('users', { name: 'John' });
this.store.simpleQueryRecord('users', { email: 'john#example.com' });
I'm filtering a list of stocks by symbol, but it doesn't work. Maybe there's something wrong with the code? Something I have missed? Here are some of things I've tried:
function filterBySymbol: function(select, value) {
var ordersStore = this.getBrokerageOrderHistoryList().getStore();
ordersStore.clearFilter();
if (value !== '') {
ordersStore.data.filterBy(function (record, id) {
// log to make certain this gets called (and it is)
console.log(id, record.get('symbol') === value);
return record.get('symbol') === value;
});
// Other things I've tried (nothing worked):
// 1)
// var f = new Ext.util.Filter({
// filterFn: function(record) {
// return record.get('symbol') === value;
// }
// });
// ordersStore.filterBy(f);
// 2)
// ordersStore.filter(function (record) {
// return record.get('symbol') === value;
// });
// 3)
// this.getBrokerageOrderHistoryList().setStore(ordersStore);
// this.getBrokerageOrderHistoryList().refresh();
}
}
It turns out we had to disable remote filtering on the store, which is supposed to default to false, but it wasn't:
this.getOrderList().getStore().setRemoteFilter(false);
one of these should work
// 1
ordersStore.filter("symbol", value);
// 2
ordersStore.filter([
{ filterFn: function(item) { return item.get("symbol") === value; }}
]);
// 3
ordersStore.filterBy(function(item) {
return item.get("symbol") === value;
}
UPDATE: sample that works:
Ext.define('ST.store.Products', {
extend: 'Ext.data.Store',
config: {
fields: ["title", "category" ],
storeId: "Products",
data: [
{ title: 'Text1', category: 'cat1'},
{ title: 'Text2', category: 'cat2'},
{ title: 'Text3', category: 'cat3'},
]
}
});
console.log("before");
Ext.getStore("Products").each(function(item){
console.log(item.data.title);
});
Ext.getStore("Products").filterBy(function(item){
return item.get('title') == 'Text1';
});
console.log("after");
var store = Ext.getStore("Products").each(function(item){
console.log(item.data.title);
});
in my case I see the following in the developer console
before
Text1
Text2
Text3
after
Text1