Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

State containers #52384

Merged
merged 13 commits into from
Dec 6, 2019
Prev Previous commit
Next Next commit
docs: ✏️ refrech state container docs
  • Loading branch information
streamich committed Dec 6, 2019
commit 1c8a80333367f45a4004efbd2f6441c544d1ae9f
2 changes: 1 addition & 1 deletion src/plugins/kibana_utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

Utilities for building Kibana plugins.

- [Store reactive serializable app state in state containers, `createStore`](./docs/store/README.md).
- [State containers](./docs/state_containers/README.md).
50 changes: 50 additions & 0 deletions src/plugins/kibana_utils/docs/state_containers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# State containers
Dosant marked this conversation as resolved.
Show resolved Hide resolved

State containers are Redux-store-like objects meant to help you manage state in
your services or apps.

- State containers are strongly typed, you will get TypeScript autocompletion suggestions from
your editor when accessing state, executing transitions and using React helpers.
- State containers can be easily hooked up with your React components.
- State containers can be used without React, too.
- State containers provide you central place where to store state, instead of spreading
state around multiple RxJs observables, which you need to coordinate. With state
container you can always access the latest state snapshot synchronously.
- Unlike Redux, state containers are less verbose, see example below.


## Example

```ts
import { createStateContainer } from 'src/plugins/kibana_utils';

const container = createStateContainer(0, {
increment: (cnt: number) => (by: number) => cnt + by,
double: (cnt: number) => () => cnt * 2,
});

container.transitions.increment(5);
container.transitions.double();
console.log(container.get()); // 10
```


## Demos

See demos [here](../../demos/state_containers/).

You can run them with

```
ts-node src/plugins/kibana_utils/demos/state_containers/counter.ts
ts-node src/plugins/kibana_utils/demos/state_containers/todomvc.ts
```


## Reference

- [Creating a state container](./creation.md).
- [State transitions](./transitions.md).
- [Using with React](./react.md).
- [Using without React`](./no_react.md).
- [Parallels with Redux](./redux.md).
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface MyState {
}
```

Create default state of your *store*.
Create default state of your container.

```ts
const defaultState: MyState = {
Expand All @@ -27,17 +27,12 @@ const defaultState: MyState = {
};
```

Create your state container, i.e *store*.
Create your a state container.

```ts
import { createStore } from 'kibana-utils';
import { createStateContainer } from 'src/plugins/kibana_utils';

const store = createStore<MyState>(defaultState);
console.log(store.get());
```
const container = createStateContainer<MyState>(defaultState, {});

> ##### N.B.
>
> State must always be an object `{}`.
>
> You cannot create a store out of an array, e.g ~~`createStore([])`~~.
console.log(container.get());
```
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Reading state
# Consuming state in non-React setting

To read the current `state` of the store use `.get()` method.

Expand Down
41 changes: 41 additions & 0 deletions src/plugins/kibana_utils/docs/state_containers/react.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# React

`createStateContainerReactHelpers` factory allows you to easily use state containers with React.


## Example


```ts
import { createStateContainer, createStateContainerReactHelpers } from 'src/plugins/kibana_utils';

const container = createStateContainer({}, {});
export const {
Provider,
Consumer,
context,
useContainer,
useState,
useTransitions,
useSelector,
connect,
} = createStateContainerReactHelpers<typeof container>();
```

Wrap your app with `<Provider>`.

```tsx
<Provider value={container}>
<MyApplication />
</Provider>
```


## Reference

- [`useContainer()`](./react/use_container.md)
- [`useState()`](./react/use_state.md)
- [`useSelector()`](./react/use_selector.md)
- [`useTransitions()`](./react/use_transitions.md)
- [`connect()()`](./react/connect.md)
- [Context](./react/context.md)
22 changes: 22 additions & 0 deletions src/plugins/kibana_utils/docs/state_containers/react/connect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# `connect()()` higher order component

Use `connect()()` higher-order-component to inject props from state into your component.

```tsx
interface Props {
name: string;
punctuation: '.' | ',' | '!',
}
const Demo: React.FC<Props> = ({ name, punctuation }) =>
<div>Hello, {name}{punctuation}</div>;

const store = createStateContainer({ userName: 'John' });
const { Provider, connect } = createStateContainerReactHelpers(store);

const mapStateToProps = ({ userName }) => ({ name: userName });
const DemoConnected = connect<Props, 'name'>(mapStateToProps)(Demo);

<Provider>
<DemoConnected punctuation="!" />
</Provider>
```
24 changes: 24 additions & 0 deletions src/plugins/kibana_utils/docs/state_containers/react/context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# React context

`createStateContainerReactHelpers` returns `<Provider>` and `<Consumer>` components
as well as `context` React context object.

```ts
export const {
Provider,
Consumer,
context,
} = createStateContainerReactHelpers<typeof container>();
```

`<Provider>` and `<Consumer>` are just regular React context components.

```tsx
<Provider value={container}>
<div>
<Consumer>{container =>
<pre>{JSON.stringify(container.get())}</pre>
}</Consumer>
</div>
</Provider>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# `useContainer` hook

`useContainer` React hook will simply return you `container` object from React context.

```tsx
const Demo = () => {
const store = useContainer();
return <div>{store.get().isDarkMode ? '🌑' : '☀️'}</div>;
};
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# `useSelector()` hook

With `useSelector` React hook you specify a selector function, which will pick specific
data from the state. *Your component will update only when that specific part of the state changes.*

```tsx
const selector = state => state.isDarkMode;
const Demo = () => {
const isDarkMode = useSelector(selector);
return <div>{isDarkMode ? '🌑' : '☀️'}</div>;
};
```

As an optional second argument for `useSelector` you can provide a `comparator` function, which
compares currently selected value with the previous and your component will re-render only if
`comparator` returns `true`. By default it uses [`fast-deep-equal`](https://github.com/epoberezkin/fast-deep-equal).
streamich marked this conversation as resolved.
Show resolved Hide resolved

```
useSelector(selector, comparator?)
```
11 changes: 11 additions & 0 deletions src/plugins/kibana_utils/docs/state_containers/react/use_state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# `useState()` hook

- `useState` hook returns you directly the state of the container.
- It also forces component to re-render every time state changes.

```tsx
const Demo = () => {
const { isDarkMode } = useState();
return <div>{isDarkMode ? '🌑' : '☀️'}</div>;
};
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# `useTransitions` hook

Access [state transitions](../transitions.md) by `useTransitions` React hook.

```tsx
const Demo = () => {
const { isDarkMode } = useState();
const { setDarkMode } = useTransitions();
return (
<>
<div>{isDarkMode ? '🌑' : '☀️'}</div>
<button onClick={() => setDarkMode(true)}>Go dark</button>
<button onClick={() => setDarkMode(false)}>Go light</button>
</>
);
};
```
40 changes: 40 additions & 0 deletions src/plugins/kibana_utils/docs/state_containers/redux.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Redux

State containers similar to Redux stores but without the boilerplate.

State containers expose Redux-like API:

```js
container.getState()
container.dispatch()
container.replaceReducer()
container.subscribe()
container.addMiddleware()
```

State containers have a reducer and every time you execute a state transition it
actually dispatches an "action". For example, this

```js
container.transitions.increment(25);
```

is equivalent to

```js
container.dispatch({
type: 'increment',
args: [25],
});
```

Because all transitions happen through `.dispatch()` interface, you can add middleware&mdash;similar how you
would do with Redux&mdash;to monitor or intercept transitions.

For example, you can add `redux-logger` middleware to log in console all transitions happening with your store.

```js
import logger from 'redux-logger';

container.addMiddleware(logger);
```
61 changes: 61 additions & 0 deletions src/plugins/kibana_utils/docs/state_containers/transitions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# State transitions

*State transitions* describe possible state changes over time. Transitions are pure functions which
receive `state` object and other&mdash;optional&mdash;arguments and must return a new `state` object back.

```ts
type Transition = (state: State) => (...args) => State;
```

Transitions must not mutate `state` object in-place, instead they must return a
shallow copy of it, e.g. `{ ...state }`. Example:

```ts
const setUiMode: PureTransition = state => uiMode => ({ ...state, uiMode });
```

You provide transitions as a second argument when you create your state container.

```ts
import { createStateContainer } from 'src/plugins/kibana_utils';

const container = createStateContainer(0, {
increment: (cnt: number) => (by: number) => cnt + by,
double: (cnt: number) => () => cnt * 2,
});
```

Now you can execute the transitions by calling them with only optional parameters (`state` is
provided to your transitions automatically).

```ts
container.transitions.increment(25);
container.transitions.increment(5);
container.state; // 30
```

Your transitions are bound to the container so you can treat each of them as a
standalone function for export.

```ts
const defaultState = {
uiMode: 'light',
};

const container = createStateContainer(defaultState, {
setUiMode: state => uiMode => ({ ...state, uiMode }),
resetUiMode: state => () => ({ ...state, uiMode: defaultState.uiMode }),
});

export const {
setUiMode,
resetUiMode
} = container.transitions;
```

You can add TypeScript annotations for your transitions as the second generic argument
to `createStateContainer()` function.

```ts
const container = createStateContainer<MyState, MyTransitions>(defaultState, pureTransitions);
```
9 changes: 0 additions & 9 deletions src/plugins/kibana_utils/docs/store/README.md

This file was deleted.

Loading