Skip to content

Commit f0960ba

Browse files
committed
finish off state as a snapshot
1 parent e35c8ad commit f0960ba

8 files changed

+162
-36
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import asyncio
2+
3+
from idom import component, html, run, use_state
4+
5+
6+
@component
7+
def Counter():
8+
number, set_number = use_state(0)
9+
10+
async def handle_click(event):
11+
set_number(number + 5)
12+
print("about to print...")
13+
await asyncio.sleep(3)
14+
print(number)
15+
16+
return html.div(
17+
html.h1(number),
18+
html.button({"onClick": handle_click}, "Increment"),
19+
)
20+
21+
22+
run(Counter)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from idom import component, html, run, use_state
2+
3+
4+
@component
5+
def Counter():
6+
number, set_number = use_state(0)
7+
8+
def handle_click(event):
9+
set_number(number + 5)
10+
print(number)
11+
12+
return html.div(
13+
html.h1(number),
14+
html.button({"onClick": handle_click}, "Increment"),
15+
)
16+
17+
18+
run(Counter)

docs/source/adding-interactivity/batched-updates.rst

-8
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Compounding State Updates 🚧
2+
============================
3+
4+
.. note::
5+
6+
Under construction 🚧

docs/source/adding-interactivity/index.rst

+13-12
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ Adding Interactivity
77
responding-to-events
88
components-with-state
99
state-as-a-snapshot
10+
compounding-state-updates
1011
dangers-of-mutability
11-
batched-updates
1212

1313

1414
.. dropdown:: :octicon:`bookmark-fill;2em` What You'll Learn
@@ -39,14 +39,14 @@ Adding Interactivity
3939
Learn why IDOM does not change component state the moment it is set, but
4040
instead schedules a re-render.
4141

42-
.. grid-item-card:: :octicon:`issue-opened` Dangers of Mutability
43-
:link: dangers-of-mutability
42+
.. grid-item-card:: :octicon:`versions` Compounding State Updates
43+
:link: compounding-state-updates
4444
:link-type: doc
4545

4646
Under construction 🚧
4747

48-
.. grid-item-card:: :octicon:`versions` Batched Updates
49-
:link: batched-updates
48+
.. grid-item-card:: :octicon:`issue-opened` Dangers of Mutability
49+
:link: dangers-of-mutability
5050
:link-type: doc
5151

5252
Under construction 🚧
@@ -147,14 +147,15 @@ snapshot.
147147
:octicon:`book` Read More
148148
^^^^^^^^^^^^^^^^^^^^^^^^^
149149

150-
...
150+
Learn why IDOM does not change component state the moment it is set, but instead
151+
schedules a re-render.
151152

152153

153-
Section 4: Dangers of Mutability
154-
--------------------------------
154+
Section 4: Compounding State Updates
155+
------------------------------------
155156

156157
.. card::
157-
:link: dangers-of-mutability
158+
:link: compounding-state-updates
158159
:link-type: doc
159160

160161
:octicon:`book` Read More
@@ -163,11 +164,11 @@ Section 4: Dangers of Mutability
163164
...
164165

165166

166-
Section 3: Batched Updates
167-
--------------------------
167+
Section 5: Dangers of Mutability
168+
--------------------------------
168169

169170
.. card::
170-
:link: batched-updates
171+
:link: dangers-of-mutability
171172
:link-type: doc
172173

173174
:octicon:`book` Read More

docs/source/adding-interactivity/responding-to-events.rst

+2
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ want to create a generic ``Button`` component that can be used for a variety of
8080
.. idom:: _examples/button_handler_as_arg
8181

8282

83+
.. _Async Event Handler:
84+
8385
Async Event Handlers
8486
--------------------
8587

docs/source/adding-interactivity/state-as-a-snapshot.rst

+100-15
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,18 @@ in that exact moment. As a result, the view returned by that component is itself
2929
snapshot of the UI at that time.
3030

3131

32-
Investigating State Snapshots
33-
-----------------------------
32+
Setting State Triggers Renders
33+
------------------------------
3434

35-
Let's experiment with some potentially less intuitive behaviors of state to see why we
36-
should think about it with respect to these "snapshots" in time. Take a look at the
37-
example below and try to guess how it will behave. **What will the count be after you
38-
click the "Increment" button?**
35+
Setting state does not impact the current render, instead it schedules a re-render. It's
36+
only in this subsequent render that changes to state take effect. As a result, setting
37+
state more than once in the context of the same render will not cause those changes to
38+
compound. This makes it easier to reason about how your UI will react to user
39+
interactions because state does not change until the next render.
40+
41+
Let's experiment with this behaviors of state to see why we should think about it with
42+
respect to these "snapshots" in time. Take a look at the example below and try to guess
43+
how it will behave. **What will the count be after you click the "Increment" button?**
3944

4045
.. idom:: _examples/set_counter_3_times
4146

@@ -49,25 +54,105 @@ happening inside the event handler to see why this is happening:
4954
set_count(count + 1)
5055
set_count(count + 1)
5156
52-
On the initial render of your ``Counter`` the ``number`` variable is ``0``. So we ought
53-
to be able to substitute ``number`` with ``0`` everywhere it's referenced within the
54-
component. Since that includes the event handler too we should be able to rewrite the
55-
three lines above as:
57+
On the initial render of your ``Counter`` the ``number`` variable is ``0``. Because we
58+
know that state variables do not change until the next render we ought to be able to
59+
substitute ``number`` with ``0`` everywhere it's referenced within the component until
60+
then. That includes the event handler too we should be able to rewrite the three lines
61+
above as:
5662

5763
.. code-block::
5864
5965
set_count(0 + 1)
6066
set_count(0 + 1)
6167
set_count(0 + 1)
6268
63-
Even though, we called ``set_count`` three times, every time we were actually just
64-
doing ``set_count(1)`` three times. Only after the event handler returns will IDOM
65-
actually perform the next render where count is ``1``. When it does, ``number`` will be
66-
``1`` and we'll be able to perform the same subtitution as before to see what the next
67-
number will be after we click "Increment":
69+
Even though, we called ``set_count`` three times with what might have seemed like
70+
different values, every time we were actually just doing ``set_count(1)`` on each call.
71+
Only after the event handler returns will IDOM actually perform the next render where
72+
count is ``1``. When it does, ``number`` will be ``1`` and we'll be able to perform the
73+
same subtitution as before to see what the next number will be after we click
74+
"Increment":
6875

6976
.. code-block::
7077
7178
set_count(1 + 1)
7279
set_count(1 + 1)
7380
set_count(1 + 1)
81+
82+
83+
State And Delayed Reactions
84+
---------------------------
85+
86+
Given what we :ref:`learned above <setting state triggers renders>`, we ought to be able
87+
to reason about what should happen in the example below. What will be printed when the
88+
"Increment" button is clicked?
89+
90+
.. idom:: _examples/print_count_after_set
91+
92+
If we use the same subtitution trick we saw before, we can rewrite these lines:
93+
94+
.. code-block::
95+
96+
set_number(number + 5)
97+
print(number)
98+
99+
Using the value of ``number`` in the initial render which is ``0``:
100+
101+
.. code-block::
102+
103+
set_number(0 + 5)
104+
print(0)
105+
106+
Thus when we click the button we should expect that the next render will show ``5``, but
107+
we will ``print`` the number ``0`` instead. The next time we click the view will show
108+
``10`` and the printout will be ``5``. In this sense the print statement, because it
109+
lives within the prior snapshot, trails what is displayed in the next render.
110+
111+
What if we slightly modify this example, by introducing a delay between when we call
112+
``set_number`` and when we print? Will this behavior remain the same? To add this delay
113+
we'll use an :ref:`async event handler` and :func:`~asyncio.sleep` for some time:
114+
115+
.. idom:: _examples/delayed_print_after_set
116+
117+
Even though the render completed before the print statement took place, the behavior
118+
remained the same! Despite the fact that the next render took place before the print
119+
statement did, the print statement still relies on the state snapshot from the initial
120+
render. Thus we can continue to use our substitution trick to analyze what's happening:
121+
122+
.. code-block::
123+
124+
set_number(0 + 5)
125+
print("about to print...")
126+
await asyncio.sleep(3)
127+
print(0)
128+
129+
This property of state, that it remains static within the context of particular render,
130+
while unintuitive at first, is actually an important tool for preventing subtle bugs.
131+
Let's consider the example below where there's a form that sends a message with a 5
132+
second delay. Imagine a scenario where the user:
133+
134+
1. Presses the "Send" button with the message "Hello" where "Alice" is the recipient.
135+
2. Then, before the five-second delay ends, the user changes the "To" field to "Bob".
136+
137+
The first question to ask is "What should happen?" In this case, the user's expectation
138+
is that after they press "Send", changing the recipient, even if the message has not
139+
been sent yet, should not impact where the message is ultimately sent. We then need to
140+
ask what actually happens. Will it print “You said Hello to Alice” or “You said Hello to
141+
Bob”?
142+
143+
.. idom:: _examples/print_chat_message
144+
145+
As it turns out, the code above matches the user's expectation. This is because IDOM
146+
keeps the state values fixed within the event handlers defined during a particular
147+
render. As a result, you don't need to worry about whether state has changed while
148+
code in an event handler is running.
149+
150+
.. card::
151+
:link: compounding-state-updates
152+
:link-type: doc
153+
154+
:octicon:`book` Read More
155+
^^^^^^^^^^^^^^^^^^^^^^^^^
156+
157+
What if you wanted to read the latest state values before the next render? You’ll
158+
want to use a state updater function, covered on the next page!

docs/source/credits-and-licenses.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ influence from https://reactjs.org which uses the `Creative Commons Attribution
66
International
77
<https://raw.githubusercontent.com/reactjs/reactjs.org/b2d5613b6ae20855ced7c83067b604034bebbb44/LICENSE-DOCS.md>`__
88
license. While many things have been transformed, we paraphrase and, in some places,
9-
copy language or examples where React's behavior is directly mirroried by IDOM's.
9+
copy language or examples where IDOM's behavior mirrors that of React's.
1010

1111

1212
Source Code License

0 commit comments

Comments
 (0)