Naman Arora

State Management in React JS ?

Pushlished on 03 Jun 2023

There are two types of state in a React App

  • Client side state: the state is only limited to client side such as a global menu which is open or closed.

  • Server side state: this state is both on client and server side such as user's posts, currently logged in user etc.

Client Side State Management

I like to use library zustand for state management on client side.

Firstly install zustand

npm install zustand

or if you are using yarn

yarn add zustand

Now in src/store/index.ts

import { create } from 'zustand';

interface GlobalState {
    count: number;
    increment: () => void;
}

const useStore = create<GlobalState>((set) => ({
    count: 0,
    increment: () => set((state) => ({ count: state.count + 1 }))
}));

Note: zustand also supports async functions for api requests

Now we can access these state and functions in our react app.

const Header: React.FC = () => {
    const count = useStore((state) => state.count);
    const increment = useStore((state) => state.increment);

    // or

    const { count, increment } = useState();
    // this will result in more rerenders so avoid this

    return (
        <div>
            <h1>{count}</h1>
            <button onClick={() => increment()}>Increment</button>
        </div>
    );
};

To know more about zustand check out zustand's github page

Server Side State Management

I like to use tanstack react query for server side state management when using rest apis.

Note: tanstack react query can also be used with GraphQL APIs.

Install tanstack react query

npm i @tanstack/react-query @tanstack/react-query-devtools

or if you are using yarn

yarn add @tanstack/react-query @tanstack/react-query-devtools

set up Query Client Provider in index.tsx

import React from 'react';
import App from './App';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

// Create a client
const queryClient = new QueryClient();

const Root: React.FC = () => {
    return (
        // Provide the client to your App
        <QueryClientProvider client={queryClient}>
            <App />
            <ReactQueryDevtools initialIsOpen={false} />
        </QueryClientProvider>
    );
};

render(<Root />, document.getElementById('root'));

Now we can make queries and mutations in App.tsx

import React from 'react';
import { getTodos, postTodo } from './api';

const App: React.FC = () => {
    // Access the client
    const queryClient = useQueryClient();

    // Queries
    const { data: todos, isLoading, isError } = useQuery(['todos'], getTodos);

    // Mutations
    const { mutate: addTodo } = useMutation(['addTodo'], postTodo, {
        onSuccess: () => {
            // Invalidate and refetch
            queryClient.invalidateQueries({ queryKey: ['todos'] });
        }
    });

    if (isLoading || isError || !todos) {
        return <p>Loading...</p>;
    }

    return (
        <div>
            <ul>
                {todos.map((todo) => (
                    <li key={todo.id}>{todo.title}</li>
                ))}
            </ul>

            <button
                onClick={() => {
                    addTodo({
                        id: Date.now(),
                        title: 'New Todo'
                    });
                }}
            >
                Add Todo
            </button>
        </div>
    );
};

You should strongly type your getTodos, postTodo from api folder to get better typescript intellisense.

To know more about React Query check out React Query's github page