@@ -89,7 +89,9 @@ Use Effect
89
89
90
90
.. code-block ::
91
91
92
- use_effect(did_render)
92
+ @use_effect
93
+ def did_render():
94
+ ... # imperative or state mutating logic
93
95
94
96
The ``use_effect `` hook accepts a function which may be imperative, or mutate state. The
95
97
function will be called immediately after the layout has fully updated.
@@ -117,12 +119,11 @@ then closing a connection:
117
119
118
120
.. code-block ::
119
121
122
+ @use_effect
120
123
def establish_connection():
121
124
connection = open_connection()
122
125
return lambda: close_connection(connection)
123
126
124
- use_effect(establish_connection)
125
-
126
127
The clean-up function will be run before the component is unmounted or, before the next
127
128
effect is triggered when the component re-renders. You can
128
129
:ref: `conditionally fire events <Conditional Effects >` to avoid triggering them each
@@ -141,14 +142,19 @@ example, imagine that we had an effect that connected to a ``url`` state variabl
141
142
142
143
url, set_url = use_state("https://example.com")
143
144
145
+ @use_effect
144
146
def establish_connection():
145
147
connection = open_connection(url)
146
148
return lambda: close_connection(connection)
147
149
148
- use_effect(establish_connection)
149
-
150
150
Here, a new connection will be established whenever a new ``url `` is set.
151
151
152
+ .. warning ::
153
+
154
+ A component will be unable to render until all its outstanding effects have been
155
+ cleaned up. As such, it's best to keep cleanup logic as simple as possible and/or
156
+ to impose a time limit.
157
+
152
158
153
159
Async Effects
154
160
.............
@@ -160,48 +166,52 @@ simply write an async function.
160
166
161
167
.. code-block ::
162
168
163
- async def non_blocking_effect():
169
+ @use_effect
170
+ async def my_async_effect():
164
171
await do_something()
165
172
166
- use_effect(non_blocking_effect)
167
-
168
- However, if you need to do any cleanup, then you must ``yield False `` inside a try-finally
169
- block and place your cleanup logic in the finally block. Yielding ``False `` indicates to
170
- ReactPy that the effect will not yield again before it is cleaned up.
173
+ However, if you need to do any cleanup, then you'll need to write an async generator
174
+ instead. The generator should run the effect logic in a ``try `` block, ``yield `` control
175
+ back to ReactPy, and then run the cleanup logic in a ``finally `` block:
171
176
172
177
.. code-block ::
173
178
174
- async def blocking_effect():
175
- await do_something()
179
+ @use_effect
180
+ async def my_async_effect():
176
181
try:
177
- yield False
182
+ await effect_logic()
183
+ yield
178
184
finally:
179
- await do_cleanup()
180
-
181
- use_effect(blocking_effect)
185
+ await cleanup_logic()
182
186
183
- If you have a long-lived effect, you may ``yield True `` multiple times. ``True `` indicates
184
- to ReactPy that the effect will yield again if the effect doesn't need to be cleanup up
185
- yet.
187
+ When a component is re-rendered or unmounted the effect will be cancelled if it is still
188
+ running. This will typically happen for long-lived effects. One example might be an
189
+ effect that opens a connection and then responds to messages for the lifetime of the
190
+ connection:
186
191
187
192
.. code-block ::
188
193
189
- async def establish_connection():
190
- connection = await open_connection()
194
+ @use_effect
195
+ async def my_async_effect():
196
+ conn = await open_connection()
191
197
try:
192
198
while True:
193
- yield False
194
- await connection.send(create_message())
195
- handle_message(await connection.recv())
199
+ msg = await conn.recv()
200
+ await handle_message(msg)
196
201
finally:
197
- await close_connection(connection)
202
+ await close_connection(conn)
203
+
204
+ .. warning ::
205
+
206
+ Because an effect can be cancelled at any time, it's possible that the cleanup logic
207
+ will run before all of the effect logic has finished. For example, in the code
208
+ above, we exclude ``conn = await open_connection() `` from the ``try `` block because
209
+ if the effect is cancelled before the connection is opened, then we don't need to
210
+ close it.
198
211
199
- use_effect(non_blocking_effect)
212
+ .. note ::
200
213
201
- Note that, if an effect needs to be cleaned up, it will only do so when the effect
202
- function yields control back to ReactPy. So you should ensure that either, you can
203
- be sure that the effect will yield in a timely manner, or that you enforce a timeout
204
- on the effect. Otherwise, ReactPy may hang while waiting for the effect to yield.
214
+ We don't need a yield statement here because the effect only ends when it's cancelled.
205
215
206
216
207
217
Manual Effect Conditions
0 commit comments