Module 8: React Ecosystem

Explore the React ecosystem including popular libraries like React Query, React Hook Form, Framer Motion, and other essential tools for modern React development.

Back to Course|6.5 hours|Advanced

React Ecosystem

Explore the React ecosystem including popular libraries like React Query, React Hook Form, Framer Motion, and other essential tools for modern React development.

Progress: 0/4 topics completed0%

Select Topics Overview

React Query (TanStack Query)

Learn React Query for efficient server state management, caching, and data synchronization

Content by: Kriyansh Khunt

MERN Stack Developer

Connect

What is React Query?

React Query is a powerful library for managing server state in React applications. It provides caching, background updates, error handling, and optimistic updates out of the box.

Basic React Query Setup

Code Example
// Install React Query
npm install @tanstack/react-query

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

const queryClient = new QueryClient({
    defaultOptions: {
        queries: {
            staleTime: 5 * 60 * 1000, // 5 minutes
            cacheTime: 10 * 60 * 1000, // 10 minutes
            retry: 3,
            refetchOnWindowFocus: false,
        },
    },
});

function App() {
    return (
        <QueryClientProvider client={queryClient}>
            <div className="app">
                <UserList />
                <ReactQueryDevtools initialIsOpen={false} />
            </div>
        </QueryClientProvider>
    );
}

// Basic query usage
import { useQuery } from '@tanstack/react-query';

function UserList() {
    const { data: users, isLoading, error, refetch } = useQuery({
        queryKey: ['users'],
        queryFn: async () => {
            const response = await fetch('/api/users');
            if (!response.ok) {
                throw new Error('Failed to fetch users');
            }
            return response.json();
        },
    });

    if (isLoading) return <div>Loading users...</div>;
    if (error) return <div>Error: {error.message}</div>;

    return (
        <div>
            <h2>Users</h2>
            <button onClick={() => refetch()}>Refresh</button>
            <ul>
                {users.map(user => (
                    <li key={user.id}>{user.name}</li>
                ))}
            </ul>
        </div>
    );
}

// Query with parameters
function UserProfile({ userId }) {
    const { data: user, isLoading, error } = useQuery({
        queryKey: ['user', userId],
        queryFn: async () => {
            const response = await fetch('/api/users/' + userId);
            if (!response.ok) {
                throw new Error('Failed to fetch user');
            }
            return response.json();
        },
        enabled: !!userId, // Only run query if userId exists
    });

    if (isLoading) return <div>Loading user...</div>;
    if (error) return <div>Error: {error.message}</div>;
    if (!user) return <div>User not found</div>;

    return (
        <div>
            <h2>{user.name}</h2>
            <p>Email: {user.email}</p>
            <p>Role: {user.role}</p>
        </div>
    );
}
Swipe to see more code

Mutations and Optimistic Updates

Code Example
// Mutations with React Query
import { useMutation, useQueryClient } from '@tanstack/react-query';

function CreateUser() {
    const queryClient = useQueryClient();

    const createUserMutation = useMutation({
        mutationFn: async (userData) => {
            const response = await fetch('/api/users', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(userData),
            });
            if (!response.ok) {
                throw new Error('Failed to create user');
            }
            return response.json();
        },
        onSuccess: (newUser) => {
            // Invalidate and refetch users query
            queryClient.invalidateQueries({ queryKey: ['users'] });
            
            // Or update cache directly
            queryClient.setQueryData(['users'], (oldData) => {
                return oldData ? [...oldData, newUser] : [newUser];
            });
        },
        onError: (error) => {
            console.error('Failed to create user:', error);
        },
    });

    const handleSubmit = (e) => {
        e.preventDefault();
        const formData = new FormData(e.target);
        const userData = {
            name: formData.get('name'),
            email: formData.get('email'),
        };
        createUserMutation.mutate(userData);
    };

    return (
        <form onSubmit={handleSubmit}>
            <input name="name" placeholder="Name" required />
            <input name="email" type="email" placeholder="Email" required />
            <button 
                type="submit" 
                disabled={createUserMutation.isPending}
            >
                {createUserMutation.isPending ? 'Creating...' : 'Create User'}
            </button>
        </form>
    );
}

// Optimistic updates
function UpdateUser({ user }) {
    const queryClient = useQueryClient();

    const updateUserMutation = useMutation({
        mutationFn: async ({ userId, updates }) => {
            const response = await fetch('/api/users/' + userId, {
                method: 'PATCH',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(updates),
            });
            if (!response.ok) {
                throw new Error('Failed to update user');
            }
            return response.json();
        },
        onMutate: async ({ userId, updates }) => {
            // Cancel any outgoing refetches
            await queryClient.cancelQueries({ queryKey: ['users'] });

            // Snapshot the previous value
            const previousUsers = queryClient.getQueryData(['users']);

            // Optimistically update to the new value
            queryClient.setQueryData(['users'], (old) => {
                return old.map(user => 
                    user.id === userId ? { ...user, ...updates } : user
                );
            });

            // Return a context object with the snapshotted value
            return { previousUsers };
        },
        onError: (err, variables, context) => {
            // If the mutation fails, use the context returned from onMutate to roll back
            if (context.previousUsers) {
                queryClient.setQueryData(['users'], context.previousUsers);
            }
        },
        onSettled: () => {
            // Always refetch after error or success
            queryClient.invalidateQueries({ queryKey: ['users'] });
        },
    });

    const handleUpdate = (updates) => {
        updateUserMutation.mutate({ userId: user.id, updates });
    };

    return (
        <div>
            <h3>{user.name}</h3>
            <button 
                onClick={() => handleUpdate({ role: 'Admin' })}
                disabled={updateUserMutation.isPending}
            >
                Make Admin
            </button>
        </div>
    );
}
Swipe to see more code

🎯 Practice Exercise

Test your understanding of this topic:

Additional Resources

📚 Recommended Reading

  • React Query Documentation
  • React Hook Form Guide
  • Framer Motion Tutorial

🌐 Online Resources

  • React Ecosystem Overview
  • Popular React Libraries
  • React Query Best Practices

Ready for the Next Module?

Continue your learning journey and master the next set of concepts.

Continue to Module 9