Giter VIP home page Giter VIP logo

kasia's Introduction

kasia

A React Redux toolset for the WordPress API

Made with โค at @outlandish

npm version travis ci build coverage


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

// e.g. Get a Post by its slug
@connectWpPost(Post, 'spongebob-squarepants')
function SpongebobSquarepants (props) {
  const { post: spongebob } = props.kasia

  return spongebob
    ? <h1>{spongebob.title}</h1> //=> Spongebob Squarepants
    : <span>Loading...</span>
}

Features

  • Declaratively connect React components to data from WordPress.
  • Uses node-wpapi internally 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.

Check out the Kasia boilerplate!

Glossary

Requirements

Kasia suits applications that are built using these technologies:

Install

npm install kasia --save

Import

// ES2015
import Kasia from 'kasia'
// 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...

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

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

const { kasiaReducer, kasiaSagas } = Kasia({ WP })

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

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. 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:

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 <span>Loading...</span>
    }

    return <h1>{page.title}</h1>
  }
}

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

@connectWpQuery(queryFn[, propsComparatorFn, options]) : Component

Connect a component to the result of an arbitrary WP-API query.

  • queryFn {Function} Function that accepts args wpapi, props, state and should return a WP-API query
  • propsComparatorFn {Function} (optional) Function that determines if new data should be requested by inspecting props
  • [options.displayName] {String} (optional) Display name of the component, useful if component is wrapped by other decorators which will disguise the actual displayName. Important if the component is used with prepared queries (server-side rendering).

Returns a connected component.

By default the component will request new data via the given queryFn if the propsComparatorFn returns true. The default property comparison behaviour is to diff primitive values on the props objects.

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.

Example, fetching the most recent "News" entities:

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()
})
export default class RecentNews extends Component {
  render () {
    const {
      query,
      entities: { news }
    } = this.props.kasia

    if (!query.complete) {
      return <span>Loading...</span>
    }

    return (
      <div>
        <h1>Recent News Headlines</h1>
        {Object.keys(news).map((key) =>
          <h2>{news[key].title}</h2>)}
      </div>
    )
  }
}

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

Kasia(options) : Object

Configure Kasia.

  • options {Object} Options object

Returns an object containing the Kasia reducer and sagas.

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

The options object accepts:

  • WP {wpapi}

    An instance of node-wpapi.

  • keyEntitiesBy {String} (optional) (default 'id')

    Property of entities used to key them in the store

  • contentTypes {Array} (optional)

    Array of custom content type definitions

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

    Array of Kasia plugins.

    import KasiaWpApiMenusPlugin from 'kasia-plugin-wp-api-menus'
    
    // Example passing in plugin
    plugins: [
        [KasiaWpApiMenusPlugin, { route: 'menus' }], // with configuration
        KasiaWpApiMenusPlugin, // without configuration
    ]

Exports

kasia

The Kasia configurator.

import Kasia from 'kasia'

kasia/connect

The connect decorators.

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.

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

kasia/util

Utility methods to help you when building your application.

import { 
  makePreloaderSaga, 
  makeQueryPreloaderSaga,
  makePostPreloaderSaga
} from 'kasia/util'

The Shape of Things

Kasia restructures the shape of things returned from the WP-API.

The changes made to the data are all effects available in the wp-api-response-modify library.

Why?

The JSON returned from WP-API contains such things as objects with a single property (e.g. objects with rendered), meta data property names prefixed with an underscore (e.g. _links), and

What changes should I be aware of?

  • Queries initiated by connectWpPost will always request embedded data.

    The primary reason for this is to reduce the number of requests made to the WP-API as it is very common to not only want content data, but also any metadata such as authors.

  • All property names are camel-cased.

    "featured_media" => "featuredMedia"
  • Links are removed.

    { title: 'Wow what an amazing title!', _links: {}, ... }
    // becomes...
    { title: 'Wow what an amazing  title!', ... }
  • Objects that have a single property 'rendered' are flattened.

    { content: { rendered: '<h1>Hello, World!</h1>' }, ... }
    // becomes...
    { content: '<h1>Hello, World!</h1>', ... }
  • Content types are normalised using normalizr. This means that any embedded content data is made available on the store within its respective content type collection. For example:

    {
      posts: {},
      users: {},
      pages: {},
      news: {}, // custom content type
      ...
    }

Plugins

Kasia exposes a simple API for third-party plugins.

A plugin should:

  • be a function that accepts these arguments:

    • WP {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.

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

Available plugins:

Universal Applications

Utilities

util/makePreloaderSaga(components, renderProps) : Generator

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

  • components {Array} Array of components
  • renderProps {Object} Render props object derived from the matched route

Returns a saga operation.

util/makeQueryPreloaderSaga(queryFn, renderProps) : Generator

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

  • queryFn {Function} Query function that accepts wpapi as argument
  • renderProps {Object} Render props object

Returns a saga operation.

util/makePostPreloaderSaga(contentType, id[, state]) : Generator

Create a single saga operation that will preload data for a single post from the WP API.

  • contentType {String} The content type of the item to fetch
  • id {String|Number|Function} ID of the post or a function to derive from renderProps
  • renderProps {Object} Render props object
  • [state] {Object} (optional) State object (default: null)

Returns a saga operation.

ConnectedComponent.makePreloader(renderProps[, state]) : Array<Array>

Connected components expose a static method makePreloader that produces an array of saga operations to facilitate the request for entity data on the server ("preloaders").

Create an array of preloader operations.

  • renderProps {Object} Render props object derived from the matched route
  • [state] {Object} (optional) State object (default: null)

Returns an array of saga operations in the form:

// Saga operations
[ [sagaGeneratorFn, action] ]

Elements:

  • sagaGenerator {Function} Must be called with the action

  • action {Object} An action object containing information for the saga to fetch data

Example

A somewhat contrived example using the available kasia/util methods (see below).

import { match } from 'react-router'

import { 
  makePreloaderSaga,
  makeQueryPreloaderSaga
} from 'kasia/util'

// Our application's react-router routes
import routes from './routes'

// Configures the redux store with saga middleware
// and enhances it with the `runSaga` method
import store from './store'

// Takes the components and render props from matched route, and
// the store state and produces the complete HTML as a string
import renderToString from './render'

// Collection of query functions that request data via `wpapi`
import { categoriesQuery } from './queries'

// Run all `sagas` until their completion
function runSagas (store, sagas) {
  return sagas.reduce((promise, saga) => {
    return promise.then(() => store.runSaga(saga).done)
  }, Promise.resolve())
}

// Produce a static webpage and send to the client for the given `route`
export function preload (res, route) { 
  return match({ routes, location: route })
    .then((error, redirectLocation, renderProps) => {
      if (error) {
        res.sendStatus(500)
        return
      }
        
      if (redirectLocation) {
        res.redirect(302, redirectLocation.pathname + redirectLocation.search)
        return
      }

      const preloaders = [
        makeQueryPreloaderSaga(categoriesQuery, renderProps),
        makePreloaderSaga(renderProps.components, renderProps)
      ]

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

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.

If you're not sure how to contribute, check out Kent C. Dodds' great video tutorials on egghead.io!

Author & License

kasia was created by Outlandish and is released under the MIT license.

kasia's People

Contributors

conatus avatar nickarora avatar rasmuswinter avatar sdgluck avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.