I am trying to do a wrapper for the graphQL queries, I tried this
const GQLWrapper = ({ query, children}) => (
<Query query={query}>
{({ loading, error, data }) => {
if (loading) {
return null
}
if (error) {
<QueryError />
}
const { gqlData } = data.page
return (
<div>
{children}
</div>
)
}}
</Query>
)
but i don't understand how to use render props to pass the data to the child component.
Also if it is a better solution to use HOC, please let me know (when should one be used or the other). Thank you
You just make your children a function and pass the data there
return <div>{children(gqlData)}</div>;
When you use your wrapper you do:
<GQLWrapper query={myquery}>
{(gqlData) => <SomeComponent data={gqlData} />}
</GQLWrapper>
Related
I can console.log() the data I want after mapping through my data from my GraphQL query. However, the nested .map functions are not rendering my JSX. Is it possible to render JSX in nested .maps?
const NikonGallery = ({ data }) => {
return (
<Layout>
{data.allFiles.nodes.map((item) => {
Object.entries(item).map(([key, value]) => {
value.map((image) => {
console.log("Individual image", image) // Logs show the data I want
return (
<>
<GatsbyImage
image={image.gatsbyImageData}
alt={image.description}
/>
</>
)
})
})
})}
</Layout>
)
}
export default NikonGallery
The data from GraphQL/Contentful is a nested array of objects. I'm having trouble getting the JSX to render when I call the nested objects via .map.
Regarding the nested maps, you'll need to make some changes to return a proper expression from each map. The reason the console log works is because the code still loops; however, no expression is returned from the map for React to render. Try this:
data.allFiles.nodes.map((item) => {
return Object.entries(item).map(([key, value]) => {
return value.map((image) => {
console.log("Individual image", image) // Logs show the data I want
return (
<>
<GatsbyImage
image={image.gatsbyImageData}
alt={image.description}
/>
</>
)
})
})
})}
</Layout>
)
When using the Gatsby image plugin for dynamic images such as this, you should use the getImage() method provided by the plugin. The import should look like this:
import { GatsbyImage, getImage } from "gatsby-plugin-image";
And the usage in your case would look something like this:
value.map((image) => {
const gatsbyImage = getImage(image);
return (
<>
<GatsbyImage
image={gatsbyImage}
alt={image.description}
/>
</>
)
})
I have component where I have array of data that is being looped using map and rendered a new component base one that and inside the looped component I have a useEffect that fetches the data from the api but it runs same api twice.
Here is the code
I am looping through array of rule_set_versions which is in this case size of 2
const ExpandedContent = ({ experiment }) => {
return experiment.rule_set_versions &&
experiment.rule_set_versions.map((ruleSetVersion) => <RuleSetVersionCollapse key={ruleSetVersion.id} ruleSetVersion={ruleSetVersion} />)
}
const ExperimentsCollapse = ({ experiment }) => {
return <React.Fragment>
<div className={styles.experiment_collapse_root}>
<Collapse>
<Collapse.Panel className={styles.experiment_item} extra={<ExtraTest experiment={experiment} />}>
<ExpandedContent experiment={experiment} />
</Collapse.Panel>
</Collapse>
</div>
</React.Fragment>
}
Here is my RuleSetVersionCollapse snippet
const ruleSet = useSelector(state => state.ruleSet)
React.useEffect(() => {
if (!ruleSet.id) {
dispatch(getRuleSetAction(ruleSetVersion.rule_set_id))
}
}, [dispatch])
And the useEffect runs twice even though the ruleSetVersion.rule_set_id is same on both the case.
Can anyone suggest any way I can solve this issue.
Thanks
The Mutation component in react-apollo exposes a handy loading boolean in the render prop function which is ideal for adding loaders to the UI whilst a request is being made. In the example below my Button component calls the createPlan function when clicked which initiates a GraphQL mutation. Whilst this is happening a spinner appears on the button courtesy of the loading prop.
<Mutation mutation={CREATE_PLAN}>
{(createPlan, { loading }) => (
<Button
onClick={() => createPlan({ variables: { input: {} } })}
loading={loading}
>
Save
</Button>
)}
</Mutation>
The issue I have is that other aspects of my UI also need to change based on this loading boolean. I have tried lifting the Mutation component up the React tree so that I can manually pass the loading prop down to any components which rely on it, which works, but the page I am building has multiple mutations that can take place at any given time (such as deleting a plan, adding a single item in a plan, deleting a single item in a plan etc.) and having all of these Mutation components sitting at the page-level component feels very messy.
Is there a way that I can access the loading property outside of this Mutation component? If not, what is the best way to handle this problem? I have read that you can manually update the Apollo local state using the update function on the Mutation component (see example below) but I haven't been able to work out how to access the loading value here (plus it feels like accessing the loading property of a specific mutation without having to manually write it to the cache yourself would be a common request).
<Mutation
mutation={CREATE_PLAN}
update={cache => {
cache.writeData({
data: {
createPlanLoading: `I DON"T HAVE ACCESS TO THE LOADING BOOLEAN HERE`,
},
});
}}
>
{(createPlan, { loading }) => (
<Button
onClick={() => createPlan({ variables: { input: {} } })}
loading={loading}
>
Save
</Button>
)}
</Mutation>
I face the same problem in my projects and yes, putting all mutations components at the page-level component is very messy. The best way I found to handle this is by creating React states. For instance:
const [createPlanLoading, setCreatePLanLoading] = React.useState(false);
...
<Mutation mutation={CREATE_PLAN} onCompleted={() => setCreatePLanLoading(false)}>
{(createPlan, { loading }) => (
<Button
onClick={() => {
createPlan({ variables: { input: {} } });
setCreatePLanLoading(true);
}
loading={loading}
>
Save
</Button>
)}
</Mutation>
I like the answer with React States. However, when there are many different children it looks messy with so many variables.
I've made a bit update for it for these cases:
const Parent = () => {
const [loadingChilds, setLoading] = useState({});
// check if at least one child item is loading, then show spinner
const loading = Object.values(loadingChilds).reduce((t, value) => t || value, false);
return (
<div>
{loading ? (
<CircularProgress />
) : null}
<Child1 setLoading={setLoading}/>
<Child2 setLoading={setLoading}/>
</div>
);
};
const Child1 = ({ setLoading }) => {
const [send, { loading }] = useMutation(MUTATION_NAME);
useEffect(() => {
// add info about state to the state object if it's changed
setLoading((prev) => (prev.Child1 !== loading ? { ...prev, Child1: loading } : prev));
});
const someActionHandler = (variables) => {
send({ variables});
};
return (
<div>
Child 1 Content
</div>
);
};
const Child2 = ({ setLoading }) => {
const [send, { loading }] = useMutation(MUTATION_NAME2);
useEffect(() => {
// add info about state to the state object if it's changed
setLoading((prev) => (prev.Child2 !== loading ? { ...prev, Child2: loading } : prev));
});
const someActionHandler = (variables) => {
send({ variables});
};
return (
<div>
Child 2 Content
</div>
);
};
I want to use a HOC component to display a loading animation. Some components have the prop "isLoading" and others "isPending". How can I give the HOC a defauult prop, that will make it show the loader?
isLoading
isPending
isNotReady
...
const WithLoader = ControlledComponent => ({ isPending, ...props }) => {
You can create a Higher order component WithLoader like below:
export const WithLoader = ControlledComponent => ({
isLoading, isPending, isNotReady, children,
}) => ((isLoading || isPending || isNotReady) ? <Loader />
: <>{children}</>
);
And then reuse it like:
<WithLoader>
<YourComponent />
</WithLoader>
Or
<WithLoader>
<YourComponent1 />
<YourComponent2/>
</WithLoader>
This way, you can create a generic higher order loader component which can be used even for multiple sibling components.
P.S. In the above implementation example of WithLoader, <>{..}</> used are react fragments, assuming that you don't want to have any additional markup in the DOM. If you want an html container for styling purposes, you can make it a <div> tag instead of a React.Fragment(<>).
This should work.
Assuming your loading component name is Loading
const WithLoader = ControlledComponent => ({ isLoading, isPending, isNotReady, ...props }) => {
return (isPending || isLoading || isNotReady) ?
<Loading />
:
<ControlledComponent {...props} />;
}
In my component, I am using react-adopt, to compose graphql queries and mutations, so that my render props don't get too messy. I have the following code:
This is my mutation, it takes one argument - planID.
const CREATE_ORDER_MUTATION = gql`
mutation CREATE_ORDER_MUTATION($planID: String!) {
createOrder(planID: $planID) {
planID
name
description
subscriptionType
images
subscriptionType
pricePerMonth
}
}
This is the adopt function, it takes a couple of mutations, one of which is createOrder. The way Apollo works is that I need to pass variables prop to createOrder component here. The problem is, I don't have the planID at this point. planID is available only inside of the actual component.
const Composed = adopt({
toggleCart: <Mutation mutation={TOGGLE_CART_MUTATION} />,
createOrder: <Mutation mutation={CREATE_ORDER_MUTATION} />,
});
My component looks like this. I have the planID available here, but how can I pass it as an argument to mutation?!
render() {
const { plan } = this.props;
return (
<Composed>
{({ toggleCart, createOrder }) => {
const handleAdd = () => {
toggleCart();
Router.push('/waschingmachine');
createOrder();
};
return (
<StyledPlan>
<h1>{plan.name}</h1>
<p>{plan.description}</p>
<img src={`static${plan.images[0]}`} alt="blue1" />
<h2>{plan.pricePerMonth / 100} EUR</h2>
<div className="buttons">
<Link
href={{
pathname: '/plan',
query: { id: plan.id },
}}
>
<button type="button">info</button>
</Link>
<button onClick={handleAdd} type="button">
Select Plan
</button>
</div>
</StyledPlan>
);
}}
</Composed>
);
}
If there is no way to solve it this way, how would you approach it differently?
Here is how you can pass arguments through react-adopt way into your inner mutations mapper:
// In a nutshell, `react-adopt` allows you to pass props to your `Composed`
// component, so your inner `mapper` can access those props to pass them to
// your <Query> or <Mutation>
// Here's your initial `Composed` component from above
const Composed = adopt({
// `planId` is passed from below via props (see `<ContainerComponent />)
toggleCart: ({ planId, render }) => (
<Mutation mutation={TOGGLE_CART_MUTATION} variables={{ planId }}>{render}</Mutation>,
),
// `planId` is passed from below via props (see `<ContainerComponent />)
createOrder: ({ planId, render })=> (
<Mutation mutation={CREATE_ORDER_MUTATION} variables={{ planId }}>{render}</Mutation>
)
});
// `<ContainerComponent />` will take a plan as its props and passed `planId` to
// the `<Composed />` component
const ContainerComponent = ({ plan }) => (
<Composed planId={plan.id}>
{({ toggleCart, createOrder }) => {
const handleAdd = e => {
e.preventDefault();
toggleCart();
// ...
createOrder();
// ...
};
// Whatever your UI needs you can pass them here via here
return <YourPresentationComponent onClick={handleAdd} />;
}}
</Composed>
)
// Now all you need to do is to pass `plan` from your render() to the
// <ContainerComponent /> (This is the render() where you return your
render() {
const { plan } = this.props;
return <ContainerComponent plan={plan} />
}
Hopefully this can be helpful to solve your issue! Just a side note, you can also get the previous mapper value and pass them onto the next mapper fn as part of the argument, see below:
const Composed = adopt({
// Here you can retrieve the actual `plan` by using `userId` and pass the result
// into your next children mapper fn to consume
plan: ({ userId, render }) => (
<Query query={GET_PLAN_GRQPHQL} variables={{ userId }}>{render}</Query>
),
// `plan` is actually coming from above <Query /> component
toggleCart: ({ plan, render }) => { //... },
createOrder: ({ plan, render }) => { //...}
});
The mutate function passed in the rendered children can be called with options.
Options can include variables used in the GraphQL mutation string. [1].
This means that you can call the createOrder mutation function like so.
createOrder({ variables: { planID: 'some plan id' } });
Given the dynamic nature of planID, there are a number of ways to implement this. One of which is to use data attributes as below:
A data attribute can be set on for the plan id on the button .
<button onClick={handleAdd} data-planid={plan.id} type="button">
Select Plan
</button>
handleAdd can be refactored to get the planid from the target dataset attribute and invoke createOrder with planID variable.
const handleAdd = event => {
const planID = event.target.dataset.planid;
toggleCart();
Router.push('/waschingmachine');
createOrder({ variables: { planID } });
};
Another is to directly pass planID to handleAdd when calling it in the onClick prop for the button.
<button onClick={() => handleAdd(plan.id)} type="button">
Select Plan
</button>
Then update the handler
const handleAdd = planID => {
toggleCart();
Router.push('/waschingmachine');
createOrder({ variables: { planID } });
};
There are tradeoffs to both approaches. For the earlier approach, the planid are set in the DOM as attributes and can be read later.
While for the later one, N handlers are created for N plans and are kept in memory.