Skip to content

Commit 4e6bc6c

Browse files
committed
change 'args' to the more explicit 'dependencies'
1 parent 161ee6a commit 4e6bc6c

File tree

3 files changed

+97
-78
lines changed

3 files changed

+97
-78
lines changed

docs/source/reference-material/hooks-api.rst

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ then closing a connection:
124124
.. code-block::
125125
126126
def establish_connection():
127-
connection = open_connection(url)
127+
connection = open_connection()
128128
return lambda: close_connection(connection)
129129
130130
use_effect(establish_connection)
@@ -139,19 +139,21 @@ Conditional Effects
139139
...................
140140

141141
By default, effects are triggered after every successful render to ensure that all state
142-
referenced by the effect is up to date. However you can limit the number of times an
143-
effect is fired by specifying exactly what state the effect depends on. In doing so
144-
the effect will only occur when the given state changes:
142+
referenced by the effect is up to date. However, when an effect function references
143+
non-global variables, the effect will only if the value of that variable changes. For
144+
example, imagine that we had an effect that connected to a ``url`` state variable:
145145

146146
.. code-block::
147147
148+
url, set_url = use_state("https://example.com")
149+
148150
def establish_connection():
149151
connection = open_connection(url)
150152
return lambda: close_connection(connection)
151153
152-
use_effect(establish_connection, [url])
154+
use_effect(establish_connection)
153155
154-
Now a new connection will only be established if a new ``url`` is provided.
156+
Here, a new connection will be established whenever a new ``url`` is set.
155157

156158

157159
Async Effects
@@ -181,6 +183,20 @@ There are **three important subtleties** to note about using asynchronous effect
181183
and before the next effect following a subsequent update.
182184

183185

186+
Manual Effect Conditions
187+
........................
188+
189+
In some cases, you may want to explicitely declare when an effect should be triggered.
190+
You can do this by passing ``dependencies`` to ``use_effect``. Each of the following values
191+
produce different effect behaviors:
192+
193+
- ``use_effect(..., dependencies=None)`` - triggers and cleans up on every render.
194+
- ``use_effect(..., dependencies=[])`` - only triggers on the first and cleans up after
195+
the last render.
196+
- ``use_effect(..., dependencies=[x, y])`` - triggers on the first render and on subsequent renders if
197+
``x`` or ``y`` have changed.
198+
199+
184200
Supplementary Hooks
185201
===================
186202

@@ -221,38 +237,32 @@ Use Callback
221237

222238
.. code-block::
223239
224-
memoized_callback = use_callback(lambda: do_something(a, b), [a, b])
240+
memoized_callback = use_callback(lambda: do_something(a, b))
225241
226242
A derivative of :ref:`Use Memo`, the ``use_callback`` hook returns a
227243
`memoized <memoization>`_ callback. This is useful when passing callbacks to child
228-
components which check reference equality to prevent unnecessary renders. The of
229-
``memoized_callback`` will only change when the given dependencies do.
244+
components which check reference equality to prevent unnecessary renders. The
245+
``memoized_callback`` will only change when any local variables is references do.
230246

231247
.. note::
232248

233-
The list of "dependencies" are not passed as arguments to the function. Ostensibly
234-
though, that is what they represent. Thus any variable referenced by the function
235-
must be listed as dependencies. We're
236-
`working on a linter <https://github.com/idom-team/idom/issues/202>`_ to help
237-
enforce this.
238-
249+
You may manually specify what values the callback depends on in the :ref:`same way
250+
as effects <Manual Effect Conditions>` using the ``args`` parameter.
239251

240252

241253
Use Memo
242254
--------
243255

244256
.. code-block::
245257
246-
memoized_value = use_memo(lambda: compute_something_expensive(a, b), [a, b])
258+
memoized_value = use_memo(lambda: compute_something_expensive(a, b))
247259
248260
Returns a `memoized <memoization>`_ value. By passing a constructor function accepting
249261
no arguments and an array of dependencies for that constructor, the ``use_callback``
250262
hook will return the value computed by the constructor. The ``memoized_value`` will only
251-
be recomputed when a value in the array of dependencies changes. This optimizes
252-
performance because you don't need to ``compute_something_expensive`` on every render.
253-
254-
If the array of dependencies is ``None`` then the constructor will be called on every
255-
render.
263+
be recomputed if a local variable referenced by the constructor changes (e.g. ``a`` or
264+
``b`` here). This optimizes performance because you don't need to
265+
``compute_something_expensive`` on every render.
256266

257267
Unlike ``use_effect`` the constructor function is called during each render (instead of
258268
after) and should not incur side effects.
@@ -265,11 +275,8 @@ after) and should not incur side effects.
265275

266276
.. note::
267277

268-
The list of "dependencies" are not passed as arguments to the function ostensibly
269-
though, that is what they represent. Thus any variable referenced by the function
270-
must be listed as dependencies. We're
271-
`working on a linter <https://github.com/idom-team/idom/issues/202>`_
272-
to help enforce this.
278+
You may manually specify what values the callback depends on in the :ref:`same way
279+
as effects <Manual Effect Conditions>` using the ``args`` parameter.
273280

274281

275282
Use Ref

src/idom/core/hooks.py

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -109,39 +109,41 @@ def dispatch(
109109
@overload
110110
def use_effect(
111111
function: None = None,
112-
args: Sequence[Any] | ellipsis | None = ...,
112+
dependencies: Sequence[Any] | ellipsis | None = ...,
113113
) -> Callable[[_EffectApplyFunc], None]:
114114
...
115115

116116

117117
@overload
118118
def use_effect(
119119
function: _EffectApplyFunc,
120-
args: Sequence[Any] | ellipsis | None = ...,
120+
dependencies: Sequence[Any] | ellipsis | None = ...,
121121
) -> None:
122122
...
123123

124124

125125
def use_effect(
126126
function: Optional[_EffectApplyFunc] = None,
127-
args: Sequence[Any] | ellipsis | None = ...,
127+
dependencies: Sequence[Any] | ellipsis | None = ...,
128128
) -> Optional[Callable[[_EffectApplyFunc], None]]:
129129
"""See the full :ref:`Use Effect` docs for details
130130
131131
Parameters:
132132
function:
133133
Applies the effect and can return a clean-up function
134-
args:
135-
Dependencies for the effect. If provided the effect will only trigger when
136-
these args change.
134+
dependencies:
135+
Dependencies for the effect. The effect will only trigger if the identity
136+
of any value in the given sequence changes (i.e. their :func:`id` is
137+
different). By default these are inferred based on local variables that are
138+
referenced by the given function.
137139
138140
Returns:
139141
If not function is provided, a decorator. Otherwise ``None``.
140142
"""
141143
hook = current_hook()
142144

143-
args = _try_to_infer_closure_args(function, args)
144-
memoize = use_memo(args=args)
145+
dependencies = _try_to_infer_closure_values(function, dependencies)
146+
memoize = use_memo(dependencies=dependencies)
145147
last_clean_callback: Ref[Optional[_EffectCleanFunc]] = use_ref(None)
146148

147149
def add_effect(function: _EffectApplyFunc) -> None:
@@ -221,34 +223,39 @@ def dispatch(action: _ActionType) -> None:
221223
@overload
222224
def use_callback(
223225
function: None = None,
224-
args: Sequence[Any] | ellipsis | None = ...,
226+
dependencies: Sequence[Any] | ellipsis | None = ...,
225227
) -> Callable[[_CallbackFunc], _CallbackFunc]:
226228
...
227229

228230

229231
@overload
230232
def use_callback(
231233
function: _CallbackFunc,
232-
args: Sequence[Any] | ellipsis | None = ...,
234+
dependencies: Sequence[Any] | ellipsis | None = ...,
233235
) -> _CallbackFunc:
234236
...
235237

236238

237239
def use_callback(
238240
function: Optional[_CallbackFunc] = None,
239-
args: Sequence[Any] | ellipsis | None = ...,
241+
dependencies: Sequence[Any] | ellipsis | None = ...,
240242
) -> Union[_CallbackFunc, Callable[[_CallbackFunc], _CallbackFunc]]:
241243
"""See the full :ref:`Use Callback` docs for details
242244
243245
Parameters:
244-
function: the function whose identity will be preserved
245-
args: The identity the ``function`` will be udpated when these ``args`` change.
246+
function:
247+
The function whose identity will be preserved
248+
dependencies:
249+
Dependencies of the callback. The identity the ``function`` will be udpated
250+
if the identity of any value in the given sequence changes (i.e. their
251+
:func:`id` is different). By default these are inferred based on local
252+
variables that are referenced by the given function.
246253
247254
Returns:
248255
The current function
249256
"""
250-
args = _try_to_infer_closure_args(function, args)
251-
memoize = use_memo(args=args)
257+
dependencies = _try_to_infer_closure_values(function, dependencies)
258+
memoize = use_memo(dependencies=dependencies)
252259

253260
def setup(function: _CallbackFunc) -> _CallbackFunc:
254261
return memoize(lambda: function)
@@ -269,49 +276,54 @@ def __call__(self, func: Callable[[], _StateType]) -> _StateType:
269276
@overload
270277
def use_memo(
271278
function: None = None,
272-
args: Sequence[Any] | ellipsis | None = ...,
279+
dependencies: Sequence[Any] | ellipsis | None = ...,
273280
) -> _LambdaCaller:
274281
...
275282

276283

277284
@overload
278285
def use_memo(
279286
function: Callable[[], _StateType],
280-
args: Sequence[Any] | ellipsis | None = ...,
287+
dependencies: Sequence[Any] | ellipsis | None = ...,
281288
) -> _StateType:
282289
...
283290

284291

285292
def use_memo(
286293
function: Optional[Callable[[], _StateType]] = None,
287-
args: Sequence[Any] | ellipsis | None = ...,
294+
dependencies: Sequence[Any] | ellipsis | None = ...,
288295
) -> Union[_StateType, Callable[[Callable[[], _StateType]], _StateType]]:
289296
"""See the full :ref:`Use Memo` docs for details
290297
291298
Parameters:
292-
function: The function to be memoized.
293-
args: The ``function`` will be recomputed when these args change.
299+
function:
300+
The function to be memoized.
301+
dependencies:
302+
Dependencies for the memoized function. The memo will only be recomputed if
303+
the identity of any value in the given sequence changes (i.e. their
304+
:func:`id` is different). By default these are inferred based on local
305+
variables that are referenced by the given function.
294306
295307
Returns:
296308
The current state
297309
"""
298-
args = _try_to_infer_closure_args(function, args)
310+
dependencies = _try_to_infer_closure_values(function, dependencies)
299311

300312
memo: _Memo[_StateType] = _use_const(_Memo)
301313

302314
if memo.empty():
303315
# we need to initialize on the first run
304316
changed = True
305-
memo.args = () if args is None else args
306-
elif args is None:
317+
memo.deps = () if dependencies is None else dependencies
318+
elif dependencies is None:
307319
changed = True
308-
memo.args = ()
320+
memo.deps = ()
309321
elif (
310-
len(memo.args) != len(args)
311-
# if args are same length check identity for each item
312-
or any(current is not new for current, new in zip(memo.args, args))
322+
len(memo.deps) != len(dependencies)
323+
# if deps are same length check identity for each item
324+
or any(current is not new for current, new in zip(memo.deps, dependencies))
313325
):
314-
memo.args = args
326+
memo.deps = dependencies
315327
changed = True
316328
else:
317329
changed = False
@@ -338,10 +350,10 @@ def setup(function: Callable[[], _StateType]) -> _StateType:
338350
class _Memo(Generic[_StateType]):
339351
"""Simple object for storing memoization data"""
340352

341-
__slots__ = "value", "args"
353+
__slots__ = "value", "deps"
342354

343355
value: _StateType
344-
args: Sequence[Any]
356+
deps: Sequence[Any]
345357

346358
def empty(self) -> bool:
347359
try:
@@ -368,11 +380,11 @@ def _use_const(function: Callable[[], _StateType]) -> _StateType:
368380
return current_hook().use_state(function)
369381

370382

371-
def _try_to_infer_closure_args(
383+
def _try_to_infer_closure_values(
372384
func: Callable[..., Any] | None,
373-
args: Sequence[Any] | ellipsis | None,
385+
values: Sequence[Any] | ellipsis | None,
374386
) -> Sequence[Any] | None:
375-
if args is ...:
387+
if values is ...:
376388
if isinstance(func, FunctionType):
377389
return (
378390
[cell.cell_contents for cell in func.__closure__]
@@ -382,7 +394,7 @@ def _try_to_infer_closure_args(
382394
else:
383395
return None
384396
else:
385-
return cast("Sequence[Any] | None", args)
397+
return cast("Sequence[Any] | None", values)
386398

387399

388400
_current_life_cycle_hook: Dict[int, "LifeCycleHook"] = {}

0 commit comments

Comments
 (0)