/* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the "Elastic License * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side * Public License v 1"; you may not use this file except in compliance with, at * your election, the "Elastic License 2.0", the "GNU Affero General Public * License v3.0 only", or the "Server Side Public License, v 1". */ import React, { useEffect, useMemo } from 'react'; import { Link, useLocation } from 'react-router-dom'; import { Router, Routes, Route } from '@kbn/shared-ux-router'; import { History } from 'history'; import { EuiButton, EuiCheckbox, EuiFieldText, EuiPageBody, EuiPageTemplate, EuiPageSection, EuiPageHeader, EuiPageHeaderSection, EuiSpacer, EuiText, EuiTitle, } from '@elastic/eui'; import { BaseState, BaseStateContainer, createKbnUrlStateStorage, createStateContainer, getStateFromKbnUrl, INullableBaseStateContainer, StateContainer, syncState, useContainerSelector, } from '@kbn/kibana-utils-plugin/public'; import { defaultState, pureTransitions, TodoActions, TodoState, } from '@kbn/kibana-utils-plugin/demos/state_containers/todomvc'; interface TodoAppProps { filter: 'completed' | 'not-completed' | null; stateContainer: StateContainer; } const TodoApp: React.FC = ({ filter, stateContainer }) => { const { edit: editTodo, delete: deleteTodo, add: addTodo } = stateContainer.transitions; const todos = useContainerSelector(stateContainer, (state) => state.todos); const filteredTodos = useMemo( () => todos.filter((todo) => { if (!filter) return true; if (filter === 'completed') return todo.completed; if (filter === 'not-completed') return !todo.completed; return true; }), [todos, filter] ); const location = useLocation(); return ( <>
All Completed Not Completed
{ const inputRef = (e.target as HTMLFormElement).elements.namedItem( 'newTodo' ) as HTMLInputElement; if (!inputRef || !inputRef.value) return; addTodo({ text: inputRef.value, completed: false, id: todos.map((todo) => todo.id).reduce((a, b) => Math.max(a, b), 0) + 1, }); inputRef.value = ''; e.preventDefault(); }} > ); }; export const TodoAppPage: React.FC<{ history: History; appTitle: string; appBasePath: string; }> = (props) => { const initialAppUrl = React.useRef(window.location.href); const stateContainer = React.useMemo( () => createStateContainer(defaultState, pureTransitions), [] ); // Most of kibana apps persist state in the URL in two ways: // * Rison encoded. // * Hashed URL: In the URL only the hash from the state is stored. The state itself is stored in // the sessionStorage. See `state:storeInSessionStorage` advanced option for more context. // This example shows how to use both of them const [useHashedUrl, setUseHashedUrl] = React.useState(false); useEffect(() => { // storage to sync our app state with // in this case we want to sync state with query params in the URL serialised in rison format // similar like Discover or Dashboard apps do const kbnUrlStateStorage = createKbnUrlStateStorage({ useHash: useHashedUrl, history: props.history, }); // key to store state in the storage. In this case in the key of the query param in the URL const appStateKey = `_todo`; // take care of initial state. Make sure state in memory is the same as in the URL before starting any syncing const initialAppState: TodoState = getStateFromKbnUrl(appStateKey, initialAppUrl.current) || kbnUrlStateStorage.get(appStateKey) || defaultState; stateContainer.set(initialAppState); kbnUrlStateStorage.set(appStateKey, initialAppState, { replace: true }); // start syncing state between state container and the URL const { stop, start } = syncState({ stateContainer: withDefaultState(stateContainer, defaultState), storageKey: appStateKey, stateStorage: kbnUrlStateStorage, }); start(); return () => { stop(); }; }, [stateContainer, props.history, useHashedUrl]); return (

{props.appTitle}

This is a simple TODO app that uses state containers and state syncing utils. It stores state in the URL similar like Discover or Dashboard apps do.
Play with the app and see how the state is persisted in the URL.
Undo/Redo with browser history also works.

Most of kibana apps persist state in the URL in two ways:

  1. Expanded state in rison format
  2. Just a state hash.
    In the URL only the hash from the state is stored. The state itself is stored in the sessionStorage. See `state:storeInSessionStorage` advanced option for more context.

You can switch between these two mods:

setUseHashedUrl(!useHashedUrl)}> {useHashedUrl ? 'Use Expanded State' : 'Use Hashed State'}
); }; function withDefaultState( stateContainer: BaseStateContainer, // eslint-disable-next-line @typescript-eslint/no-shadow defaultState: State ): INullableBaseStateContainer { return { ...stateContainer, set: (state: State | null) => { stateContainer.set({ ...defaultState, ...state, }); }, }; }