npm package: @omneo/shapes-react-helpers
Description: The best way to use Omneo Shapes in any React project
Author: Omneo
Latest version: 0.0.7

# Shapes Provider for React
The best way to use Omneo Shapes in any React project

## Features
- Create and consume Shape SDK clients
- Receive Shape client stat updaes through props
- Uses React Context API under the hood and exposes context for full flexibility
- `Shape` helper component to easily hydrate and filter Shape resources
- `withShapes` component wrapper for simple context subscription
- Automatic shape rendering using `data-omneo-shape` attribute

## Getting started
```sh
$ yarn add @omneo/react-shapes-provider
```

Include the Shapes provider at the top level of your project
```js
import {ShapesProvider} from '@omneo/shapes-react-helpers';

return(
    <ShapesProvider shapesClient={{
        url:"https://api.[tenant].getomneo.com/id",
        token: "..."
    }}/>
)
```

## <ShapesProvider/>
The ShapesProvider consumes a Shapes SDK client and mirrors the client state within React. This provider can then be subscribed to using React’s Context API, for components to receive the data state, as well as convenient access to all client functions (`hydrate`,`find`,`get`…)

ShapesProvider requires a Shapes SDK client to operate. This can be passed in as a reference to an existing client initialised in React or on the window, or the ShapesProvider can create a new client with a client config object. If no client or config is passed into the provider, the provider will attempt to find an already initialised shape at `window.ShapesSDKClient`

Using existing client reference
```js
const client = new ShapesSDK.init({...})
return(
    <ShapesProvider 
        shapesClient={{
            url:"https://api.[tenant].getomneo.com/id",
            token: "...",
            logging: true,
            isPrivate: false
        }}
        children={children}
    />
)
```

Using client config
```js
 <ShapesProvider 
    shapesClient={{
        url:"https://api.[tenant].getomneo.com/id",
        token: "...",
        logging: true,
        isPrivate: false
    }}
    children={children}
/>
```

Automatically try and connect to `window.ShapesSDKClient`
```js
 <ShapesProvider children={children}/>
```

## Context values
The ShapesProvider context exposes all resources and methods of the ShapesSDK client, in the following structure:
```js
{
    state: {
        profiles: {...},
        balances: {...},
        transactions: {...},
        ... // All other resources
    },
    methods: {
        on(),
        off(),
        hydrate(),
        find(),
        get(),
        post(),
        put(),
        delete(),
        dispatch()
    }
}
```

## <Shape/>
This library includes a helper component that uses the ShapesProvider context and passes the context state and methods to its children. The `Shape` component also includes a helper function to automatically `hydrate()` configured resources and include only those resources in the state props, send to child components.

Child components to `Shape` can be included as a children function
```js
import React from 'react';
import {Shape} from './ShapesContext';

// Hydrate and include profile in props
const ProfileName = () => (
    <Shape 
        requires={['profile']} 
        children={props=>(
            <p>Name: {props.profile.data.full_name}</p>
        )}
    />
)
```
They can also be included directly as JSX. Props will be added to direct children only.
```js
import React from 'react';
import {Shape} from './ShapesContext';

// Hydrate and include balances prop. Use find() method to pluck balance value.
const ProfileName = (parentProps) => (
    <Shape 
        requires={['balances']} 
        children={props=>{
            const {prefix = null, suffix = null} = parentProps;
            return(
                 <p>Rewards: {prefix}{props.methods.find('balances.data.combined_balance_dollars')}{suffix}</p>
            )
        }}
    />
)

export default ProfileName
```

## withShapes()
If you don't want to use the `<Shape/>` helper component, this library includes a function similar to Redux's `connect()`. Simply export your React components wrapped in `withShapes()` and they will receive a `shapes` prop, including the `state` and `methods` objects from ShapesProvider.

```js
import React from 'react';
import {withShapes} from './ShapesContext';

class RewardBalance extends React.PureComponent{
    componentDidMount(){
        this.props.shapes.methods.hydrate('balances');
    }

    render(){
        const {loading, data} = this.props.shapes.state.balances;
        if(loading){
            return <p>Loading....</p>
        }

        return <p>{data.combined_balance_dollars}</p>
    }
}

export default withShapes(RewardBalance)
```

## Automatic Shapes
ShapesProvider includes the ability to automatically render named components to any element on a page with the `data-omneo-shape` attribute. This can drastically reduce the complexity and heavy lifting of rendering out components such as "like" or "wishlist" buttons on a PLP - This function also allows these components to be rendered outside of the regular React structure, so `data-omneo-shape` elements can be put anywhere, not just within the `children` of the Provider or the React app. Automatic Shapes uses the React Portals API under the hood.

### Using automatic shapes
Add the `automatic` prop to `<ShapesProvider>` and include the `shapeTypes` prop with an object of named react components. This example will render `<p>This is an example</p>` inside the `<div data-omneo-shape="ExampleShape"/>` div.

```js
import React from 'react';
import {ShapesProvider} from './ShapesContext';

const ExampleComponent = () => (
    <p>This is an example</p>
)

const App = () => (
    <div className="App">
        <div data-omneo-shape="ExampleShape"/> 
        <ShapesProvider 
          automatic 
          shapeTypes={{
              ExampleShape: ExampleComponent
          }} 
        />
    </div>
)

export default App
```

### Including automatic shape parameters
Automatic shapes will parse all data attributes on the element and include them as props. This simplifies the configuration of shapes where minor configuration is needed. Config values are included within the `props.config` object.

```js
import React from 'react';
import {ShapesProvider} from './ShapesContext';

const Name = props => (
    <p>Name: {props.config.name}</p>
)

const App = () => (
    <div className="App">
        <div data-omneo-shape="Name" data-name="Amy"/> 
        <div data-omneo-shape="Name" data-name="David"/> 
        <div data-omneo-shape="Name" data-name="Ashley"/> 
        <ShapesProvider 
          automatic 
          shapeTypes={{
              Name: Name
          }} 
        />
    </div>
)

export default App
```

### Advanced usage
As automatic shapes rendering is managed from within the ShapesProvider, all relevant context data and props are included from your React application. You can use the `Shape` component or your own Redux state and all props will be retained when rendered to the page.