Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Smart nano stores for state management, or how we made front-end simpler

Smart nano stores for state management, or how we made front-end simpler

Nina Torgunakova

October 31, 2023
Tweet

More Decks by Nina Torgunakova

Other Decks in Programming

Transcript

  1. Smart nano stores
    for state management
    2
    @evilmartians
    or how we made front-end simpler

    View full-size slide

  2. Nina Torgunakova
    Frontend Engineer
    3
    @evilmartians

    View full-size slide

  3. 4
    Simplicity of HTML + CSS + JS
    @evilmartians

    View full-size slide

  4. 5
    What we had before
    @evilmartians

    View full-size slide

  5. 6
    @evilmartians

    View full-size slide

  6. 7
    What we have today
    * Source: www.medium.com/@withinsight1/the-front-end-spectrum-c0f30998c9f0/
    @evilmartians

    View full-size slide

  7. 8
    @evilmartians

    View full-size slide

  8. The modern Web Development World is
    becoming more and more complex.
    Who suffers from it?
    9
    @evilmartians

    View full-size slide

  9. 1. Developers
    10
    @evilmartians

    View full-size slide

  10. 11
    Everything is built around
    frameworks but remains complex
    @evilmartians

    View full-size slide

  11. We need a lot of actions
    to make something simple.
    12
    But the most crucial:
    @evilmartians

    View full-size slide

  12. 2. Users
    13
    @evilmartians

    View full-size slide

  13. 14
    * Source: www.keycdn.com/support/the-growth-of-web-page-size
    @evilmartians

    View full-size slide

  14. 15
    * Source: www.hobo-web.co.uk/your-website-design-should-load-in-4-seconds/
    @evilmartians

    View full-size slide

  15. 16
    The more complex and confusing
    a code, the more likely a user
    is to encounter bugs
    @evilmartians

    View full-size slide

  16. 3. Business
    17
    @evilmartians

    View full-size slide

  17. 18
    Sometimes, to do the same amount of
    work, more programmers are needed.
    Now
    Before
    @evilmartians

    View full-size slide

  18. 19
    As a result, both time
    and money are wasted.
    @evilmartians

    View full-size slide

  19. 20
    Result: the growing complexity
    of Web Development makes everyone
    frustrated.
    Users Developers Business
    @evilmartians

    View full-size slide

  20. 21
    Simplicity is a key
    What can we do for the first step?
    @evilmartians

    View full-size slide

  21. 22
    Let's try to find
    inspiration!
    @evilmartians

    View full-size slide

  22. Fold knowledge into data,
    so program logic can be
    stupid and robust.
    23
    Eric Raymond,
    “Rule of Representation”
    @evilmartians

    View full-size slide

  23. Now development revolves around frameworks
    24
    @evilmartians

    View full-size slide

  24. 25
    Framework
    Logic UI
    Features
    Data
    @evilmartians

    View full-size slide

  25. But what if we made a mistake?
    26
    @evilmartians

    View full-size slide

  26. We also can change our model and fold
    knowledge into data, and not the framework.
    29
    @evilmartians

    View full-size slide

  27. 30
    Data
    Logic
    UI
    Features
    Framework
    Logic
    UI
    Features
    Data
    Framework
    @evilmartians

    View full-size slide

  28. State Management
    31
    Application
    Data State
    @evilmartians

    View full-size slide

  29. 32
    Use stores to move logic outside of components
    Component Component
    Component
    Store Store
    @evilmartians

    View full-size slide

  30. OK, any other ideas about
    the ideal process of state
    management?
    33
    @evilmartians

    View full-size slide

  31. Bad programmers worry about
    the code.
    Good programmers worry about
    data structures and their
    relationships.
    34
    Linus Torvalds
    @evilmartians

    View full-size slide

  32. 35
    Global State Any State Change
    All
    Components
    Call the selector function
    Popular global state approach:
    @evilmartians

    View full-size slide

  33. 36
    How about improving data structure
    and making it faster?
    @evilmartians

    View full-size slide

  34. 37
    Reactive programming approach:
    @evilmartians

    View full-size slide

  35. 38
    Applying to State Management:
    Component
    Component
    Component
    Subscriber 1
    Subscriber 2
    Subscriber 3
    Small
    Store
    Small
    Store
    Small
    Store
    @evilmartians

    View full-size slide

  36. 39
    Small
    Store
    State Change
    Subscribed
    components
    Receive
    changed value
    Small
    Store
    State Change
    Subscribed
    components
    Receive
    changed value
    Only subscribed components receive changes
    from needed stores:
    @evilmartians

    View full-size slide

  37. 40
    But what if some data
    depends on other data?
    @evilmartians

    View full-size slide

  38. 41
    Solution: Relationships between stores
    Store
    Store Store
    Store
    @evilmartians

    View full-size slide

  39. 42
    Chains of reactive computations
    Component
    Component
    Store
    Store
    Store
    @evilmartians

    View full-size slide

  40. 1. Move logic to stores outside of component
    2. Make stores small and smart
    Let's go further!
    43
    @evilmartians

    View full-size slide

  41. Perfection is achieved,
    not when there is nothing
    more to add, but when
    there is nothing left
    to take away.
    44
    Antoine de Saint-Exupéry
    @evilmartians

    View full-size slide

  42. 45
    State Management in modern frameworks:
    @evilmartians

    View full-size slide

  43. 46
    Smart
    Store
    Set value Get value
    @evilmartians

    View full-size slide

  44. Stores can be smart.
    But the process should be simple
    and minimalistic.
    47
    @evilmartians

    View full-size slide

  45. 1. Move logic to stores outside of components
    2. Make stores small and smart
    3. Provide minimalism and simplicity
    One more thing!
    48
    @evilmartians

    View full-size slide

  46. Everything should be made
    as simple as possible,
    but no simpler.
    49
    Albert Einstein
    @evilmartians

    View full-size slide

  47. We want some simple way;
    but not oversimplified.
    50
    @evilmartians

    View full-size slide

  48. We need to consider many different challenges:
    - Synchronizing changes between browser tabs
    - Implementing SPA navigation
    - Automatic translation of content
    - Conflict resolution
    …and more…
    51
    @evilmartians

    View full-size slide

  49. No mental overload for hard cases
    52
    No boilerplate for easy cases
    @evilmartians

    View full-size slide

  50. 53
    Making complex things simple to understand
    @evilmartians

    View full-size slide

  51. 54
    Let's summarize!
    @evilmartians

    View full-size slide

  52. 1. Move logic to stores outside of components.
    55
    Component Component
    Component
    Store Store
    @evilmartians

    View full-size slide

  53. 2. Make stores small and smart.
    56
    Store
    Store
    Store
    Store
    Store
    @evilmartians

    View full-size slide

  54. 3. Provide minimalism and simplicity.
    57
    Store
    One-line operations:
    ● Create store
    ● Get value
    ● Set value
    Get value
    Set value
    @evilmartians

    View full-size slide

  55. 4. Consider developer challenges.
    58
    SPA Navigation
    Smart data
    fetching
    Automatic
    Translations
    Conflict
    resolution
    Synchronizing
    @evilmartians

    View full-size slide

  56. 59
    ???
    What state manager
    will solve all the
    problems?
    @evilmartians

    View full-size slide

  57. 60
    Nano Stores
    @evilmartians

    View full-size slide

  58. Let's build a simple online shop:
    61
    @evilmartians

    View full-size slide

  59. 62
    @evilmartians

    View full-size slide

  60. 63
    @evilmartians

    View full-size slide

  61. Creating atom store for products in catalog:
    // core/state.ts
    import { atom } from 'nanostores';
    export const $products = atom([
    { name: 'Nano Phone', price: 30 },
    { name: 'Nano TV', price: 75 },
    { name: 'Nano Laptop' , price: 75 }
    ]);
    64
    @evilmartians

    View full-size slide

  62. Using atom store:
    // components/Catalog.tsx
    import { $products } from 'core/state';
    import { useStore } from '@nanostores/react' ;
    const Catalog = () => {
    const products = useStore($products);
    return <>{products.map((product) => )}>;
    };
    65
    @evilmartians

    View full-size slide

  63. 66
    @evilmartians

    View full-size slide

  64. Adding filtering
    using search input
    67
    @evilmartians

    View full-size slide

  65. Creating atom store for search input:
    // core/state.ts
    import { atom } from 'nanostores';
    export const $searchInput = atom('');
    68
    @evilmartians

    View full-size slide

  66. Using atom store for search input:
    // components/SearchBar.tsx
    import { $searchInput } from 'core/state';
    import { useStore } from '@nanostores/react'
    ;
    const SearchBar = () => {
    const searchInput = useStore($searchInput);
    return $searchInput.set(e.target.value)
    } value={searchInput} />
    ;
    };
    69
    @evilmartians

    View full-size slide

  67. Adding computed store to filter catalog:
    // core/state.ts
    import { computed } from 'nanostores';
    const $filteredProducts = computed(
    [$searchInput, $products], ( searchInput, products) => {
    return products.filter(product => product.name.includes(searchInput));
    }
    );
    70
    @evilmartians

    View full-size slide

  68. Using computed store in components:
    // components/Catalog.tsx
    import { useStore } from '@nanostores/react' ;
    import { $filteredProducts } from 'core/state';
    // Usage is exactly the same:
    const Catalog = () => {
    const filteredProducts = useStore($filteredProducts);
    return <>{filteredProducts. map((product) => )}>;
    };
    71
    @evilmartians

    View full-size slide

  69. 72
    @evilmartians

    View full-size slide

  70. 73
    Nano Stores Query
    A tiny data fetcher for Nano Stores.
    @evilmartians

    View full-size slide

  71. Define Fetcher Context:
    // core/fetcher.ts
    import { nanoquery } from '@nanostores/query' ;
    export const [createFetcherStore ] = nanoquery({
    fetcher: (...keys: string[]) => fetch(keys.join('')).then((r) => r.json()),
    });
    74
    @evilmartians

    View full-size slide

  72. Create Fetcher Store using context:
    // core/state.ts
    import { createFetcherStore } from 'core/fetcher' ;
    export const $fetchedProducts = createFetcherStore (['products']);
    75
    @evilmartians

    View full-size slide

  73. Using Fetcher Store:
    // components/Catalog.tsx
    import { useStore } from '@nanostores/react' ;
    import { $fetchedProducts } from 'core/state';
    const Catalog = () => {
    const { data, error, loading } = useStore($fetchedProducts);
    if (loading) return ;
    if (error) return <>Error!>;
    return <>{data.content. map((product) => )}>;
    };
    76
    @evilmartians

    View full-size slide

  74. 77
    @evilmartians

    View full-size slide

  75. 78
    @evilmartians

    View full-size slide

  76. More customization for fetching:
    // core/fetcher.ts
    const [createFetcherStore ] = nanoquery({
    fetcher: (...keys: string[]) => fetch(keys.join('')).then((r) => r.json()),
    refetchInterval : 30000, // revalidate cache on an interval, ms
    refetchOnFocus : true, // revalidate cache when the window focuses
    refetchOnReconnect : true, // revalidate cache when network connection restores
    });
    79
    @evilmartians

    View full-size slide

  77. 80
    @evilmartians

    View full-size slide

  78. 81
    Nano Stores Router
    A tiny URL router for Nano Stores.
    @evilmartians

    View full-size slide

  79. Create router store:
    // core/router.ts
    import { createRouter } from '@nanostores/router';
    export const $router = createRouter({
    catalog: '/catalog',
    cart: '/cart',
    ...
    });
    82
    @evilmartians

    View full-size slide

  80. Using router store:
    // components/Router.tsx
    import { $router } from 'core/router' ;
    const Router = () => {
    const router = useStore($router);
    switch (router?.route) {
    case 'catalog':
    return ;
    case 'cart':
    return ;
    ...
    }
    }
    83
    @evilmartians

    View full-size slide

  81. Subscribing to store's changes:
    // core/router.ts
    import { atom } from 'nanostores' ;
    const $headerName = atom('');
    $router.subscribe((newRouterValue ) => {
    let header = 'Nano Shop' ;
    switch (newRouterValue ?.route) {
    case 'catalog':
    header = 'Nano Shop Catalog';
    case 'cart':
    header = 'Nano Shop Cart'; ...
    }
    $headerName .set(header);
    document.title = header;
    });
    84
    @evilmartians

    View full-size slide

  82. Using current value in component:
    // components/Header.tsx
    import { useStore } from '@nanostores/react' ;
    import { $headerName } from 'core/router' ;
    export const Header = () => {
    const headerName = useStore($headerName);
    return {headerName};
    };
    85
    @evilmartians

    View full-size slide

  83. 86
    @evilmartians

    View full-size slide

  84. 87
    @evilmartians

    View full-size slide

  85. 88
    Nano Stores Persistent
    A tiny store for local storage.
    @evilmartians

    View full-size slide

  86. Keep cart items in the LocalStorage key:
    import { persistentAtom } from '@nanostores/persistent';
    export const $shoppingCart = persistentAtom('cart', [], {
    encode: JSON.stringify,
    decode: JSON.parse,
    });
    89
    @evilmartians

    View full-size slide

  87. Change store value:
    // Adding newProduct to Persistent Map store:
    export const addProductToCart = (newProduct: Product) => {
    $shoppingCart .set([...$shoppingCart .get(), newProduct]);
    };
    90
    @evilmartians

    View full-size slide

  88. 91
    @evilmartians

    View full-size slide

  89. 92
    Store changes are synchronized
    between browser tabs by default:
    @evilmartians

    View full-size slide

  90. 93
    Testing Nano Stores
    @evilmartians

    View full-size slide

  91. afterEach(() => {
    $shoppingCart .set([]);
    });
    it("should add product to cart" , () => {
    $shoppingCart .listen(() => {});
    addProductToCart ({ name: "Nano Tablet" , price: 50 });
    expect($shoppingCart .get().length).toEqual(1);
    });
    94
    No need to emulate DOM:
    @evilmartians

    View full-size slide

  92. 95
    Nano Stores atom code
    in 84 lines:
    @evilmartians

    View full-size slide

  93. So, what is next?
    1. Try out a new approach to state
    management in your own project.
    2. See the power of simplicity!
    96
    github.com/nanostores
    @evilmartians

    View full-size slide

  94. 97
    Questions?
    @evilmartians

    View full-size slide