| 1 |
# State Management |
| 2 |
|
| 3 |
CartFlows uses a layered state management approach combining **Redux** for global application state and **React Context** (via `SettingsProvider`) for server-injected initial data. |
| 4 |
|
| 5 |
## Overview |
| 6 |
|
| 7 |
``` |
| 8 |
WordPress Server |
| 9 |
│ wp_localize_script( 'cartflows-editor-app', 'wcfEditorAppData', [...] ) |
| 10 |
│ wp_localize_script( 'cartflows-settings-app', 'wcfSettingsAppData', [...] ) |
| 11 |
↓ |
| 12 |
React App Boot |
| 13 |
│ |
| 14 |
├── SettingsProvider (Context) ← wraps entire app |
| 15 |
│ └── initialData.js ← reads window.wcf*AppData |
| 16 |
│ |
| 17 |
└── Redux Store |
| 18 |
└── reducer(s) ← handles UI + async data updates |
| 19 |
``` |
| 20 |
|
| 21 |
## SettingsProvider |
| 22 |
|
| 23 |
**Location:** `admin-core/assets/src/utils/SettingsProvider/` |
| 24 |
|
| 25 |
`SettingsProvider` is a React Context provider that makes server-injected data available throughout the component tree without prop drilling. |
| 26 |
|
| 27 |
```jsx |
| 28 |
// Usage — wraps entire app in EditorApp.js |
| 29 |
<SettingsProvider> |
| 30 |
<MainEditor /> |
| 31 |
</SettingsProvider> |
| 32 |
``` |
| 33 |
|
| 34 |
**`initialData.js`** reads the global `wcfEditorAppData` or `wcfSettingsAppData` object injected by PHP and provides it as the context's initial value. |
| 35 |
|
| 36 |
### What SettingsProvider provides |
| 37 |
|
| 38 |
- Plugin settings (global CartFlows options) |
| 39 |
- Current flow/step data |
| 40 |
- Feature flags (pro features enabled/disabled) |
| 41 |
- Admin URLs, REST nonce, AJAX URL |
| 42 |
- Page builder detection |
| 43 |
|
| 44 |
## Redux Store |
| 45 |
|
| 46 |
The settings app maintains a Redux store with a reducer in `settings-app/data/reducer.js`. |
| 47 |
|
| 48 |
### State Shape (Settings App) |
| 49 |
|
| 50 |
```js |
| 51 |
{ |
| 52 |
flows: [], // All flows/funnels |
| 53 |
flowsStats: {}, // Per-flow statistics |
| 54 |
analytics: {}, // Analytics data |
| 55 |
globalSettings: {}, // CartFlows global settings |
| 56 |
ui: { |
| 57 |
activeTab: '', // Currently active navigation tab |
| 58 |
isLoading: false, // Global loading indicator |
| 59 |
filters: {} // Flow list filters |
| 60 |
} |
| 61 |
} |
| 62 |
``` |
| 63 |
|
| 64 |
### Action Pattern |
| 65 |
|
| 66 |
The settings app dispatches standard Redux actions to update state. API calls are made with `@wordpress/api-fetch` and the results dispatched to the store: |
| 67 |
|
| 68 |
```js |
| 69 |
// Typical async fetch pattern |
| 70 |
const response = await apiFetch( { |
| 71 |
path: '/cartflows/v1/flows', |
| 72 |
method: 'POST', |
| 73 |
} ); |
| 74 |
dispatch( { type: 'SET_FLOWS', payload: response.flows } ); |
| 75 |
``` |
| 76 |
|
| 77 |
## Data Flow |
| 78 |
|
| 79 |
``` |
| 80 |
REST API response |
| 81 |
│ |
| 82 |
▼ |
| 83 |
Redux dispatch |
| 84 |
│ |
| 85 |
▼ |
| 86 |
Reducer updates state |
| 87 |
│ |
| 88 |
▼ |
| 89 |
Connected components re-render |
| 90 |
``` |
| 91 |
|
| 92 |
## `@wordpress/data` Integration |
| 93 |
|
| 94 |
CartFlows uses `@wordpress/data` alongside Redux for integration with the WordPress core data layer. This provides access to: |
| 95 |
- `wp.data.select( 'core' )` — WordPress core entities |
| 96 |
- `wp.data.dispatch( 'core/notices' )` — Admin notice system |
| 97 |
|
| 98 |
## Custom Hooks |
| 99 |
|
| 100 |
### `usePublishedFlows` |
| 101 |
|
| 102 |
**Location:** `settings-app/hooks/usePublishedFlows.js` |
| 103 |
|
| 104 |
A custom React hook that fetches and returns the list of published flows from the REST API. It handles loading and error states internally. |
| 105 |
|
| 106 |
```js |
| 107 |
const { flows, isLoading, error } = usePublishedFlows(); |
| 108 |
``` |
| 109 |
|
| 110 |
## Event System |
| 111 |
|
| 112 |
The editor app uses a custom `settingsEvents` event emitter (imported from the settings module) for cross-component communication that doesn't fit neatly into the Redux model (e.g., triggering save from a toolbar button). |
| 113 |
|
| 114 |
```js |
| 115 |
import settingsEvents from '@Utils/settingsEvents'; |
| 116 |
|
| 117 |
// Emit |
| 118 |
settingsEvents.emit( 'save' ); |
| 119 |
|
| 120 |
// Listen |
| 121 |
settingsEvents.on( 'save', handleSave ); |
| 122 |
``` |
| 123 |
|
| 124 |
## Context vs Redux — When to Use Each |
| 125 |
|
| 126 |
| Use Case | Solution | |
| 127 |
|----------|---------| |
| 128 |
| Server-injected initial data | `SettingsProvider` (Context) | |
| 129 |
| Feature flags, admin URLs, nonce | `SettingsProvider` (Context) | |
| 130 |
| Dynamic UI state (loading, tabs) | Redux | |
| 131 |
| Fetched API data (flows, analytics) | Redux | |
| 132 |
| Cross-component events (save, reload) | `settingsEvents` emitter | |
| 133 |
| Confirmation dialogs | `ConfirmDialogProvider` (Context) | |
| 134 |
|
| 135 |
## Related Pages |
| 136 |
|
| 137 |
- [](Frontend-ArchitectureFrontend-Architecture](Frontend-Architecture](Frontend-Architecture) |
| 138 |
- [](Editor-AppEditor-App](Editor-App](Editor-App) |
| 139 |
- [](Settings-AppSettings-App](Settings-App](Settings-App) |
| 140 |
- [](REST-API-ReferenceREST-API-Reference](REST-API-Reference](REST-API-Reference) |
| 141 |
|