Skip to content

jf248/react-powerplug

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

71 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

react-powerplug

npm npm GitHub issues GitHub stars Twitter

🔌 Renderless Pluggable State Containers


I'm working on version 1.0. Follow me on twitter to know when news will come up.

React PowerPlug is a set of pluggable renderless components that provides different types of state and logics so you can use with your dumb components. It creates a state and pass down the logic to the children, so you can handle your data/callbacks.

It's has been created to (but not limited to) use with storybook, react styleguidist, documentations, prototypes. You can also use to create a bunch of uncontrolled components where the outside app don't care about your state, for example a menu dropdown.

Quick examples:

import { State, Index, Toggle } from 'react-powerplug'
import { Pagination, Tabs, Checkbox } from './MyDumbComponents'

<State initial={{ offset: 0, limit: 10, totalCount: 200 }}>
  {({ state, setState }) => (
    <Pagination {...state} onChange={(offset) => setState({ offset })} />
  )}
</State>

<Index initial={0}>
  {({ index, setIndex }) => (
    <Tabs selected={index} onChange={setIndex}>
      <Tab title="First tab">Content from the first tab</Tab>
      <Tab title="Second tab">Content from the second tab</Tab>
    </Tabs>
  )}
</Index>

<Toggle initial={true}>
  {({ on, toggle }) => (
    <Checkbox checked={on} onChange={toggle} />
  )}
</Toggle>

// You can also use a `render` prop instead

<Toggle
  initial={false}
  render={({ on, toggle }) => (
    <Checkbox checked={on} onChange={toggle}>
  )}
/>

Renderless Components

Note: Also known as render props or children as function.
For most people, this may look pretty weird. But it's extremely powerful. Usually you render some element whitin your component, but if you are creating an 'agnostic' component or a kind of abstraction component you can not simply render elements, you don't know what the user wants to render. So you render values, actions, states, data... props. You pick these props passing a function to children (or render prop) and now with this data you have total control of what you want to do. Now render what you want to render and do what you want to do. Renderless components are about abstracting some logic without any UI.

React PowerPlug brings to you a bunch of components that act as state and logic containers, so you can get this powerful abstraction and use it any way you want.

Note: with PowerPlug you can use children or render prop.
You decide which one is best for you.

Components

Note: This is a kind of a cheat sheet for fast search.
If you want a more detailed API Reference and examples for each component see the Docs

State Containers

State

Component Props: { initial }
Render Props: { state, setState }
see docs

<State initial={{ isLoading: false, data: null }}>
  {({ state, setState }) => (
    <DataReceiver
      data={state.data}
      onStart={() => setState({ isLoading: true })}
      onFinish={data => setState({ data, isLoading: false })}
    />
  )}
</State>

Toggle

Component Props: { initial }
Render Props: { on, off, toggle, setOn }
see docs

<Toggle initial={true}>
  {({ on, toggle }) => (
    <Checkbox checked={on} onChange={toggle} />
  )}
</Toggle>

Loading

Component Props: { initial }
Render Props: { isLoading, toggle, setLoading }
see docs

<Loading initial={false}>
  {({ isLoading, toggleLoading }) => (
    <Button onClick={toggleLoading}>
      {isLoading ? 'Loading...' : 'Click me'}
    </Button>
  )}
</Loading>

Value

Component Props: { initial }
Render Props: { value, setValue }
see docs

<Value initial="React">
  {({ value, setValue }) => (
    <Select
      label="Choose one"
      options={['React', 'Preact', 'Vue']}
      value={value}
      onChange={setValue}
    />
  )}
</Value>

Index

Component Props: { initial }
Render Props: { index, setIndex }
see docs

<Index initial={0}>
  {({ index, setIndex }) => (
    <Tabs selected={index} onChange={setIndex}>
      <Tab title="First tab">Content from the first tab</Tab>
      <Tab title="Second tab">Content from the second tab</Tab>
    </Tabs>
  )}
</Index>

Set

Component Props: { initial }
Render Props: { set, get, values }
see docs

<Set initial={{ sounds: true, graphics: 'medium' }}>
  {({ set, get }) => (
    <Settings>
      <ToggleCheck checked={get('sounds')} onChange={c => set('sounds', c)}>
        Game Sounds
      </ToggleCheck>
      <Select
        label="Graphics"
        options={['low', 'medium', 'high']}
        selected={get('graphics')}
        onSelect={value => set('graphics', value)}
      />
    </Settings>
  )}
</Set>

List

Component Props: { initial }
Render Props: { list, push, pull, sort, setList }
see docs

<List initial={['react', 'babel']}>
  {({ list, pull, push }) => (
    <div>
      <FormInput onSubmit={push} />
      {list.map(tag => (
        <Tag onRemove={() => pull(value => value === tag)}>
          {tag}
        </Tag>
      )}
    </div>
  )}
</List>

Feedback Containers

It's like css pseudo-selectors, but in js :)

Hover

Component Props: { }
Render Props: { isHover, bindHover }
see docs

<Hover>
  {({ isHover, bindHover }) => (
    <div {...bindHover}>
      You are {isHover ? 'hovering' : 'not hovering'} this div.
    </div>
  )}
</Hover>

Active

Component Props: { }
Render Props: { isActive, bindActive }
see docs

<Active>
  {({ isActive, bindActive }) => (
    <div {...bindActive}>
      You are {isActive ? 'clicking' : 'not clicking'} this div.
    </div>
  )}
</Active>

Focus

Component Props: { }
Render Props: { isFocus, bindFocus }
see docs

<Focus>
  {({ isFocus, bindFocus }) => (
    <div>
      <input {...bindFocus} placeholder="Focus me" />
      <div>You are {isFocus ? 'focusing' : 'not focusing'} the input.</div>
    </div>
  )}
</Focus>

Form Containers

Note: v1.0 will have more powerful form-related components, stay tuned!

Bind

Component Props: { initial }
Render Props: { value, setValue, bind }
see docs

<Bind initial="hello world">
  {({ bind, value }) => (
    <div>
      <ControlledInput {...bind} />
      <div>You typed {value}</div>
    </div>
  )}
</Bind>

Form

Component Props: { initial }
Render Props: { input }
see docs

<Form initial={{ subject: '', message: '' }}>
  {({ input }) => {
    const subject = input('subject')
    const message = input('message')

    return (
      <div>
        <ControlledInput
          placeholder="Subject"
          {...subject.bind}
        />
        <ControlledTextArea
          placeholder="Message"
          {...message.bind}
        />
        <Submit>Send</Submit>

        {/*
          input(id) => { bind, setValue, value }
        */}
      </div>
    )
  }
</Form>

Composing Components

If you want to merge two of more components functionalities, you can compose they in a single one.
For complete guide see Compose docs

import { compose } from 'react-powerplug'

const ToggleCounter = compose(Toggle, Counter)

<ToggleCounter>
  {({ count, inc, dec, on, toggle }) => (
    <ProductCard
      {...productInfo}
      isFavorited={on}
      onFavorite={toggle}
      count={count}
      onAdd={inc}
      onRemove={dec}
    />
  )}
</ToggleCounter>

If you need to pass props, especially for initial, just pass a created element. Internals this will be cloned.

const ToggleCounter = compose(<Toggle initial={false} />, <Counter initial={2} />)

// or just mix it
const ToggleCounter = compose(Toggle, <Counter initial={3} />)

Also, you can use a built-in Compose component and pass components on states prop

import { Compose } from 'react-powerplug'

<Compose states={[Toggle, Counter]}>
  {({ on, toggle, count, inc, dec }) => (
    <ProductCard {...} />
  )}
</Compose>

Behind the scenes, that's what happens:

<Counter /* passed props */>
  {({ count, inc, dec }) => (
    <Toggle /* passed props */>
      {({ on, toggle }) => (s
        <ProductCard
          {...productInfo}
          isFavorited={on}
          onFavorite={toggle}
          count={count}
          onAdd={inc}
          onRemove={dec}
        />
      )}
    </Toggle>
  )}
</Counter>

Because of that, when you use toggle function, only <Toggle> will be rerendered, but if you use inc or dec functions, both <Counter> and <Toggle> will be rerendered. Even using compose() utility.

CodeSandbox examples

Using Toggle in a Checkbox
Using State in a Pagination

Install

Node Module

yarn add react-powerplug
npm i react-powerplug

UMD library

<script src="https://unpkg.com/react-powerplug/dist/react-powerplug.min.js"></script>

exposed as ReactPowerPlug

Contribute

You can help improving this project sending PRs and helping with issues.
Also you can ping me at Twitter

About

🔌 3kB gzipped Renderless Pluggable Containers

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript 98.6%
  • CSS 1.4%