diff --git a/src/components/state-management/__snapshots__/StateManagementApp.test.tsx.snap b/src/components/state-management/__snapshots__/StateManagementApp.test.tsx.snap index e226ce2..09c80d5 100644 --- a/src/components/state-management/__snapshots__/StateManagementApp.test.tsx.snap +++ b/src/components/state-management/__snapshots__/StateManagementApp.test.tsx.snap @@ -8,2073 +8,2014 @@ exports[`renders correctly 1`] = `
-
-

- State Management in React -

+

+ State Management in React +

+ + +

+ What is State Management ? +

+ + +

+ + "State" is any data that describes the current behavior of an application. + + This could include values like "a list of objects fetched from the server", "which item is currently selected", "name of the currently logged-in user", and "is this modal open?". +

+ + +

+ + State + + allows you to create interactive and dynamic user interfaces. By managing the state of component, you can update the UI in response to user interactions, API calls, or other events. +

+ + +

+ Here are different types for state management that will be covered in our example: +

+ + +
    - +
  • + + Local State: + + : Data we manage in one or another component e.g handling inputs data in from. +
  • +
  • + + Server State : + + Data are fetched from the server via an API and cached on the client, there are a couple of great libraries that make data fetching in React a breeze such as: + + + React Query + + + . +
  • -
    -
    + + +

    + Let's break down the state management code example ( + + view demo + + ): +

    + + + + + + + +
    +
    +
    -
    +

    + Initializing local state for filters using useState hook +

    +

    + Here, we use the + + useState + + hook to manage the local state of our filters. This state will store the user's input for the movie title and category. +

    +
    +
    +

    + Use React Query to Manage Server State +

    +

    + The + + fetchData + + function builds the API URL with query parameters and fetches data from the server. +

    +

    + The + + useQuery + + hook from React Query fetches data based on the current filters and manages the server state, including loading and error states. +

    +
    +
    +

    + Error Handling +

    +

    + If an error occurs during data fetching, we display an error message. +

    +
    +
    +

    + Handle User Input: SearchField & Select +

    +

    + We use the + + SearchField + + component to allow the user to input a search query. The + + onChange + + handler updates the local state with the new search term, which triggers a re-fetch of the data. +

    +

    + The + + Select + + component allows the user to choose a category. The + + onChange + + handler updates the local state with the selected category, which also triggers a re-fetch of the data. +

    +
    +
    +

    + Loading State +

    +

    + While data is being fetched, we display a loading indicator. +

    +
    +
    -
    -
    -
    -
    -
    -
    + class="ch-frame-button ch-frame-button-left" + />
    -
    - - ServerStateExample.tsx -
    -
    + class="ch-frame-button-space" + />
    +
    +
    -
    -
    + + ServerStateExample.tsx +
    +
    +
    + +
    +
    +
    + - -
    -
    - - _ - 116 - -
    - - import { - -
    -
    -
    - - _ - 116 - -
    - +
    +
    + + _ + 117 + +
    + + import { + +
    +
    +
    + + _ + 117 + +
    + Card, - -
    -
    -
    - - _ - 116 - -
    - + +
    +
    +
    + + _ + 117 + +
    + Image, - -
    -
    -
    - - _ - 116 - -
    - + +
    +
    +
    + + _ + 117 + +
    + Inline, - -
    -
    -
    - - _ - 116 - -
    - + +
    +
    +
    + + _ + 117 + +
    + SearchField, - -
    -
    -
    - - _ - 116 - -
    - + +
    +
    +
    + + _ + 117 + +
    + Select, - -
    -
    -
    - - _ - 116 - -
    - + +
    +
    +
    + + _ + 117 + +
    + Stack, - -
    -
    -
    - - _ - 116 - -
    - + +
    +
    +
    + + _ + 117 + +
    + Text, - -
    -
    -
    - - _ - 116 - -
    - - } from '@marigold/components'; - -
    -
    -
    - - _ - 116 - -
    - - import { useQuery } from '@tanstack/react-query'; - -
    -
    -
    - - _ - 116 - -
    - - import { useState } from 'react'; - -
    -
    -
    - - _ - 116 - -
    - -
    -
    -
    - - _ - 116 - -
    - - interface IMovie { - -
    -
    -
    - - _ - 116 - -
    - + +
    +
    +
    + + _ + 117 + +
    + + } from '@marigold/components'; + +
    +
    +
    + + _ + 117 + +
    + + import { useQuery } from '@tanstack/react-query'; + +
    +
    +
    + + _ + 117 + +
    + + import { useState } from 'react'; + +
    +
    +
    + + _ + 117 + +
    + +
    +
    +
    + + _ + 117 + +
    + + interface IMovie { + +
    +
    +
    + + _ + 117 + +
    + href: string; - -
    -
    -
    - - _ - 116 - -
    - + +
    +
    +
    + + _ + 117 + +
    + year: number; - -
    -
    -
    - - _ - 116 - -
    - + +
    +
    +
    + + _ + 117 + +
    + title: string; - -
    -
    -
    - - _ - 116 - -
    - + +
    +
    +
    + + _ + 117 + +
    + category: string; - -
    -
    -
    - - _ - 116 - -
    - + +
    +
    +
    + + _ + 117 + +
    + thumbnail: string; - -
    -
    -
    - - _ - 116 - -
    - + +
    +
    +
    + + _ + 117 + +
    + thumbnail_width: number; - -
    -
    -
    - - _ - 116 - -
    - + +
    +
    +
    + + _ + 117 + +
    + thumbnail_height: number; - -
    -
    -
    - - _ - 116 - -
    - - extract: string; - -
    -
    -
    - - _ - 116 - -
    - - } - -
    -
    -
    - - _ - 116 - -
    - - function ServerStateExample() { - -
    -
    -
    - - _ - 116 - -
    - - const [filters, setFilters] = useState<{ title: string; category: string }>({ - -
    -
    -
    - - _ - 116 - -
    - - title: '', - -
    -
    -
    - - _ - 116 - -
    - - category: '', - -
    -
    -
    - - _ - 116 - -
    - - }); - -
    -
    -
    - - _ - 116 - -
    - -
    -
    -
    - - _ - 116 - -
    - - const fetchData = async ( - -
    -
    -
    - - _ - 116 - -
    - - url: string, - -
    -
    -
    - - _ - 116 - -
    - - queryParams: Record<string, string> - -
    -
    -
    - - _ - 116 - -
    - - ) => { - -
    -
    -
    - - _ - 116 - -
    - - const queryString = new URLSearchParams(queryParams).toString(); - -
    -
    -
    - - _ - 116 - -
    - - const apiURL = \`\${url}\${queryString ? \`?\${queryString}\` : ''}\`; - -
    -
    -
    - - _ - 116 - -
    - - const data = await fetch(apiURL); - -
    -
    -
    - - _ - 116 - -
    - - return await data.json(); - -
    -
    -
    - - _ - 116 - -
    - - }; - -
    -
    -
    - - _ - 116 - -
    - -
    -
    -
    - - _ - 116 - -
    - - const { - -
    -
    -
    - - _ - 116 - -
    - - data: movies, - -
    -
    -
    - - _ - 116 - -
    - - isError, - -
    -
    -
    - - _ - 116 - -
    - - isLoading, - -
    -
    -
    - - _ - 116 - -
    - - error, - -
    -
    -
    - - _ - 116 - -
    - - } = useQuery<Array<IMovie>>({ - -
    -
    -
    - - _ - 116 - -
    - - queryKey: ['users', filters], - -
    -
    -
    - - _ - 116 - -
    - - queryFn: async () => - -
    -
    -
    - - _ - 116 - -
    - - await fetchData( - -
    -
    -
    - - _ - 116 - -
    - - 'https://6630d183c92f351c03db2e12.mockapi.io/movies', - -
    -
    -
    - - _ - 116 - -
    - - filters - -
    -
    -
    - - _ - 116 - -
    - - ), - -
    -
    -
    - - _ - 116 - -
    - - }); - -
    -
    -
    - - _ - 116 - -
    - -
    -
    -
    - - _ - 116 - -
    - - if (isError) { - -
    -
    -
    - - _ - 116 - -
    - - return <span>Error: {error.message}</span>; - -
    -
    -
    - - _ - 116 - -
    - - } - -
    -
    -
    - - _ - 116 - -
    - -
    -
    -
    - - _ - 116 - -
    - - return ( - -
    -
    -
    - - _ - 116 - -
    - - <> - -
    -
    -
    - - _ - 116 - -
    - - <Stack space={4}> - -
    -
    -
    - - _ - 116 - -
    - - <Inline space={4}> - -
    -
    -
    - - _ - 116 - -
    - - <SearchField - -
    -
    -
    - - _ - 116 - -
    - - value={filters?.title} - -
    -
    -
    - - _ - 116 - -
    - - onChange={value => setFilters(prev => ({ ...prev, title: value }))} - -
    -
    -
    - - _ - 116 - -
    - - label="search" - -
    -
    -
    - - _ - 116 - -
    - - width={'1/2'} - -
    -
    -
    - - _ - 116 - -
    - - /> - -
    -
    -
    - - _ - 116 - -
    - - <Select - -
    -
    -
    - - _ - 116 - -
    - - label="Category" - -
    -
    -
    - - _ - 116 - -
    - - placeholder="Select your character" - -
    -
    -
    - - _ - 116 - -
    - - width={'1/5'} - -
    -
    -
    - - _ - 116 - -
    - - onChange={key => { - -
    -
    -
    - - _ - 116 - -
    - - setFilters(prev => ({ ...prev, category: key as string })); - -
    -
    -
    - - _ - 116 - -
    - - }} - -
    -
    -
    - - _ - 116 - -
    - - selectedKey={filters.category} - -
    -
    -
    - - _ - 116 - -
    - - > - -
    -
    -
    - - _ - 116 - -
    - - <Select.Option id={''}>all</Select.Option> - -
    -
    -
    - - _ - 116 - -
    - - <Select.Option id={'Drama'}>Drama</Select.Option> - -
    -
    -
    - - _ - 116 - -
    - - <Select.Option id={'Silent'}>Silent</Select.Option> - -
    -
    -
    - - _ - 116 - -
    - - <Select.Option id={'Comedy'}>Comedy</Select.Option> - -
    -
    -
    - - _ - 116 - -
    - - <Select.Option id={'Western'}>Western</Select.Option> - -
    -
    -
    - - _ - 116 - -
    - - <Select.Option id={'Historical'}>Historical</Select.Option> - -
    -
    -
    - - _ - 116 - -
    - - <Select.Option id={'Fantasy'}>Fantasy</Select.Option> - -
    -
    -
    - - _ - 116 - -
    - - </Select> - -
    -
    -
    - - _ - 116 - -
    - - </Inline> - -
    -
    -
    - - _ - 116 - -
    - -
    -
    -
    - - _ - 116 - -
    - - {isLoading && <span>Loading...</span>} - -
    -
    -
    - - _ - 116 - -
    - - <Inline space={4}> - -
    -
    -
    - - _ - 116 - -
    - - { - -
    -
    -
    - - _ - 116 - -
    - - // check if search returns nothing(movies='not found') - -
    -
    -
    - - _ - 116 - -
    - - typeof movies === 'string' ? ( - -
    -
    -
    - - _ - 116 - -
    - - <Text fontSize="xl" color="text-error"> - -
    -
    -
    - - _ - 116 - -
    - - {movies} - -
    -
    -
    - - _ - 116 - -
    - - </Text> - -
    -
    -
    - - _ - 116 - -
    - - ) : ( - -
    -
    -
    - - _ - 116 - -
    - - movies?.map(movie => ( - -
    -
    -
    - - _ - 116 - -
    - - <Card variant="hovering" key={movie.href}> - -
    -
    -
    - - _ - 116 - -
    - - <Text fontSize="xl" weight="bold"> - -
    -
    -
    - - _ - 116 - -
    - - {movie.title} - -
    -
    -
    - - _ - 116 - -
    - - </Text> - -
    -
    -
    - - _ - 116 - -
    - - <Image - -
    -
    -
    - - _ - 116 - -
    - - src={movie.thumbnail} - -
    -
    -
    - - _ - 116 - -
    - - alt={movie.title} - -
    -
    -
    - - _ - 116 - -
    - - width={300} - -
    -
    -
    - - _ - 116 - -
    - - height={300} - -
    -
    -
    - - _ - 116 - -
    - - /> - -
    -
    -
    - - _ - 116 - -
    - - </Card> - -
    -
    -
    - - _ - 116 - -
    - - )) - -
    -
    -
    - - _ - 116 - -
    - - ) - -
    -
    -
    - - _ - 116 - -
    - - } - -
    -
    -
    - - _ - 116 - -
    - - </Inline> - -
    -
    -
    - - _ - 116 - -
    - - </Stack> - -
    -
    -
    - - _ - 116 - -
    - - </> - -
    -
    -
    - - _ - 116 - -
    - - ); - -
    -
    -
    - - _ - 116 - -
    - - } - -
    -
    -
    - - _ - 116 - -
    - -
    -
    -
    - - _ - 116 - -
    - - export default ServerStateExample; - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - - - -
    -
    - + +
    -
    -
    - - Category - - - -
    - +
    + + _ + 117 + +
    + + extract: string; + +
    -
    - +
    + + _ + 117 + +
    + + } + +
    +
    +
    + + _ + 117 + +
    + + function ServerStateExample() { + +
    +
    +
    + + _ + 117 + +
    + + const [filters, setFilters] = useState<{ title: string; category: string }>({ + +
    +
    +
    + + _ + 117 + +
    + + title: '', + +
    +
    +
    + + _ + 117 + +
    + + category: '', + +
    +
    +
    + + _ + 117 + +
    + + }); + +
    +
    +
    + + _ + 117 + +
    + +
    +
    +
    + + _ + 117 + +
    + + const fetchData = async ( + +
    +
    +
    + + _ + 117 + +
    + + url: string, + +
    +
    +
    + + _ + 117 + +
    + + queryParams: Record<string, string> + +
    +
    +
    + + _ + 117 + +
    + + ) => { + +
    +
    +
    + + _ + 117 + +
    + + const queryString = new URLSearchParams(queryParams).toString(); + +
    +
    +
    + + _ + 117 + +
    + + const apiURL = \`\${url}\${queryString ? \`?\${queryString}\` : ''}\`; + +
    +
    +
    + + _ + 117 + +
    + + const data = await fetch(apiURL); + +
    +
    +
    + + _ + 117 + +
    + + return await data.json(); + +
    +
    +
    + + _ + 117 + +
    + + }; + +
    +
    +
    + + _ + 117 + +
    + +
    +
    +
    + + _ + 117 + +
    + + const { + +
    +
    +
    + + _ + 117 + +
    + + data: movies, + +
    +
    +
    + + _ + 117 + +
    + + isError, + +
    +
    +
    + + _ + 117 + +
    + + isLoading, + +
    +
    +
    + + _ + 117 + +
    + + error, + +
    +
    +
    + + _ + 117 + +
    + + } = useQuery<Array<IMovie>>({ + +
    +
    +
    + + _ + 117 + +
    + + queryKey: ['users', filters], + +
    +
    +
    + + _ + 117 + +
    + + queryFn: async () => + +
    +
    +
    + + _ + 117 + +
    + + await fetchData( + +
    +
    +
    + + _ + 117 + +
    + + 'https://6630d183c92f351c03db2e12.mockapi.io/movies', + +
    +
    +
    + + _ + 117 + +
    + + filters + +
    +
    +
    + + _ + 117 + +
    + + ), + +
    +
    +
    + + _ + 117 + +
    + + }); + +
    +
    +
    + + _ + 117 + +
    + +
    +
    +
    + + _ + 117 + +
    + + if (isError) { + +
    +
    +
    + + _ + 117 + +
    + + return <span>Error: {error.message}</span>; + +
    +
    +
    + + _ + 117 + +
    + + } + +
    +
    +
    + + _ + 117 + +
    + +
    +
    +
    + + _ + 117 + +
    + + return ( + +
    +
    +
    + + _ + 117 + +
    + + <> + +
    +
    +
    + + _ + 117 + +
    + + <Stack space={4}> + +
    +
    +
    + + _ + 117 + +
    + + <Inline space={4}> + +
    +
    +
    + + _ + 117 + +
    + + <SearchField + +
    +
    +
    + + _ + 117 + +
    + + value={filters?.title} + +
    +
    +
    + + _ + 117 + +
    + + onChange={value => setFilters(prev => ({ ...prev, title: value }))} + +
    +
    +
    + + _ + 117 + +
    + + label="search" + +
    +
    +
    + + _ + 117 + +
    + + width={'1/2'} + +
    +
    +
    + + _ + 117 + +
    + + /> + +
    +
    +
    + + _ + 117 + +
    + + <Select + +
    +
    +
    + + _ + 117 + +
    + + label="Category" + +
    +
    +
    + + _ + 117 + +
    + + placeholder="Select your character" + +
    +
    +
    + + _ + 117 + +
    + + width={'1/5'} + +
    +
    +
    + + _ + 117 + +
    + + onChange={key => { + +
    +
    +
    + + _ + 117 + +
    + + setFilters(prev => ({ ...prev, category: key as string })); + +
    +
    +
    + + _ + 117 + +
    + + }} + +
    +
    +
    + + _ + 117 + +
    + + selectedKey={filters.category} + +
    +
    +
    + + _ + 117 + +
    + + > + +
    +
    +
    + + _ + 117 + +
    + + <Select.Option id={''}>all</Select.Option> + +
    +
    +
    + + _ + 117 + +
    + + <Select.Option id={'Drama'}>Drama</Select.Option> + +
    +
    +
    + + _ + 117 + +
    + + <Select.Option id={'Silent'}>Silent</Select.Option> + +
    +
    +
    + + _ + 117 + +
    + + <Select.Option id={'Comedy'}>Comedy</Select.Option> + +
    +
    +
    + + _ + 117 + +
    + + <Select.Option id={'Western'}>Western</Select.Option> + +
    +
    +
    + + _ + 117 + +
    + + <Select.Option id={'Historical'}>Historical</Select.Option> + +
    +
    +
    + + _ + 117 + +
    + + <Select.Option id={'Fantasy'}>Fantasy</Select.Option> + +
    +
    +
    + + _ + 117 + +
    + + </Select> + +
    +
    +
    + + _ + 117 + +
    + + </Inline> + +
    +
    +
    + + _ + 117 + +
    + +
    +
    +
    + + _ + 117 + +
    + + {isLoading && <span>Loading...</span>} + +
    +
    +
    + + _ + 117 + +
    + + <Inline space={4}> + +
    +
    +
    + + _ + 117 + +
    + + { + +
    +
    +
    + + _ + 117 + +
    + + // check if search returns nothing(movies='not found') + +
    +
    +
    + + _ + 117 + +
    + + typeof movies === 'string' ? ( + +
    +
    +
    + + _ + 117 + +
    + + <Text fontSize="xl" color="text-error"> + +
    +
    +
    + + _ + 117 + +
    + + {movies} + +
    +
    +
    + + _ + 117 + +
    + + </Text> + +
    +
    +
    + + _ + 117 + +
    + + ) : ( + +
    +
    +
    + + _ + 117 + +
    + + movies?.map(movie => ( + +
    +
    +
    + + _ + 117 + +
    + + <Card variant="hovering" key={movie.href}> + +
    +
    +
    + + _ + 117 + +
    + + <Text fontSize="xl" weight="bold"> + +
    +
    +
    + + _ + 117 + +
    + + {movie.title} + +
    +
    +
    + + _ + 117 + +
    + + </Text> + +
    +
    +
    + + _ + 117 + +
    + + <Image + +
    +
    +
    + + _ + 117 + +
    + + src={movie.thumbnail} + +
    +
    +
    + + _ + 117 + +
    + + alt={movie.title} + +
    +
    +
    + + _ + 117 + +
    + + width={300} + +
    +
    +
    + + _ + 117 + +
    + + height={300} + +
    +
    +
    + + _ + 117 + +
    + + /> + +
    +
    +
    + + _ + 117 + +
    + + </Card> + +
    +
    +
    + + _ + 117 + +
    + + )) + +
    +
    +
    + + _ + 117 + +
    + + ) + +
    +
    +
    + + _ + 117 + +
    + + } + +
    +
    +
    + + _ + 117 + +
    + + </Inline> + +
    +
    +
    + + _ + 117 + +
    + + </Stack> + +
    +
    +
    + + _ + 117 + +
    + + </> + +
    +
    +
    + + _ + 117 + +
    + + ); + +
    +
    +
    + + _ + 117 + +
    + + } + +
    +
    +
    + + _ + 117 + +
    + +
    +
    +
    + + _ + 117 + +
    + +
    +
    +
    + + _ + 117 + +
    + + export default ServerStateExample; + +
    +
    +
    +
    - - Loading... - -
    -
    -
    - - -

    - Write some description here -

    - - -
    +
    +
    + + +