Skip to Content
DocsDeveloper GuideState Management

State Management

Because WebF is compatible with the modern web ecosystem, you can handle state management using the same tools and patterns you already use for web development.

Framework-Specific State Management

You can use the built-in state management solutions provided by your chosen UI framework:

  • React: useState and useReducer for local state, and the Context API for passing state through the component tree.
  • Vue: The reactivity system, including ref, reactive, and the Composition API.
  • Svelte: Svelte’s built-in reactive statements and stores.

React Example

import React, { useState, useContext, createContext } from 'react'; // Local state with useState function Counter() { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); } // Context for global state const UserContext = createContext(null); function App() { const [user, setUser] = useState({ name: 'John', role: 'admin' }); return ( <UserContext.Provider value={{ user, setUser }}> <Dashboard /> </UserContext.Provider> ); } function Dashboard() { const { user } = useContext(UserContext); return <h1>Welcome, {user.name}!</h1>; }

Vue Example

<script setup> import { ref, reactive, computed } from 'vue'; // Reactive state const count = ref(0); const user = reactive({ name: 'John', role: 'admin' }); // Computed properties const isAdmin = computed(() => user.role === 'admin'); function increment() { count.value++; } </script> <template> <div> <p>Count: {{ count }}</p> <button @click="increment">Increment</button> <p v-if="isAdmin">Admin: {{ user.name }}</p> </div> </template>

Dedicated State Libraries

For more complex application state, you can use any major state management library from the npm ecosystem. These libraries work out of the box with WebF. Popular choices include:

  • Redux (with React Redux) - Predictable state container with time-travel debugging
  • Zustand - A popular, minimal solution for React with a simple API
  • Pinia - The recommended solution for Vue 3
  • XState - For finite state machines and statecharts
  • MobX - Reactive state management with decorators
  • Jotai - Atomic state management for React
  • Recoil - Facebook’s state management library for React

Example: Using Zustand with React

Here’s a quick example of how you can use Zustand to manage global state in React.

Installation

npm install zustand

Create a Store

src/store.js

import { create } from 'zustand'; export const useStore = create((set) => ({ // State count: 0, user: null, // Actions increase: () => set((state) => ({ count: state.count + 1 })), decrease: () => set((state) => ({ count: state.count - 1 })), setUser: (user) => set({ user }), clearUser: () => set({ user: null }), }));

Use in Components

src/App.jsx

import React from 'react'; import { useStore } from './store'; function Counter() { const { count, increase, decrease } = useStore(); return ( <div> <p>Global count: {count}</p> <button onClick={increase}>+1</button> <button onClick={decrease}>-1</button> </div> ); } function UserProfile() { const { user, setUser, clearUser } = useStore(); return ( <div> {user ? ( <> <p>User: {user.name}</p> <button onClick={clearUser}>Logout</button> </> ) : ( <button onClick={() => setUser({ name: 'John' })}> Login </button> )} </div> ); } function App() { return ( <div> <Counter /> <UserProfile /> </div> ); }

Advanced Patterns

Zustand also supports more advanced patterns:

import { create } from 'zustand'; import { persist, devtools } from 'zustand/middleware'; // Persist state to localStorage export const usePersistedStore = create( persist( (set) => ({ settings: { theme: 'dark', language: 'en' }, updateSettings: (settings) => set({ settings }), }), { name: 'app-storage', // localStorage key } ) ); // DevTools integration export const useDebugStore = create( devtools((set) => ({ items: [], addItem: (item) => set((state) => ({ items: [...state.items, item] })), })) );

Example: Using Redux with React

For applications requiring Redux:

Installation

npm install @reduxjs/toolkit react-redux

Create Store

src/store/index.js

import { configureStore, createSlice } from '@reduxjs/toolkit'; const counterSlice = createSlice({ name: 'counter', initialState: { value: 0 }, reducers: { increment: (state) => { state.value += 1 }, decrement: (state) => { state.value -= 1 }, }, }); export const { increment, decrement } = counterSlice.actions; export const store = configureStore({ reducer: { counter: counterSlice.reducer, }, });

Use in App

src/App.jsx

import React from 'react'; import { Provider, useDispatch, useSelector } from 'react-redux'; import { store, increment, decrement } from './store'; function Counter() { const count = useSelector((state) => state.counter.value); const dispatch = useDispatch(); return ( <div> <p>Count: {count}</p> <button onClick={() => dispatch(increment())}>+</button> <button onClick={() => dispatch(decrement())}>-</button> </div> ); } function App() { return ( <Provider store={store}> <Counter /> </Provider> ); } export default App;

Best Practices

1. Choose the Right Tool

  • Local component state: Use framework built-ins (useState, ref)
  • Shared state across a few components: Use Context API or provide/inject
  • Complex global state: Use dedicated libraries (Zustand, Redux, Pinia)
  • Server state: Consider libraries like React Query or SWR

2. Keep State Minimal

Only store in global state what truly needs to be global:

// ❌ Bad - storing derived data const store = create((set) => ({ users: [], userCount: 0, // This can be computed })); // ✅ Good - compute derived data const store = create((set) => ({ users: [], })); function useUserCount() { return useStore((state) => state.users.length); }

3. Separate Concerns

Organize your state by feature or domain:

// stores/auth.js export const useAuthStore = create((set) => ({ user: null, login: (user) => set({ user }), logout: () => set({ user: null }), })); // stores/cart.js export const useCartStore = create((set) => ({ items: [], addItem: (item) => set((state) => ({ items: [...state.items, item] })), }));

4. Handle Async Operations

import { create } from 'zustand'; export const useDataStore = create((set) => ({ data: null, loading: false, error: null, fetchData: async () => { set({ loading: true, error: null }); try { const response = await fetch('/api/data'); const data = await response.json(); set({ data, loading: false }); } catch (error) { set({ error: error.message, loading: false }); } }, }));

Performance Considerations

Selector Optimization

Use selectors to prevent unnecessary re-renders:

// ❌ Bad - component re-renders when any state changes function UserName() { const store = useStore(); return <div>{store.user.name}</div>; } // ✅ Good - only re-renders when user.name changes function UserName() { const userName = useStore((state) => state.user.name); return <div>{userName}</div>; }

Memoization

import { useMemo } from 'react'; function ExpensiveComponent() { const items = useStore((state) => state.items); const sortedItems = useMemo(() => { return items.sort((a, b) => a.name.localeCompare(b.name)); }, [items]); return <div>{sortedItems.map(item => <div key={item.id}>{item.name}</div>)}</div>; }

Next Steps