Skip to content

Commit

Permalink
Adds stories/documentation for useCheckoutPricing
Browse files Browse the repository at this point in the history
  • Loading branch information
dbrudner committed Feb 27, 2020
1 parent 0888d75 commit f4f80ab
Show file tree
Hide file tree
Showing 3 changed files with 493 additions and 0 deletions.
212 changes: 212 additions & 0 deletions docs/2-hooks/useCheckoutPricing.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import { Subtitle } from '@storybook/addon-docs/blocks';
import { PropsTable } from '@storybook/components';

<Meta title="Hooks/useCheckoutPricing" />

# useCheckoutPricing

<Subtitle slot={() => 'To create a pricing preview'} />

`import { useCheckoutPricing } from '@recurly/react-recurly';`

A [custom hook][react-hooks] for interacting with [Recurly.js' pricing API][rjs-pricing] for providing users with an estimate of a purchase before they check out.

To initialize, call `useCheckoutPricing` [with an initial checkout pricing input][use-checkout-pricing-input].

```js
export default function PricingPreview() {
const initialPricingInput = {
subscriptions: [
{
plan: 'my-plan',
},
],
};

const [{ price, loading }, setCheckoutPricing] = useCheckoutPricing(initialPricingInput);

if (!loading) {
return <div>{price.now.subtotal}</div>
}
}
```

Calling `useCheckoutPricing` returns an array of two items:

1. A [useCheckoutPricingState][use-checkout-pricing-state] object for reading output.
2. A [setCheckoutPricing][set-checkout-pricing] function for updating input values.

For complete documentation on what `useCheckoutPricing` returns, see the [useCheckoutPricingReturn reference page][use-checkout-pricing-return].

## Initializing

It's important to know that immediately after calling `useCheckoutPricing`, `useCheckoutPricingState.price` will default to an empty object while waiting for asynchronous actions to resolve.

The below example will throw a type error because of this.
```js
export default function PricingPreview() {
const initialPricingInput = {
subscriptions: [
{
plan: 'my-plan',
},
],
};

const [{ price, loading }, setCheckoutPricing] = useCheckoutPricing(initialPricingInput);

return <div>{price.now && price.now.subtotal}</div>
}
```

The loading state can be checked before rendering anything that uses `useCheckoutPricingState.price` to circumvent the error.

```js
export default function PricingPreview() {
const initialPricingInput = {
subscriptions: [
{
plan: 'my-plan',
},
],
};

const [{ price }, setCheckoutPricing] = useCheckoutPricing(initialPricingInput);

return <div>{price.now && price.now.subtotal}</div>
}
```

Existence checking can be used as well.

```js
export default function PricingPreview() {
const initialPricingInput = {
subscriptions: [
{
plan: 'my-plan',
},
],
};

const [{ price }, setCheckoutPricing] = useCheckoutPricing(initialPricingInput);

return <div>{price.now && price.now.subtotal}</div>
}
```

## Updating

The [setCheckoutPricing][set-checkout-pricing] function can be used to update the checkout pricing input if needed with a new [useCheckoutPricingInput][use-checkout-pricing-input].

For example, if a customer wishes to choose a different plan for their subscription on a checkout page and the price preview needs to be updated, calling `setCheckoutPricing` with new inputs will update its associated `useCheckoutPricingState` and trigger a re-render.

```js
export default function PricingPreview() {
const [plan, setPlan] = useState("")
const initialPricingInput = {
subscriptions: [
{
plan: 'my-plan',
},
],
};

const [{ price, loading }, setCheckoutPricing] = useCheckoutPricing(initialPricingInput);

const handleChange = e => {
const newPlan = e.target.value;
setPlan(newPlan);

// calling setCheckoutPricing here with new input values will update `price` and trigger a re-render
setCheckoutPricing({ subscriptions: [{ plan: newPlan }] });
}

if (!loading) {
return <div>
<div>
{price.now.subtotal}
</div>
<select value={plan} onChange={handleChange}>
<option value="">Select a plan</option>
<option value="my-plan">My plan</option>
<option value="my-second-plan">My second plan</option>
</select>
</div>
}
}
```

`setCheckoutPricing` is an implementation of [react state][react-use-state] and follows the same rules and patterns. It's also possible pass a function to `setCheckoutPricing` that accepts its previous inputs as the first and only argument:

```js
setCheckoutPricing(previous => ({ ...previous, subscriptions: [{ plan: newPlan }] }));
```

## Loading state

Because Recurly.js' pricing API is asynchronous, a loading state is provided as a boolean.

```js
export default function PricingPreview() {
const [plan, setPlan] = useState(null)

const initialPricingInput = {
subscriptions: [
{
plan: 'my-plan',
},
],
};

const [{ loading }, setCheckoutPricing] = useCheckoutPricing(initialPricingInput);

if (loading) {
return <div>Loading...</div>
}
...
}
```

When `useCheckoutPricing` is first initiated or when it's updated with `setCheckoutPricing`, `useCheckoutPricingState.loading` will be set to `true` until its asynchronous actions have resolved, then set back to `false`.
## Error handling

Any errors from `useCheckoutPricing` will be thrown by default. Passing a function as the second argument to `useCheckoutPricing` allows errors to be caught and handled.

```js
export default function PricingPreview() {
const [recurlyError, setRecurlyError] = useState(null);

const initialPricingInput = {
subscriptions: [
{
plan: 'my-plan',
},
],
};

const [{ price, loading }, setCheckoutPricing] = useCheckoutPricing(initialPricingInput, setRecurlyError);

if (recurlyError && !loading) {
return <div>Error: {recurlyError.message}</div>
}
...
}
```

Errors will always take the shape of a [Recurly.js error][recurly-error].

## Demo

For a full example of `useCheckoutPricing`, see our [interactive demo][interactive-demo].

[react-hooks]: https://reactjs.org/docs/hooks-intro.html
[rjs-pricing]: https://developers.recurly.com/reference/recurly-js/#pricing
[react-use-state]: https://reactjs.org/docs/hooks-state.html
[recurly-error]: https://developers.recurly.com/reference/recurly-js/#errors
[interactive-demo]: /?path=/docs/introduction-interactive-demo--page
[set-checkout-pricing]: /?path=/docs/reference-usecheckoutpricingreturn--page#setcheckoutpricing)
[use-checkout-pricing-state]: /?path=/docs/reference-usecheckoutpricingreturn--page#usecheckoutpricingstate
[use-checkout-pricing-return]: /?path=/docs/reference-usecheckoutpricingreturn--page
[use-checkout-pricing-input]: /?path=/docs/reference-usecheckoutpricinginput--page
[use-checkout-pricing-updating]: /?path=/docs/reference-usecheckoutpricinginput--page#updating
Loading

0 comments on commit f4f80ab

Please sign in to comment.