How to use variables in a GraphQL query? - reactjs

So I have to make two queries. First will return an array with multiple objects and I want to get the ID of the first object to use it in my second query.
The problem is that I can't use b_id: props.getBusiness.business[0]._id
Any idea how can I work with this?
const GET_USER_BUSINESS = gql`
query getUserBusiness {
getUserBusiness {
_id
}
}
`;
const GET_BUSINESS_JOBS = gql`
query getBusinessJobs($b_id: ID!) {
getBusinessJobs(b_id: $b_id ) {
_id
name
}
}
`;
export default compose(
withApollo,
graphql(GET_USER_BUSINESS,
{
name: "business"
}),
graphql(GET_BUSINESS_JOBS,
{
name: "jobs",
options: (props) => (
{
variables:
{
b_id: props.getUserBusiness.business[0].b_id
}
}
)
})
)(ProposalContainer);

Couple of things. One, by default, the graphql HOC passes a prop called data down to the wrapped component with the query data. However, because you specify a name in the HOC's config, the prop will actually be called whatever name you pass it. In other words, you can access the _id this way:
props.business.getUserBusiness[0]._id
...if getUserBusiness returns an array. Otherwise:
props.business.getUserBusiness._id
Secondly, props.business will be undefined until the query completes. We probably don't want to send the GET_BUSINESS_JOBS query until we have an _id to work with. So we want to pass a skip function to the HOC's config:
export default compose(
withApollo,
graphql(GET_USER_BUSINESS,
{
name: "business"
}),
graphql(GET_BUSINESS_JOBS,
{
name: "jobs",
skip: (props) => !props.business || !props.business.getUserBusiness[0]
options: (props) => (
{
variables:
{
b_id: props.business.getUserBusiness[0]._id
}
}
)
})
)(ProposalContainer)

Related

Graphql urql refetch every n seconds

Im using Typescript, React- and graphql with the urql client library.
My Query looks something like this:
query objectId($id: Int!) {
object(id: $id) {
id
name
__typename
}
}
This is how I call the query:
const [{ data }] = useObjectIdQuery({ variables: { id }, pause: !id });
Question:
How can i refetch every n seconds without reloading the page?
My backend reads JSON files and they update consistently.
Now I have looked into the documentation here, and also on a bunch of Stackoverflow and no-name sites.
Thank you.
I found out that the documentation provides a function for that. I built myself a hook to use it in the whole project for any query. Just becareful with the query parameter, it has to be the already build graphql DocumentNode as parameter.
You can import them like this for each Query:
import { ObjecIdDocument } from "../../graphql";
The graphql path may be different in your case.
This is the full Hook:
import { useEffect } from "react";
import { useQuery } from "urql";
import { DocumentNode } from "graphql";
const useRefreshingQuery = (variables: object, query: DocumentNode, delayInSec: number, pause: boolean) => {
const [result, reexecuteQuery] = useQuery({
query: query,
variables: variables,
pause: pause,
});
useEffect(() => {
if (result.fetching) {
return;
}
const timerId = setTimeout(() => {
reexecuteQuery({ requestPolicy: "network-only" });
}, delayInSec * 1000);
return () => clearTimeout(timerId);
}, [result.fetching, reexecuteQuery, variables]);
return result;
};
export default useRefreshingQuery;
You can use the Hook like this:
import { ObjecIdDocument } from "../../graphql";
const result = useRefreshingQuery({ id: UID }, ObjectIdDocument, 10, !UID);

ReactJS : import GraphQL queries dynamically based on a State

I query a specific variable called nameTranslated from my schema, It takes the parameter of the locale with is En-CA, Fr-FA etc and gets the desired word in french. And the way I handle this in my frontend reactjs application is like this:
export const App = () => {
const { locale } = useIntl()
const LOAD_TABLE = gql`
query getItems($id: String!) {
Items(id: $id) {
id
notes
nameTranslate(language:"${l}")
defaultClass {
nameTranslate(language:"${l}")
}
}
}
`
useEffect(() => {
// a function to fetch LOAD_TABLE
},[locale])
}
The above code works perfectly fine and whenever I change the locale variable it re fetches the query. But the problem with this is i have many other query I need to work with, my file length becomes too long and hard to manage. At the same time if I pull the file out, I lose the privilage of dynamacally adding a type for nameTranslate.. How can I solve this issue?
You can make it more modular but still dynamic by using custom hooks, for example:
// hooks/useItemsQuery.js
function useItemsQuery(locale) {
const itemsQuery = useMemo(() => gql`
query getItems($id: String!) {
Items(id: $id) {
id
notes
nameTranslate(language:"${locale}")
defaultClass {
nameTranslate(language:"${locale}")
}
}
}
`, [locale])
return itemsQuery
}
// App.js
export const App = () => {
const { locale } = useIntl()
const itemsQuery = useItemsQuery(locale)
useEffect(() => {
// a function to fetch itemsQuery
}, [itemsQuery])
}
Or if you need to call outside of React just a normal function will do. I think this won't have a perf impact as es6 tpl literals are cached (the gql`` part) as long as the variables don't change, even inside a function. If that's the case the use of useMemo above is redundant anyway.
function itemsQuery(locale) {
return gql`
query getItems($id: String!) {
Items(id: $id) {
id
notes
nameTranslate(language:"${locale}")
defaultClass {
nameTranslate(language:"${locale}")
}
}
}
`
}

Apollo GraphQL Client (ReactJS) - Accessing props in graphql query

I have a component that shows all users tied to a specific type of entity. The component renders with the apollo graphql compose helper. The export of the component looks like this:
export const UsersContainer = compose(
connect(mapStateToProps, mapDispatchToProps),
graphql(gql`
query manager($id: Int!) {
manager(id: $id) {
users {
id
firstName
lastName
email
username
}
}
}`, {
options: (props) => ({
variables: {
id: props.currentOrg.org.id
}
}),
})
)(Users);
This all works fine. The issue I'm facing is that I want to make this component dynamic so it will work with all entity types (ie. manager, client, vendor). So, in the above query: query manager($id: Int!) would change to: query client($id: Int!), and so forth.
How can I access the redux store to pull in data to dynamically build the gql query? The data is all available in the store. I just need a way to access the props in a way that I can dynamically build the gql query.
You effectively need to define three different queries and then conditionally switch between them. The problem is the graphql HOC doesn't provide an easy way to derive which query to use from props. This is probably easiest to do with the new Query component, which uses the render props pattern:
const QueryA = gql`query { # blah }`
const QueryB = gql`query { # blah }`
const WrapperComponent = props => {
const query = props.foo ? QueryA: QueryB
return (
<Query query={query}>
{({loading, error, data}) => (
<YourComponent/>
)}
</Query>
)
}
const mapStateToProps = ({ foo }) => ({ foo })
export default connect(mapStateToProps)(WrapperComponent)
Using the HOC, you could do something like:
const QueryA = gql`query { # blah }`
const QueryB = gql`query { # blah }`
const QueryAComponent = graphql(QueryA)(YourComponent)
const QueryBComponent = graphql(QueryB)(YourComponent)
const WrapperComponent = props => (
props.foo
? QueryAComponent
: QueryBComponent
)
const mapStateToProps = ({ foo }) => ({ foo })
export default connect(mapStateToProps)(WrapperComponent)
If you're going the HOC route, you could also use something like recompose's branch to conditionally render the components.

Passing ID as correct variable type in GraphQL Mutate function

I'm trying to edit an item using GraphQL by passing in arguments using this.props.mutate. I'm getting following error... Error: GraphQL error: Variable $id of required type ID! was not provided. Hence, the problem lies in me passing the wrong ID type to the mutate function. Anyone know how to pass the ID type as the correct type to the mutate function? or can I cast the ID type from string to the correct type to pass as variable to the mutate function? Thank you
i'm using local component state to hold the values to prefill a form in case you are wondering why I am using local state
import UPDATE_CHAT_MUTATIONS from '../graphql/mutations/updateChat';
class EditChat extends React.Component {
state = {
text: '',
id: ''
}
componentWillMount() {
this._onEditLoad()
}
_onEditLoad = () => {
const chat = this.props.navigation.state.params;
this.setState({ text: chat.text, id: chat._id })
}
_onChangeText = text => this.setState({ text });
_onEditPress = async () => {
const { id, text } = this.state;
await this.props.mutate({
variables: {
_id: id,
text
}
});
Keyboard.dismiss();
this.props.navigation.goBack(null);
}
i managed to get it to work! I made an error on the graphql mutations on the client side. Below is the code that works!! Hope this will help those who face the same issue. Cheers
import { gql } from 'react-apollo';
export default gql`
mutation updateChat($_id: ID!, $text: String!) {
updateChat(_id: $_id, text: $text) {
text
_id
updatedAt
}
}
`;

Look-ups in Redux Reducers

Let's say I have the following state:
state = {
products: {
50: {
sku: "000",
name: "A Product",
category: 123,
...
}
},
categories: {
123: {
name: "Some Category",
parentCategory: 100,
department: "Electronics"
}
},
filteredProducts: [50]
}
I want to be able to filter products based on categories. However, I need to filter based on multiple properties of categories. i.e. I might want to get all categories within the Electronics department or I might want to get a category with id 123 and all it's sub-categories.
This is a bit of a contrived example that closely matches what I'm trying to achieve but it's a bit easier to understand, so please bear with me. I'm aware that in this specific instance, I could probably use something like reselect, but assuming that I needed to do a category lookup for a products reducer, what would my options be?
You can use reselect as you mentioned, and make some selectors with parameter the re-use these selectors from categories in products to be as follow:
Make your category/selectors file as follow:
import { createSelector } from 'reselect';
const categoriesSelector = state => state.categories;
const selectCategoryById = id => {
return createSelector(
categoriesSelector,
categories => categories[id]
);
}
const selectCategoryByName = name => {
return createSelector(
categoriesSelector,
categories => categories.filter(c => c.name === name)
);
}
export default {
categoriesSelector,
selectCategoryById,
selectCategoryByName,
}
Meanwhile, in product/selector you can import both category and product selector files as follow:
import { createSelector } from 'reselect';
import { selectCategoryById } from './category/selectors';
const productsSelector = state => state.products;
const selectProductByCategoryId = id => {
return createSelector(
productsSelector,
selectCategoryById,
(products, categories) => products.filter(p.category.indexOf(id) > -1)
);
}
export default {
productsSelector,
selectProductByCategoryId,
}
And in product/reducer, you can import both selectors and return the new changed state based on category logic.

Resources