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:
useStateanduseReducerfor local state, and theContextAPI 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 zustandCreate 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-reduxCreate 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
- Learn about Hybrid Routing for navigation
- Explore Native Plugins for device features
- Check Performance tips