Skip to content

Commit 286494f

Browse files
committed
docs(state): add cautionary notes and best practices for state management
1 parent 05cded9 commit 286494f

2 files changed

Lines changed: 94 additions & 15 deletions

File tree

packages/app/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,13 @@ const configure: AppModuleInitiator = (configurator) => {
134134
};
135135
```
136136

137+
> [!CAUTION]
138+
> The state management module is a powerful tool, but it`s important to know the potential pitfalls and limitations when using it in your application. The state management is global and can lead to unexpected behavior if not used carefully.
139+
>
140+
> __example 1:__ If you have multiple components that rely on the same state, updating the state in one component can cause re-renders in all components that use that state, potentially leading to performance issues.
141+
>
142+
> __example 2:__ The user has open multiple tabs of the application, and each tab is modifying the same state. This can lead to unexpected behavior, as changes made in one tab will be reflected to all tabs. (like storing user preferences for selected columns)
143+
137144
#### Bookmarks
138145

139146
[<img src="https://img.shields.io/github/package-json/v/equinor/fusion-framework?filename=packages%2Fmodules%2Fbookmark%2Fpackage.json&label=@equinor/fusion-framework-module-bookmark&style=for-the-badge" />](https://github.com/equinor/fusion-framework/tree/main/packages/modules/bookmark)

packages/react/app/README.md

Lines changed: 87 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,13 @@ export const configure: ModuleInitiator = (appConfigurator) => {
130130
};
131131
```
132132

133+
> [!CAUTION]
134+
> The state management module is a powerful tool, but it`s important to know the potential pitfalls and limitations when using it in your application. The state management is global and can lead to unexpected behavior if not used carefully.
135+
>
136+
> __example 1:__ If you have multiple components that rely on the same state, updating the state in one component can cause re-renders in all components that use that state, potentially leading to performance issues.
137+
>
138+
> __example 2:__ The user has open multiple tabs of the application, and each tab is modifying the same state. This can lead to unexpected behavior, as changes made in one tab will be reflected to all tabs. _(like storing user preferences for selected columns)_
139+
133140
### Basic Usage
134141

135142
Use `useAppState` just like React's `useState`, but with automatic persistence. The first parameter is a unique key, and the second is an options object with the default value:
@@ -178,21 +185,6 @@ const App = () => (
178185
);
179186
```
180187

181-
### Best Practices
182-
183-
> [!WARNING]
184-
> **Avoid Stale Closures**: When updating state based on the current value, always use the updater function to prevent stale closure issues in concurrent updates.
185-
>
186-
> ```typescript
187-
> const [count, setCount] = useAppState('counter', { defaultValue: 0 });
188-
>
189-
> // ❌ Bad: Can use stale value in rapid updates
190-
> const increment = () => setCount(count + 1);
191-
>
192-
> // ✅ Good: Always gets the latest value
193-
> const increment = () => setCount(prev => (prev || 0) + 1);
194-
> ```
195-
196188
### Advanced Usage
197189

198190
**Complex Objects with TypeScript:**
@@ -225,6 +217,86 @@ const SettingsPanel = () => {
225217
const clearSettings = () => setSettings(undefined);
226218
```
227219

220+
### Best Practices
221+
222+
#### Avoid Stale Closures
223+
> [!WARNING]
224+
> When updating state based on the current value, always use the updater function to prevent stale closure issues in concurrent updates.
225+
226+
```typescript
227+
const [count, setCount] = useAppState('counter', { defaultValue: 0 });
228+
229+
// ❌ Bad: Can use stale value in rapid updates
230+
const increment = () => setCount(count + 1);
231+
232+
// ✅ Good: Always gets the latest value
233+
const increment = () => setCount(prev => (prev || 0) + 1);
234+
```
235+
236+
#### State Key Organization
237+
238+
Use hierarchical naming for better organization:
239+
240+
```typescript
241+
// ✅ Good - hierarchical, descriptive
242+
'user.profile.personal'
243+
'user.preferences.theme'
244+
'app.settings.notifications'
245+
'feature.dashboard.filters'
246+
247+
// ❌ Avoid - flat, unclear
248+
'userdata'
249+
'settings'
250+
'stuff'
251+
```
252+
253+
#### Use strong typing
254+
255+
```typescript
256+
// ✅ Good - strong typing
257+
interface UserProfile {
258+
id: string;
259+
name: string;
260+
email: string;
261+
}
262+
263+
const [user, setUser] = useAppState<UserProfile>('user.profile');
264+
265+
// ❌ Avoid - weak typing
266+
const [user, setUser] = useAppState('user.profile');
267+
```
268+
269+
> [!TIP] Validate Complex Schemas
270+
> Use a library like `zod` or `yup` to validate complex state schemas before using them.
271+
> ```typescript
272+
> const userSchema = z.object({
273+
> id: z.string().uuid(),
274+
> name: z.string().min(2).max(100),
275+
> email: z.string().email(),
276+
> });
277+
>
278+
> type UserProfile = z.infer<typeof userSchema>;
279+
>
280+
> // ✅ Good - strong typing with validation
281+
> const useMyUser = () => {
282+
> const [value, setValue] = useAppState<UserProfile>('user.profile');
283+
> const setUser = useCallback((user: UserProfile) => {
284+
> if (userSchema.safeParse(user).success) {
285+
> setValue(user);
286+
> return true;
287+
> } else {
288+
> console.warn('Provided user is invalid');
289+
> return false;
290+
> }
291+
> }, [setValue]);
292+
> if(!userSchema.safeParse(value).success) {
293+
> console.warn('Current user state is invalid');
294+
> return null;
295+
> }
296+
> return value;
297+
> };
298+
> ```
299+
228300
## Feature Flag
229301
230302
> [!IMPORTANT]

0 commit comments

Comments
 (0)