@@ -29,13 +29,18 @@ in that exact moment. As a result, the view returned by that component is itself
29
29
snapshot of the UI at that time.
30
30
31
31
32
- Investigating State Snapshots
33
- -----------------------------
32
+ Setting State Triggers Renders
33
+ ------------------------------
34
34
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? **
39
44
40
45
.. idom :: _examples/set_counter_3_times
41
46
@@ -49,25 +54,105 @@ happening inside the event handler to see why this is happening:
49
54
set_count(count + 1)
50
55
set_count(count + 1)
51
56
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:
56
62
57
63
.. code-block ::
58
64
59
65
set_count(0 + 1)
60
66
set_count(0 + 1)
61
67
set_count(0 + 1)
62
68
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":
68
75
69
76
.. code-block ::
70
77
71
78
set_count(1 + 1)
72
79
set_count(1 + 1)
73
80
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!
0 commit comments