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 Management] State containers improvements #54436

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions examples/state_containers_examples/public/todo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
PureTransition,
syncStates,
getStateFromKbnUrl,
BaseState,
} from '../../../src/plugins/kibana_utils/public';
import { useUrlTracker } from '../../../src/plugins/kibana_react/public';
import {
Expand Down Expand Up @@ -79,7 +80,7 @@ const TodoApp: React.FC<TodoAppProps> = ({ filter }) => {
const { setText } = GlobalStateHelpers.useTransitions();
const { text } = GlobalStateHelpers.useState();
const { edit: editTodo, delete: deleteTodo, add: addTodo } = useTransitions();
const todos = useState();
const todos = useState().todos;
const filteredTodos = todos.filter(todo => {
if (!filter) return true;
if (filter === 'completed') return todo.completed;
Expand Down Expand Up @@ -306,22 +307,18 @@ export const TodoAppPage: React.FC<{
);
};

function withDefaultState<State>(
function withDefaultState<State extends BaseState>(
stateContainer: BaseStateContainer<State>,
// eslint-disable-next-line no-shadow
defaultState: State
): INullableBaseStateContainer<State> {
return {
...stateContainer,
set: (state: State | null) => {
if (Array.isArray(defaultState)) {
stateContainer.set(state || defaultState);
} else {
stateContainer.set({
...defaultState,
...state,
});
}
stateContainer.set({
...defaultState,
...state,
});
},
};
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@
"custom-event-polyfill": "^0.3.0",
"d3": "3.5.17",
"d3-cloud": "1.2.5",
"deep-freeze-strict": "^1.1.1",
"deepmerge": "^4.2.2",
"del": "^5.1.0",
"elastic-apm-node": "^3.2.0",
Expand Down Expand Up @@ -314,6 +315,7 @@
"@types/classnames": "^2.2.9",
"@types/d3": "^3.5.43",
"@types/dedent": "^0.7.0",
"@types/deep-freeze-strict": "^1.1.0",
"@types/delete-empty": "^2.0.0",
"@types/elasticsearch": "^5.0.33",
"@types/enzyme": "^3.9.0",
Expand Down
8 changes: 8 additions & 0 deletions renovate.json5
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,14 @@
'@types/dedent',
],
},
{
groupSlug: 'deep-freeze-strict',
groupName: 'deep-freeze-strict related packages',
packageNames: [
'deep-freeze-strict',
'@types/deep-freeze-strict',
],
},
{
groupSlug: 'delete-empty',
groupName: 'delete-empty related packages',
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/kibana_utils/demos/demos.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('demos', () => {
describe('state sync', () => {
test('url sync demo works', async () => {
expect(await urlSyncResult).toMatchInlineSnapshot(
`"http://localhost/#?_s=!((completed:!f,id:0,text:'Learning%20state%20containers'),(completed:!f,id:2,text:test))"`
`"http://localhost/#?_s=(todos:!((completed:!f,id:0,text:'Learning%20state%20containers'),(completed:!f,id:2,text:test)))"`
);
});
});
Expand Down
22 changes: 16 additions & 6 deletions src/plugins/kibana_utils/demos/state_containers/counter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,24 @@

import { createStateContainer } from '../../public/state_containers';

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

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

container.transitions.increment(5);
container.transitions.double();

console.log(container.get()); // eslint-disable-line
console.log(container.selectors.count()); // eslint-disable-line

export const result = container.get();
export const result = container.selectors.count();
57 changes: 39 additions & 18 deletions src/plugins/kibana_utils/demos/state_containers/todomvc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,19 @@ export interface TodoItem {
id: number;
}

export type TodoState = TodoItem[];
export interface TodoState {
todos: TodoItem[];
}

export const defaultState: TodoState = [
{
id: 0,
text: 'Learning state containers',
completed: false,
},
];
export const defaultState: TodoState = {
todos: [
{
id: 0,
text: 'Learning state containers',
completed: false,
},
],
};

export interface TodoActions {
add: PureTransition<TodoState, [TodoItem]>;
Expand All @@ -44,17 +48,34 @@ export interface TodoActions {
clearCompleted: PureTransition<TodoState, []>;
}

export interface TodosSelectors {
todos: (state: TodoState) => () => TodoItem[];
todo: (state: TodoState) => (id: number) => TodoItem | null;
}

export const pureTransitions: TodoActions = {
add: state => todo => [...state, todo],
edit: state => todo => state.map(item => (item.id === todo.id ? { ...item, ...todo } : item)),
delete: state => id => state.filter(item => item.id !== id),
complete: state => id =>
state.map(item => (item.id === id ? { ...item, completed: true } : item)),
completeAll: state => () => state.map(item => ({ ...item, completed: true })),
clearCompleted: state => () => state.filter(({ completed }) => !completed),
add: state => todo => ({ todos: [...state.todos, todo] }),
edit: state => todo => ({
todos: state.todos.map(item => (item.id === todo.id ? { ...item, ...todo } : item)),
}),
delete: state => id => ({ todos: state.todos.filter(item => item.id !== id) }),
complete: state => id => ({
todos: state.todos.map(item => (item.id === id ? { ...item, completed: true } : item)),
}),
completeAll: state => () => ({ todos: state.todos.map(item => ({ ...item, completed: true })) }),
clearCompleted: state => () => ({ todos: state.todos.filter(({ completed }) => !completed) }),
};

export const pureSelectors: TodosSelectors = {
todos: state => () => state.todos,
todo: state => id => state.todos.find(todo => todo.id === id) ?? null,
};

const container = createStateContainer<TodoState, TodoActions>(defaultState, pureTransitions);
const container = createStateContainer<TodoState, TodoActions, TodosSelectors>(
defaultState,
pureTransitions,
pureSelectors
);

container.transitions.add({
id: 1,
Expand All @@ -64,6 +85,6 @@ container.transitions.add({
container.transitions.complete(0);
container.transitions.complete(1);

console.log(container.get()); // eslint-disable-line
console.log(container.selectors.todos()); // eslint-disable-line

export const result = container.get();
export const result = container.selectors.todos();
4 changes: 2 additions & 2 deletions src/plugins/kibana_utils/demos/state_sync/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/

import { defaultState, pureTransitions, TodoActions, TodoState } from '../state_containers/todomvc';
import { BaseStateContainer, createStateContainer } from '../../public/state_containers';
import { BaseState, BaseStateContainer, createStateContainer } from '../../public/state_containers';
import {
createKbnUrlStateStorage,
syncState,
Expand Down Expand Up @@ -55,7 +55,7 @@ export const result = Promise.resolve()
return window.location.href;
});

function withDefaultState<State>(
function withDefaultState<State extends BaseState>(
// eslint-disable-next-line no-shadow
stateContainer: BaseStateContainer<State>,
// eslint-disable-next-line no-shadow
Expand Down
17 changes: 12 additions & 5 deletions src/plugins/kibana_utils/docs/state_containers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,21 @@ your services or apps.
```ts
import { createStateContainer } from 'src/plugins/kibana_utils';

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

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

console.log(container.selectors.count()); // 10
```


Expand Down
2 changes: 1 addition & 1 deletion src/plugins/kibana_utils/docs/state_containers/creation.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Create your a state container.
```ts
import { createStateContainer } from 'src/plugins/kibana_utils';

const container = createStateContainer<MyState>(defaultState, {});
const container = createStateContainer<MyState>(defaultState);

console.log(container.get());
```
6 changes: 3 additions & 3 deletions src/plugins/kibana_utils/docs/state_containers/no_react.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Consuming state in non-React setting

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

```ts
store.get();
stateContainer.get();
```

To listen for latest state changes use `.state$` observable.

```ts
store.state$.subscribe(state => { ... });
stateContainer.state$.subscribe(state => { ... });
```
2 changes: 1 addition & 1 deletion src/plugins/kibana_utils/docs/state_containers/react.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
```ts
import { createStateContainer, createStateContainerReactHelpers } from 'src/plugins/kibana_utils';

const container = createStateContainer({}, {});
const container = createStateContainer({});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seemed to be reasonable to also make transitions optional. Just like selectors

export const {
Provider,
Consumer,
Expand Down
Loading