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. 1

  2. The modern Web Development World is becoming more and more

    complex. Who suffers from it? 9 @evilmartians
  3. We need a lot of actions to make something simple.

    12 But the most crucial: @evilmartians
  4. 16 The more complex and confusing a code, the more

    likely a user is to encounter bugs @evilmartians
  5. 18 Sometimes, to do the same amount of work, more

    programmers are needed. Now Before @evilmartians
  6. 20 Result: the growing complexity of Web Development makes everyone

    frustrated. Users Developers Business @evilmartians
  7. 21 Simplicity is a key What can we do for

    the first step? @evilmartians
  8. Fold knowledge into data, so program logic can be stupid

    and robust. 23 Eric Raymond, “Rule of Representation” @evilmartians
  9. 27

  10. We also can change our model and fold knowledge into

    data, and not the framework. 29 @evilmartians
  11. 32 Use stores to move logic outside of components Component

    Component Component Store Store @evilmartians
  12. Bad programmers worry about the code. Good programmers worry about

    data structures and their relationships. 34 Linus Torvalds @evilmartians
  13. 35 Global State Any State Change All Components Call the

    selector function Popular global state approach: @evilmartians
  14. 38 Applying to State Management: Component Component Component Subscriber 1

    Subscriber 2 Subscriber 3 Small Store Small Store Small Store @evilmartians
  15. 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
  16. 1. Move logic to stores outside of component 2. Make

    stores small and smart Let's go further! 43 @evilmartians
  17. 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
  18. Stores can be smart. But the process should be simple

    and minimalistic. 47 @evilmartians
  19. 1. Move logic to stores outside of components 2. Make

    stores small and smart 3. Provide minimalism and simplicity One more thing! 48 @evilmartians
  20. Everything should be made as simple as possible, but no

    simpler. 49 Albert Einstein @evilmartians
  21. 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
  22. 1. Move logic to stores outside of components. 55 Component

    Component Component Store Store @evilmartians
  23. 3. Provide minimalism and simplicity. 57 Store One-line operations: •

    Create store • Get value • Set value Get value Set value @evilmartians
  24. 4. Consider developer challenges. 58 SPA Navigation Smart data fetching

    Automatic Translations Conflict resolution Synchronizing @evilmartians
  25. Creating atom store for products in catalog: // core/state.ts import

    { atom } from 'nanostores'; export const $products = atom<Product[]>([ { name: 'Nano Phone', price: 30 }, { name: 'Nano TV', price: 75 }, { name: 'Nano Laptop' , price: 75 } ]); 64 @evilmartians
  26. 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) => <Card product={product} />)}</>; }; 65 @evilmartians
  27. Creating atom store for search input: // core/state.ts import {

    atom } from 'nanostores'; export const $searchInput = atom<string>(''); 68 @evilmartians
  28. 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 <input onChange={(e) => $searchInput.set(e.target.value) } value={searchInput} /> ; }; 69 @evilmartians
  29. 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
  30. 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) => <Card product={product} />)}</>; }; 71 @evilmartians
  31. 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
  32. Create Fetcher Store using context: // core/state.ts import { createFetcherStore

    } from 'core/fetcher' ; export const $fetchedProducts = createFetcherStore (['products']); 75 @evilmartians
  33. 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 <Spinner/>; if (error) return <>Error!</>; return <>{data.content. map((product) => <Card product={product} />)}</>; }; 76 @evilmartians
  34. 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
  35. Create router store: // core/router.ts import { createRouter } from

    '@nanostores/router'; export const $router = createRouter({ catalog: '/catalog', cart: '/cart', ... }); 82 @evilmartians
  36. Using router store: // components/Router.tsx import { $router } from

    'core/router' ; const Router = () => { const router = useStore($router); switch (router?.route) { case 'catalog': return <CatalogPage />; case 'cart': return <CartPage />; ... } } 83 @evilmartians
  37. 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
  38. 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 <header>{headerName}</header>; }; 85 @evilmartians
  39. Keep cart items in the LocalStorage key: import { persistentAtom

    } from '@nanostores/persistent'; export const $shoppingCart = persistentAtom<Product[]>('cart', [], { encode: JSON.stringify, decode: JSON.parse, }); 89 @evilmartians
  40. Change store value: // Adding newProduct to Persistent Map store:

    export const addProductToCart = (newProduct: Product) => { $shoppingCart .set([...$shoppingCart .get(), newProduct]); }; 90 @evilmartians
  41. 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
  42. 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