Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/outlandishideas/kasia

:tophat: A React Redux toolset for the WordPress API
https://github.com/outlandishideas/kasia

javascript kasia redux sagas universal-applications wordpress wordpress-api wp-api

Last synced: about 1 month ago
JSON representation

:tophat: A React Redux toolset for the WordPress API

Awesome Lists containing this project

README

        



kasia

A React Redux toolset for the WordPress API

Made with ❤ at @outlandish


npm version

travis ci build
Coverage Status




:sparkles: We welcome contributors!

:vertical_traffic_light: Issues are triaged using a traffic light system:

   ![#00ff00](http://placehold.it/15/00ff00/000000?text=+) [small](https://github.com/outlandishideas/kasia/issues?q=is%3Aopen+is%3Aissue+label%3Asmall) - quick tasks, great for beginner contributors

   ![#ffff00](http://placehold.it/15/ffff00/000000?text=+) [medium](https://github.com/outlandishideas/kasia/issues?q=is%3Aopen+is%3Aissue+label%3Amedium) - tasks with increased complexity, may take some time to implement

   ![#ff0000](http://placehold.it/15/ff0000/000000?text=+) [large](https://github.com/outlandishideas/kasia/issues?q=is%3Aopen+is%3Aissue+label%3Alarge) - big tasks that touch many parts of the library, will require commitment

[Get started contributing here.](https://github.com/outlandishideas/kasia/issues)


Get data from WordPress and into components with ease...

```js
// e.g. Get a post by its slug
@connectWpPost('post', 'spongebob-squarepants')
export default class extends React.Component () {
render () {
const { post: spongebob } = this.props.kasia

if (!spongebob) {
return

{'Who lives in a pineapple under the sea?'}


}

return

{spongebob.title.rendered}!


//=> Spongebob Squarepants!
}
}
```

## Features

- Declaratively connect React components to data from WordPress.
- Uses [`node-wpapi`](https://github.com/WP-API/node-wpapi) in order to facilitate complex queries.
- Register and consume Custom Content Types with ease.
- All WP data is normalised at `store.wordpress`, e.g. `store.wordpress.pages`.
- Support for universal applications.
- Support for plugins, e.g. [`wp-api-menus`](https://github.com/outlandishideas/kasia/tree/master/packages/kasia-plugin-wp-api-menus).

## Glossary

- [Requirements](#requirements)
- [Install](#install)
- [Import](#import)
- [__Configure__](#configure)
- [__Usage__](#usage)
- [Exports](#exports)
- [Plugins](#plugins)
- [Universal Applications](#universal-applications)
- [Contributing](#contributing)
- [Author & License](#author-&-license)

## Requirements

Kasia suits applications that are built using these technologies:

- React
- Redux
- Redux Sagas (>= 0.10.0)
- WordPress
- [WP-API plugin](http://v2.wp-api.org/)
- [`node-wpapi`](https://github.com/WP-API/node-wpapi)

## Install

```sh
npm install kasia --save
```

```sh
yarn add kasia
```

## Import

```js
// ES2015
import kasia from 'kasia'
```

```js
// CommonJS
var kasia = require('kasia')
```

## Configure

Configure Kasia in three steps:

1. Initialise Kasia with an instance of `node-wpapi`.

2. Spread the Kasia reducer when creating the redux root reducer.

3. Run the Kasia sagas after creating the redux-saga middleware.

A slimline example...

```js
import { combineReducers, createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import kasia from 'kasia'
import wpapi from 'wpapi'

const wpai = new wpapi({ endpoint: 'http://wordpress/wp-json' })

const { kasiaReducer, kasiaSagas } = kasia({ wpapi })

const rootSaga = function * () {
yield [...kasiaSagas]
}

const rootReducer = combineReducers({
...kasiaReducer
})

const sagaMiddleware = createSagaMiddleware()

export default function configureStore (initialState) {
const store = createStore(
rootReducer,
initialState,
applyMiddleware(sagaMiddleware)
)

sagaMiddleware.run(rootSaga)

return store
}
```

## Usage

### `kasia(options) : Object`

Configure Kasia.

- __options__ {Object} Options object

Returns an object containing the Kasia reducer and sagas.

```js
const { kasiaReducer, kasiaSagas } = kasia({
wpapi: new wpapi({ endpoint: 'http://wordpress/wp-json' })
})
```

The `options` object accepts:

- `wpapi` {wpapi}

An instance of `node-wpapi`.

- `keyEntitiesBy` {String} _(optional, default=`'id'`)_

Property of entities that is used to key them in the store.

One of: `'slug'`, `'id'`.

- `debug` {Boolean} _(optional, default=`false`)_

Log debug information to the console.

- `contentTypes` {Array} _(optional)_

Array of custom content type definitions.

```js
// Example custom content type definition
contentTypes: [{
name: 'book',
plural: 'books',
slug: 'books',
route, // optional, default="/{plural}/(?P)"
namespace, // optional, default="wp/v2"
methodName // optional, default={plural}
}]
```

- `plugins` {Array} _(optional)_

Array of Kasia plugins.

```js
import kasiaWpApiMenusPlugin from 'kasia-plugin-wp-api-menus'

// Example passing in plugin
plugins: [
[kasiaWpApiMenusPlugin, { route: 'menus' }], // with configuration
kasiaWpApiMenusPlugin, // without configuration
]
```

### Decorators

Things to keep in mind:

- A component will make a request for data 1) when it mounts and 2) if its props change. For `connectWpPost` a change
in props will trigger Kasia to try and find entity data for the new identifier in the store. If it is found, no request
is made.
- Content data should be parsed before being rendered as it may contain encoded HTML entities.
- In arbitrary queries with `connectWpQuery`, we suggest that you always call the `embed` method on the
query chain, otherwise embedded content data will be omitted from the response.
- Paging data for the request made on behalf of the component is available at `this.props.kasia.query.paging`.
- The examples given assume the use of [decorators (sometimes called annotations)](https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy).
However decorator support is not necessary.
See the end of each example for the alternative Higher Order Component approach.

#### `@connectWpPost(contentType, identifier) : Component`

Connect a component to a single entity in WordPress, e.g. Post, Page, or custom content type.

- __contentType__ {String} The content type to fetch
- __identifier__ {String|Number|Function} ID of the entity to fetch or function that derives it from `props`

Returns a connected component.

Example, using identifier derived from route parameter on `props`:

```js
import React, { Component } from 'react'
import { Route } from 'react-router'
import { connectWpPost } from 'kasia/connect'
import { Page } from 'kasia/types'

@connectWpPost(Page, (props) => props.params.slug)
export default class Page extends Component {
render () {
const { query, page } = this.props.kasia

if (!query.complete) {
return Loading...
}

return

{page.title}


}
}

// Without decorator support
export default connectWpPost(Page, (props) => props.params.slug)(Post)
```

#### `@connectWpQuery(queryFn, shouldUpdate) : Component`

Connect a component to the result of an arbitrary WP-API query. Query is always made with `?embed` query parameter.

- __queryFn__ {Function} Function that accepts args `wpapi`, `props`, `state` and should return a WP-API query
- __shouldUpdate__ {Function} Called on `componentWillReceiveProps` with args `thisProps`, `nextProps`, `state`

Returns a connected component.

The component will request new data via `queryFn` if `shouldUpdate` returns true.

Entities returned from the query will be placed on `this.props.kasia.entities` under the same
normalised structure as described in [The Shape of Things](#the-shape-of-things).

Example, fetching the most recent "News" entities:

```js
import React, { Component } from 'react'
import { Route } from 'react-router'
import { connectWpPost } from 'kasia/connect'

// Note the invocation of `embed` in the query chain
@connectWpQuery((wpapi, props) => {
return wpapi.news().month(props.month).embed().get()
}, (thisProps, nextProps) => thisProps.month != nextProps.month)
export default class RecentNews extends Component {
render () {
const {
query,
data: { news }
} = this.props.kasia

if (!query.complete) {
return Loading...
}

return (


Recent News Headlines


{Object.keys(news).map((key) =>

{news[key].title}

)}

)
}
}

// Without decorator support
export default connectWpQuery((wpapi) => {
return wpapi.news().embed().get()
})(Post)
```

## Exports

### `kasia`

The Kasia configurator and preload utilities.

```js
import kasia, { preload, preloadQuery } from 'kasia'
```

### `kasia/connect`

The connect decorators.

```js
import { connectWpPost, connectWpQuery } from 'kasia/connect'
```

### `kasia/types`

The built-in WordPress content types that can be passed to `connectWpPost` to define what content type
a request should be made for.

```js
import {
Category, Comment, Media, Page,
Post, PostStatus, PostType,
PostRevision, Tag, Taxonomy, User
} from 'kasia/types'
```

See [Universal Application Utilities](#Utilities) for more details.

## Plugins

Kasia exposes a simple API for third-party plugins.

A plugin should:

- be a function that accepts these arguments:
- __wpapi__ {wpapi} An instance of `wpapi`
- __pluginOptions__ {Object} The user's options for the plugin
- __kasiaOptions__ {Object} The user's options for Kasia

- return an object containing `reducers` (Object) and `sagas` (Array).

- use the `'kasia/'` action type prefix.

```js
// Example definition returned by a plugin
{
reducer: {
'kasia/SET_DATA': function setDataReducer () {}
'kasia/REMOVE_DATA': function removeDataReducer () {}
},
sagas: [function * fetchDataSaga () {}]
}
```

A plugin can hook into Kasia's native action types, available at `kasia/lib/constants/ActionTypes`.
All reducers for an action type are merged into a single function that calls each reducer in succession
with the state returned by the previous reducer. This means the order of plugins that touch the same
action type is important.

### Available plugins:

- [`kasia-plugin-wp-api-menus`](https://github.com/outlandishideas/kasia/tree/master/packages/kasia-plugin-wp-api-menus)
- [`kasia-plugin-wp-api-all-terms`](https://github.com/outlandishideas/kasia/tree/master/packages/kasia-plugin-wp-api-all-terms)
- [`kasia-plugin-wp-api-response-modify`](https://github.com/outlandishideas/kasia/tree/master/packages/kasia-plugin-wp-api-response-modify)

Please create a pull request to get your own added to the list.

## Universal Applications

__Important...__

- __before calling the preloaders for SSR you must call `kasia.rewind()`__
- __or if you call `runSagas()` from the utilities then this is done for you.__

### Utilities

#### `runSagas(store, sagas) : Promise`

Run a bunch of sagas against the store and wait on their completion.

- __store__ {Object} Redux store enhanced with `runSaga` method
- __sagas__ {Array} Array of functions that accept the store state and return a saga generator

Returns a Promise resolving on completion of all the sagas.

#### `preload(components[, renderProps][, state]) : Generator`

Create a saga operation that will preload all data for any Kasia components in `components`.

- __components__ {Array} Array of components
- [__renderProps__] {Object} _(optional)_ Render props object derived from the matched route
- [__state__] {Object} _(optional)_ Store state

Returns a [saga operation](#saga-operation-signature).

#### `preloadQuery(queryFn[, renderProps][, state]) : Generator`

Create a saga operation that will preload data for an arbitrary query against the WP API.

- __queryFn__ {Function} Query function that returns `node-wpapi` query
- [__renderProps__] {Object} _(optional)_ Render props object
- [__state__] {Object} _(optional)_ Store state

Returns a [saga operation](#saga-operation-signature).

#### `.preload(renderProps[, state]) : Array`

Connected components expose a static method `preload` that produces an array of saga operations
to facilitate the request for entity data on the server.

- __renderProps__ {Object} Render props object derived from the matched route
- [__state__] {Object} _(optional)_ State object (default: `null`)

Returns an array of [saga operations](#saga-operation-signature).

#### Saga Operation Signature

A saga operation is an array of the form:

```js
[ sagaGeneratorFn, action ]
```

Where:

- `sagaGenerator` Function that must be called with the `action`.

- `action` action Object containing information for the saga to fetch data.

### Example

A somewhat contrived example using the available preloader methods.

```js
import { match } from 'react-router'
import { runSagas, preload, preloadQuery } from 'kasia'

import routes from './routes'
import store from './store'
import renderToString from './render'
import getAllCategories from './queries/categories'

export default function renderPage (res, location) {
return match({ routes, location }, (error, redirect, renderProps) => {
if (error) return res.sendStatus(500)
if (redirect) return res.redirect(302, redirect.pathname + redirect.search)

// We are using `runSagas` which rewinds for us, but if we weren't then
// we would call `kasia.rewind()` here instead:
//
// kasia.rewind()

// Each preloader accepts the state that may/may not have been modified by
// the saga before it, so the order might be important depending on your use-case!
const preloaders = [
() => preload(renderProps.components, renderProps),
(state) => preloadQuery(getAllCategories, renderProps, state)
]

return runSagas(store, preloaders)
.then(() => renderToString(renderProps.components, renderProps, store.getState()))
.then((document) => res.send(document))
})
}
```

## Testing

Kasia components can be tested by:
- lifting the query function from the connectWpQuery decorator and exporting it
- lifting the should update function from the connectWpQuery deocrating and exporting it
- making your component available as a named export prior to decoration

An example:
```js
export function postQuery (wpapi) {...}

export function shouldUpdate (thisProps, nextProps, state) {...}

export class Post extends Component {...}

@connectWpQuery(postQuery, shouldUpdate)
export default Post
```

You can then test the component without decoration, the query and the shouldUpdate function
in isolation.

## Contributing

All pull requests and issues welcome!

- When submitting an issue please provide adequate steps to reproduce the problem.
- PRs must be made using the `standard` code style.
- PRs must update the version of the library according to [semantic versioning](http://semver.org/).

If you're not sure how to contribute, check out Kent C. Dodds'
[great video tutorials on egghead.io](https://egghead.io/lessons/javascript-identifying-how-to-contribute-to-an-open-source-project-on-github)!

## Author & License

`kasia` was created by [Outlandish](https://twitter.com/outlandish) and is released under the MIT license.