Skip to content

Commit

Permalink
@lizozom review 2
Browse files Browse the repository at this point in the history
  • Loading branch information
Dosant committed Feb 6, 2020
1 parent f89c652 commit 94a9159
Show file tree
Hide file tree
Showing 5 changed files with 27 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ It is up to the application to handle such scenarios.
Consider the following example:

```ts
// window.location.href is "/#?_a=(count:0)"
// window.location.href is "/#?_a=(count:0"
const defaultState = { count: 0 }; // default application state

const stateContainer = createStateContainer(defaultState);
Expand All @@ -24,15 +24,15 @@ const { start, stop } = syncState({

start();

// on this point state in the storage and in the state is out of sync
// At this point, state and storage are in sync
// state: {count: 0}
// storage: {count: 0}

// And now user changed the URL to (let's assume) "/#?_a=(corrupt:0)"
// on this point state will recieve an update: {corrupt: 0}
// Now user changes the URL manually to "/#?_a=(corrupt:0)",
// triggering a state update with {corrupt: 0}
```

Application could handle this gracefully by using simple composition during setup:
The application could, for example, handle this gracefully, by using simple composition during setup:

```ts
const { start, stop } = syncState({
Expand All @@ -45,9 +45,9 @@ const { start, stop } = syncState({
});
```

In this case, app will not get into state, which is not shaped as app expects.
In this case, the corrupt value will not get into state, preventing misshaped state.

To help application developers to not forget about such edge cases,
To help application developers remember such edge cases,
`syncState` util sets a constraint,
that setter to state container should be able to handle `null` value (see [IStateSyncConfig](../../public/state_sync/types.ts)).
Incoming `null` value from state storage usually means that state is empty (e.g. URL without `storageKey` query param).
Expand Down
20 changes: 10 additions & 10 deletions src/plugins/kibana_utils/docs/state_sync/initial_state.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Setting up initial state

The `syncState` util doesn't do any initial state syncing between state and storage.
Consider the scenario:
Consider the following scenario:

```ts
// window.location.href is "/#?_a=(count:2)"
Expand All @@ -18,23 +18,23 @@ const { start, stop } = syncState({

start();

// on this point state in the storage and in the state is out of sync
// Now the storage and state are out of sync
// state: {count: 0}
// storage: {count: 2}
```

The `syncState` doesn't make a decision, how initial state should be synced and which state should take precedence.
It is up to the application to decide, depending on the specific use case.
It is up to the application to decide, how initial state should be synced and which state should take precedence, depending on the specific use case.

Questions to consider:

1. Should default state take precedence over URL?
2. Should URL take precedence?
3. Do we have to do any state migrations for what is coming from the URL?
4. If URL doesn't have the whole state, should we merge it with default one or leave it behind?
5. Is there any other state loading in parallel (e.g. from a `SavedObject`)? How should we merge it all together?
5. Is there any other state loading in parallel (e.g. from a `SavedObject`)? How should we merge those?
6. Are we storing the state both in the URL and in the `sessionStorage`? Which one should take precedence and in which case?

So if initial state syncing is required, for simple example above it could look like this:
A possible synchronization for the state conflict above could look like this:

```ts
// window.location.href is "/#?_a=(count:2)"
Expand All @@ -44,17 +44,17 @@ const urlStateStorage = createKbnUrlStateStorage();

const initialStateFromUrl = urlStateStorage.get('_a');

// merge together default state and initial state
// merge the default state and initial state from the url and use it as initial application state
const initialState = {
...defaultState,
...initialStateFromUrl,
};

const stateContainer = createStateContainer(initialState);

// preserve initial application state in the URL
if (!initialStateFromUrl) {
// for consistency put default state to the url
urlStateStorage.set('_a', defaultState, { replace: true });
urlStateStorage.set('_a', initialState, { replace: true });
}

const { start, stop } = syncState({
Expand All @@ -65,7 +65,7 @@ const { start, stop } = syncState({

start();

// on this point state in the storage and in state are synced
// Now the storage and state are in sync
// state: {count: 2}
// storage: {count: 2}
```
11 changes: 8 additions & 3 deletions src/plugins/kibana_utils/docs/state_sync/no_state_containers.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Using state syncing utilities without state containers

It is possible to use `syncState` utility even if your app is not using [state containers](../state_containers).
The `state` which is passed into `syncState` function should just implement this simple interface:
The `state` which is passed into `syncState` function should implement this simple interface:

```ts
export interface BaseStateContainer<State extends BaseState> {
Expand All @@ -11,13 +11,14 @@ export interface BaseStateContainer<State extends BaseState> {
}
```

For example, assuming you have a custom state manager, setting up syncing state with URL could look something like this:
For example, assuming you have a custom state manager, setting up syncing state with the URL could look something like this:

```ts
// my_state_manager.ts
import { Subject } from 'rxjs';
import { map } from 'rxjs/operators';

class MyStateManager {
export class MyStateManager {
private count: number = 0;

updated$ = new Subject();
Expand All @@ -33,8 +34,12 @@ class MyStateManager {
return this.count;
}
}
```

```ts
// app.ts
import { syncState, createKbnUrlStateStorage } from 'src/plugins/kibana_utils/public';
import { MyStateManager } from './my_state_manager';

const myStateManager = new MyStateManager();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ Two types of storage compatible with `syncState`:

- [KbnUrlStateStorage](./kbn_url_storage.md) - Serialises state and persists it to URL's query param in rison or hashed format (similar to what AppState & GlobalState did in legacy world).
Listens for state updates in the URL and pushes updates back to state.
- [SessionStorageStateStorage](./session_storage.md) - Serialises state and persists it to session storage.
- [SessionStorageStateStorage](./session_storage.md) - Serializes state and persists it to session storage.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
`KbnUrlStateStorage` is a state storage for `syncState` utility which:

- Keeps state in sync with the URL.
- Serializes data and stores it in the URL in 2 different formats:
- Serializes data and stores it in the URL in one of the supported formats:
1. [Rison](https://github.com/w33ble/rison-node) encoded.
2. Hashed URL: In URL we store only the hash from the serialized state, but the state itself is stored in `sessionStorage`.
See kibana's advanced option for more context `state:storeInSessionStorage`
Expand Down

0 comments on commit 94a9159

Please sign in to comment.