javascript

Supercharge React: Zustand and Jotai, the Dynamic Duo for Simple, Powerful State Management

React state management evolves with Zustand and Jotai offering simpler alternatives to Redux. They provide lightweight, flexible solutions with minimal boilerplate, excellent TypeScript support, and powerful features for complex state handling in React applications.

Supercharge React: Zustand and Jotai, the Dynamic Duo for Simple, Powerful State Management

React has come a long way since its inception, and state management remains a crucial aspect of building complex applications. While Redux has been the go-to solution for many developers, newer alternatives like Zustand and Jotai have gained popularity for their simplicity and power.

Let’s dive into these lightweight state management libraries and see how they can supercharge your React projects.

Zustand is a tiny state management solution that packs a punch. It’s incredibly easy to set up and use, making it perfect for both small and large-scale applications. One of the things I love about Zustand is its minimal boilerplate. You don’t need to wrap your entire app in a provider or deal with complex reducers.

Here’s a quick example of how you can create a store with Zustand:

import create from 'zustand'

const useStore = create((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}))

Now, you can use this store in any component without any additional setup:

function BearCounter() {
  const bears = useStore((state) => state.bears)
  return <h1>{bears} around here...</h1>
}

function Controls() {
  const increasePopulation = useStore((state) => state.increasePopulation)
  return <button onClick={increasePopulation}>one up</button>
}

Isn’t that neat? No need for context providers or complex setup. Just create your store and use it wherever you need it.

Zustand also supports middleware out of the box. You can add persistence, devtools, and more with just a few lines of code. For example, to add Redux devtools support:

import { devtools } from 'zustand/middleware'

const useStore = create(devtools((set) => ({
  // your state and actions here
})))

Now you can debug your state changes using the Redux devtools extension in your browser. It’s like having a superpower for debugging!

But what if you need something even more minimal? That’s where Jotai comes in. Jotai takes the atom-based approach to state management, inspired by Recoil but with an even simpler API.

With Jotai, you create atoms for your state, and then use them in your components. Here’s a simple example:

import { atom, useAtom } from 'jotai'

const countAtom = atom(0)

function Counter() {
  const [count, setCount] = useAtom(countAtom)
  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
    </div>
  )
}

Jotai shines when you need to manage complex, interdependent state. You can create derived atoms that depend on other atoms, making it easy to create computed values:

const doubleCountAtom = atom(
  (get) => get(countAtom) * 2
)

function DoubleCounter() {
  const [doubleCount] = useAtom(doubleCountAtom)
  return <h2>Double count: {doubleCount}</h2>
}

One thing I particularly love about Jotai is how it handles asynchronous state. You can create atoms that resolve promises, making it a breeze to work with API calls or other async operations:

const userAtom = atom(async () => {
  const response = await fetch('https://api.example.com/user')
  return response.json()
})

function User() {
  const [user] = useAtom(userAtom)
  if (user.loading) return <div>Loading...</div>
  if (user.error) return <div>Error: {user.error.message}</div>
  return <div>Welcome, {user.name}!</div>
}

Both Zustand and Jotai offer excellent TypeScript support, which is a huge plus in my book. They provide type inference out of the box, making it easy to catch errors early and improve the overall reliability of your code.

When it comes to performance, both libraries are lightweight and fast. Zustand uses a single store approach, which can be more efficient for larger applications. Jotai, on the other hand, uses fine-grained updates, which can be beneficial for applications with frequent, small updates to the state.

One of the things that drew me to these libraries is their flexibility. They don’t force you into a specific pattern or architecture. You can use them alongside other libraries or even mix them with the built-in React state management tools like useState and useContext.

For example, you might use Zustand for global application state and Jotai for more localized, component-specific state. Or you could use Zustand for your main state management and fall back to useState for simple, component-level state that doesn’t need to be shared.

Another great feature of both libraries is their support for React’s concurrent mode. They’re designed to work seamlessly with React 18’s new features, ensuring your app stays fast and responsive even as it grows in complexity.

Let’s look at a more complex example using Zustand to manage a todo list:

import create from 'zustand'

const useTodoStore = create((set) => ({
  todos: [],
  addTodo: (text) => set((state) => ({
    todos: [...state.todos, { id: Date.now(), text, completed: false }]
  })),
  toggleTodo: (id) => set((state) => ({
    todos: state.todos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    )
  })),
  removeTodo: (id) => set((state) => ({
    todos: state.todos.filter(todo => todo.id !== id)
  }))
}))

function TodoList() {
  const todos = useTodoStore(state => state.todos)
  const toggleTodo = useTodoStore(state => state.toggleTodo)
  const removeTodo = useTodoStore(state => state.removeTodo)

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          <input
            type="checkbox"
            checked={todo.completed}
            onChange={() => toggleTodo(todo.id)}
          />
          <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
            {todo.text}
          </span>
          <button onClick={() => removeTodo(todo.id)}>Delete</button>
        </li>
      ))}
    </ul>
  )
}

function AddTodo() {
  const addTodo = useTodoStore(state => state.addTodo)
  const [text, setText] = useState('')

  const handleSubmit = (e) => {
    e.preventDefault()
    if (text.trim()) {
      addTodo(text)
      setText('')
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="Add a new todo"
      />
      <button type="submit">Add</button>
    </form>
  )
}

This example demonstrates how easy it is to manage complex state with Zustand. We’ve created a store that handles adding, toggling, and removing todos. The components can then access and modify this state without needing to pass props down through multiple levels.

Now, let’s look at a similar example using Jotai:

import { atom, useAtom } from 'jotai'

const todosAtom = atom([])

const addTodoAtom = atom(
  null,
  (get, set, text) => {
    const todos = get(todosAtom)
    set(todosAtom, [...todos, { id: Date.now(), text, completed: false }])
  }
)

const toggleTodoAtom = atom(
  null,
  (get, set, id) => {
    const todos = get(todosAtom)
    set(todosAtom, todos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ))
  }
)

const removeTodoAtom = atom(
  null,
  (get, set, id) => {
    const todos = get(todosAtom)
    set(todosAtom, todos.filter(todo => todo.id !== id))
  }
)

function TodoList() {
  const [todos] = useAtom(todosAtom)
  const [, toggleTodo] = useAtom(toggleTodoAtom)
  const [, removeTodo] = useAtom(removeTodoAtom)

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          <input
            type="checkbox"
            checked={todo.completed}
            onChange={() => toggleTodo(todo.id)}
          />
          <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
            {todo.text}
          </span>
          <button onClick={() => removeTodo(todo.id)}>Delete</button>
        </li>
      ))}
    </ul>
  )
}

function AddTodo() {
  const [, addTodo] = useAtom(addTodoAtom)
  const [text, setText] = useState('')

  const handleSubmit = (e) => {
    e.preventDefault()
    if (text.trim()) {
      addTodo(text)
      setText('')
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="Add a new todo"
      />
      <button type="submit">Add</button>
    </form>
  )
}

In this Jotai example, we’ve created separate atoms for the todos list and each action. This approach allows for even more fine-grained control over our state updates.

Both Zustand and Jotai offer powerful debugging capabilities. With Zustand, you can use the Redux DevTools extension to inspect your state and actions. Jotai provides a debug atom that you can use to log state changes.

One of the things I’ve come to appreciate about these libraries is how they encourage you to think about your state in a more modular way. Instead of having one giant state object, you can break your state down into smaller, more manageable pieces.

This approach not only makes your code more maintainable but also helps with performance. By only updating the specific parts of your state that change, you can avoid unnecessary re-renders and keep your app snappy.

Another cool feature of both libraries is their ability to create custom hooks. This allows you to encapsulate complex state logic and reuse it across your application. For example, with Zustand:

const useCounter = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}))

function Counter() {
  const { count, increment, decrement } = useCounter()
  return (
    <div>
      <button onClick={decrement}>-</button>
      <span>{count}</span>
      <button onClick={increment}>+</button>
    </div>
  )
}

And with Jotai:

const countAtom = atom(0)
const incrementAtom = atom(null, (get, set) => set(countAtom, get(countAtom) + 1))
const decrementAtom = atom(null, (get, set) => set(countAtom, get(countAtom) - 1))

function useCounter() {
  const [count] = useAtom(countAtom)
  const [, increment] = useAtom(incrementAtom)
  const [, decrement] = useAtom(decrementAtom)
  return { count, increment, decrement }
}

function Counter

Keywords: React state management, Zustand, Jotai, lightweight libraries, Redux alternatives, TypeScript support, concurrent mode, performance optimization, custom hooks, modular state design



Similar Posts
Blog Image
Curious How JavaScript Bakes and Manages Cookies?

Cookie Magic in JavaScript: From Baking Basics to Savory Security Tips

Blog Image
Supercharge Your React Native App: Unleash the Power of Hermes for Lightning-Fast Performance

Hermes optimizes React Native performance by precompiling JavaScript, improving startup times and memory usage. It's beneficial for complex apps on various devices, especially Android. Enable Hermes, optimize code, and use profiling tools for best results.

Blog Image
10 Proven JavaScript Optimization Techniques for Faster Web Applications

Learn proven JavaScript optimization techniques to boost web app performance. Discover code splitting, lazy loading, memoization, and more strategies to create faster, more responsive applications that users love. Start optimizing today.

Blog Image
Temporal API: JavaScript's Time-Saving Revolution for Effortless Date Handling

The Temporal API is a proposed replacement for JavaScript's Date object, offering improved timezone handling, intuitive time arithmetic, and support for various calendar systems. It introduces new object types like PlainDate, ZonedDateTime, and Duration, making complex date calculations and recurring events easier. With better DST handling and exact time arithmetic, Temporal promises cleaner, more reliable code for modern web development.

Blog Image
Turbocharge Your React Native App Deployment with Fastlane Magic

From Code to App Stores: Navigating React Native Deployment with Fastlane and Automated Magic

Blog Image
Is Vue.js The Secret Sauce to Your Next Web Project?

Unleash Your Web Creativity with the Progressive Powerhouse of Vue.js