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?
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.
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 }) => { //...}
});