Skip to content

Commit 2b21e37

Browse files
committed
Create Deep-Dive Blog Post for State Providers #7094
1 parent 17c5165 commit 2b21e37

1 file changed

Lines changed: 172 additions & 32 deletions

File tree

learn/blog/v10-deep-dive-state-provider.md

Lines changed: 172 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Act I Deep Dive: The State Provider Revolution
1+
# Deep Dive: The State Provider Revolution
22

33
**Subtitle: How Neo.mjs Delivers Intuitive State Management Without the Performance Tax**
44

@@ -41,62 +41,202 @@ This new foundation is what makes the modern `state.Provider` so powerful. It do
4141
it *knows* them. When a binding function runs, `core.Effect` observes every reactive property you access—no matter where
4242
it lives—and builds a precise dependency graph in real-time.
4343

44-
The result is an API that is not only more powerful but also simpler and more intuitive.
44+
The result is an API that is not only more powerful but also simpler and more intuitive, especially when it comes to
45+
changing state. The provider does what you would expect, automatically handling complex scenarios like deep merging.
4546

46-
```javascript
47-
// A component with a state provider
48-
import Container from '../container/Base.mjs';
49-
import Label from '../component/Label.mjs';
47+
```javascript live-preview
48+
import Button from 'neo.mjs/src/button/Base.mjs';
49+
import Container from 'neo.mjs/src/container/Base.mjs';
50+
import Label from 'neo.mjs/src/component/Label.mjs';
5051

5152
class MainView extends Container {
5253
static config = {
54+
className: 'My.StateProvider.Example1',
5355
stateProvider: {
5456
data: {
5557
user: {
56-
firstname: 'Tobias',
57-
lastname : 'Uhlig'
58+
firstName: 'Tobias',
59+
lastName : 'Uhlig'
5860
}
5961
}
6062
},
63+
layout: {ntype: 'vbox', align: 'start'},
6164
items: [{
6265
module: Label,
6366
bind: {
64-
// Bind the label's text to the user's full name
65-
text: data => `${data.user.firstname} ${data.user.lastname}`
67+
text: data => `User: ${data.user.firstName} ${data.user.lastName}`
68+
},
69+
style: {marginBottom: '10px'}
70+
}, {
71+
module: Button,
72+
text: 'Change First Name',
73+
handler() {
74+
// This performs a DEEP MERGE, not an overwrite.
75+
// The 'lastName' property will be preserved.
76+
this.setState({
77+
user: { firstName: 'John' }
78+
});
79+
}
80+
}, {
81+
module: Button,
82+
text: 'Change Last Name (Path-based)',
83+
style: {marginTop: '10px'},
84+
handler() {
85+
// You can also set a value using its path.
86+
this.setState({'user.lastName': 'Doe'});
6687
}
6788
}]
6889
}
6990
}
91+
MainView = Neo.setupClass(MainView);
7092
```
93+
Notice the "Change First Name" button. It calls `setState` with an object that only contains `firstName`. The v10 provider
94+
is smart enough to perform a deep merge, updating `firstName` while leaving `lastName` untouched. This prevents accidental
95+
data loss and makes state updates safe and predictable by default.
7196

72-
Where the magic truly begins is in how you *change* that data. Thanks to the new deep, proxy-based reactivity system,
73-
you can modify state with plain JavaScript assignments. It's as simple as it gets:
97+
### 3. The Power of Formulas: Derived State Made Easy
7498

75-
```javascript
76-
// Get the provider and change the data directly
77-
const provider = myComponent.getStateProvider();
99+
Because the provider is built on `Neo.core.Effect`, creating computed properties ("formulas") is a native, first-class
100+
feature. You define them in a separate `formulas` config, and the provider automatically keeps them updated.
78101

79-
// This one line is all it takes to trigger a reactive update.
80-
provider.data.user.firstname = 'Max';
102+
```javascript live-preview
103+
import Container from 'neo.mjs/src/container/Base.mjs';
104+
import Label from 'neo.mjs/src/component/Label.mjs';
105+
import TextField from 'neo.mjs/src/form/field/Text.mjs';
81106

82-
// Does not overwrite the lastname
83-
provider.setData({user: {firstname: 'Robert'}})
107+
class MainView extends Container {
108+
static config = {
109+
className: 'My.StateProvider.Example2',
110+
layout: {ntype: 'vbox', align: 'stretch'},
111+
stateProvider: {
112+
data: {
113+
user: {
114+
firstName: 'Tobias',
115+
lastName : 'Uhlig'
116+
}
117+
},
118+
formulas: {
119+
fullName: data => `${data.user.firstName} ${data.user.lastName}`
120+
}
121+
},
122+
items: [{
123+
module: Label,
124+
bind: { text: data => `Welcome, ${data.fullName}!` },
125+
style: {marginBottom: '10px'}
126+
}, {
127+
module: TextField,
128+
labelText: 'First Name',
129+
bind: { value: data => data.user.firstName },
130+
listeners: {
131+
change: function({value}) { this.setState({'user.firstName': value}) }
132+
}
133+
}, {
134+
module: TextField,
135+
labelText: 'Last Name',
136+
bind: { value: data => data.user.lastName },
137+
listeners: {
138+
change: function({value}) { this.setState({'user.lastName': value}) }
139+
}
140+
}]
141+
}
142+
}
143+
MainView = Neo.setupClass(MainView);
144+
```
145+
When you edit the text fields, the `setState` call updates the base `user` data. The `Effect` system detects this,
146+
automatically re-runs the `fullName` formula, and updates the welcome label.
84147

85-
// You can update multiple properties at once. Thanks to automatic batching,
86-
// this results in only a single UI update cycle.
87-
provider.setData({user: {firstname: 'John', lastname: 'Doe'}})
148+
### 4. Hierarchical by Design: Nested Providers That Just Work
88149

89-
// Alternative Syntax:
90-
provider.setData({
91-
'user.firstname': 'John',
92-
'user.lastname' : 'Doe'
93-
});
150+
The v10 provider was engineered to handle different scopes of state with an intelligent hierarchical model. A child
151+
component can seamlessly access data from its own provider as well as any parent provider.
152+
153+
```javascript live-preview
154+
import Container from 'neo.mjs/src/container/Base.mjs';
155+
import Label from 'neo.mjs/src/component/Label.mjs';
156+
157+
class MainView extends Container {
158+
static config = {
159+
className: 'My.StateProvider.Example3',
160+
stateProvider: {
161+
data: { theme: 'dark' }
162+
},
163+
layout: {ntype: 'vbox', align: 'stretch', padding: '10px'},
164+
items: [{
165+
module: Label,
166+
bind: { text: data => `Global Theme: ${data.theme}` }
167+
}, {
168+
module: Container,
169+
stateProvider: {
170+
data: { user: 'Alice' }
171+
},
172+
border: true,
173+
style: {padding: '10px', marginTop: '10px'},
174+
items: [{
175+
module: Label,
176+
bind: {
177+
text: data => `Local User: ${data.user} (Theme: ${data.theme})`
178+
}
179+
}]
180+
}]
181+
}
182+
}
183+
MainView = Neo.setupClass(MainView);
94184
```
185+
The nested component can access both `user` from its local provider and `theme` from the parent provider without any
186+
extra configuration.
95187

96-
There are no special setter functions to call, no reducers to write. You just change the data, and the UI updates.
97-
This clean, direct developer experience is the "what." Now, let's look at the "how."
188+
### 5. The Final Piece: State Providers in Functional Components
189+
190+
Thanks to the v10 refactoring, state providers are now a first-class citizen in functional components. You can define a
191+
provider and bind to its data with the same power and simplicity as in class-based components.
192+
193+
```javascript live-preview
194+
import {defineComponent} from 'neo.mjs';
195+
import Label from 'neo.mjs/src/component/Label.mjs';
196+
import TextField from 'neo.mjs/src/form/field/Text.mjs';
197+
198+
export default defineComponent({
199+
stateProvider: {
200+
data: {
201+
user: {
202+
firstName: 'Jane',
203+
lastName : 'Doe'
204+
}
205+
},
206+
formulas: {
207+
fullName: data => `${data.user.firstName} ${data.user.lastName}`
208+
}
209+
},
210+
createVdom(config) {
211+
return {
212+
layout: {ntype: 'vbox', align: 'stretch'},
213+
items: [{
214+
module: Label,
215+
bind: { text: data => `Welcome, ${config.data.fullName}!` },
216+
style: {marginBottom: '10px'}
217+
}, {
218+
module: TextField,
219+
labelText: 'First Name',
220+
bind: { value: data => config.data.user.firstName },
221+
listeners: {
222+
change: ({value}) => config.setState({'user.firstName': value})
223+
}
224+
}, {
225+
module: TextField,
226+
labelText: 'Last Name',
227+
bind: { value: data => config.data.user.lastName },
228+
listeners: {
229+
change: ({value}) => config.setState({'user.lastName': value})
230+
}
231+
}]
232+
}
233+
}
234+
});
235+
```
236+
This example demonstrates the full power of the new architecture: a functional component with its own reactive data,
237+
computed properties, and two-way bindings, all with clean, declarative code.
98238

99-
### 3. From Theory to Practice: The Comprehensive Guide
239+
### 6. From Theory to Practice: The Comprehensive Guide
100240

101241
The examples above show the clean, intuitive API. For a complete, hands-on exploration with dozens of live-preview
102242
examples covering everything from nested providers and formulas to advanced store management, we encourage you to
@@ -105,7 +245,7 @@ this system possible.
105245

106246
**[Read the Full State Providers Guide Here](../guides/datahandling/StateProviders.md)**
107247

108-
### 4. Under the Hood Part 1: The Proxy's Magic
248+
### 7. Under the Hood Part 1: The Proxy's Magic
109249

110250
The beautiful API above is powered by a sophisticated proxy created by `Neo.state.createHierarchicalDataProxy`.
111251
When you interact with `provider.data`, you're not touching a plain object; you're interacting with an intelligent agent
@@ -125,7 +265,7 @@ Here’s how it works:
125265

126266
This proxy is the bridge between a simple developer experience and a powerful, fine-grained reactive engine.
127267

128-
### 5. Under the Hood Part 2: The "Reactivity Bubbling" Killer Feature
268+
### 8. Under the Hood Part 2: The "Reactivity Bubbling" Killer Feature
129269

130270
This is where the Neo.mjs `state.Provider` truly shines and solves the "Context Tax." Consider this critical question:
131271

0 commit comments

Comments
 (0)