|
| 1 | +Multiple State Updates |
| 2 | +====================== |
| 3 | + |
| 4 | +Setting a state variable will queue another render. But sometimes you might want to |
| 5 | +perform multiple operations on the value before queueing the next render. To do this, it |
| 6 | +helps to understand how React batches state updates. |
| 7 | + |
| 8 | + |
| 9 | +Batched Updates |
| 10 | +--------------- |
| 11 | + |
| 12 | +As we learned :ref:`previously <state as a snapshot>`, state variables remain fixed |
| 13 | +inside each render as if state were a snapshot taken at the begining of each render. |
| 14 | +This is why, in the example below, even though it might seem like clicking the |
| 15 | +"Increment" button would cause the ``number`` to increase by ``3``, it only does by |
| 16 | +``1``: |
| 17 | + |
| 18 | +.. idom:: _examples/set_counter_3_times |
| 19 | + |
| 20 | +The reason this happens is because, so long as the event handler is synchronous (i.e. |
| 21 | +the event handler is not an ``async`` function), IDOM waits until all the code in an |
| 22 | +event handler has run before processing state and starting the next render. Thus, it's |
| 23 | +the last call to a given state setter that matters. In the example below, even though we |
| 24 | +set the color of the button to ``"orange"`` and then ``"pink"`` before ``"blue"``, |
| 25 | +the color does not quickly flash orange and pink before blue - it alway remains blue: |
| 26 | + |
| 27 | +.. idom:: _examples/set_color_3_times |
| 28 | + |
| 29 | +This behavior let's you make multiple state changes without triggering unnecessary |
| 30 | +renders or renders with inconsistent state where only some of the variables have been |
| 31 | +updated. With that said, it also means that the UI won't change until after synchronous |
| 32 | +handlers have finished running. |
| 33 | + |
| 34 | +.. note:: |
| 35 | + |
| 36 | + For asynchronous event handlers, IDOM will not render until you ``await`` something. |
| 37 | + As we saw in :ref:`prior examples <State And Delayed Reactions>`, if you introduce |
| 38 | + an asynchronous delay to an event handler after changing state, renders may take |
| 39 | + place before the remainder of the event handler completes. However, state variables |
| 40 | + within handlers, even async ones, always remains static. |
| 41 | + |
| 42 | +This behavior of IDOM to "batch" state changes that take place inside a single event |
| 43 | +handler, do not extend across event handlers. In other words, distinct events will |
| 44 | +always produce distinct renders. For example, if clicking a button increments a counter |
| 45 | +by one, no matter how fast the user clicks, the view will never jump from 1 to 3 - it |
| 46 | +will always display 1, then 2, and then 3. |
| 47 | + |
| 48 | + |
| 49 | +Incremental Updates |
| 50 | +------------------- |
| 51 | + |
| 52 | +While it's uncommon, you need to update a state variable more than once before the next |
| 53 | +render. In these cases, instead of having updates batched, you instead want them to be |
| 54 | +applied incrementally. That is, the next update can be made to depend on the prior one. |
| 55 | +For example, what it we wanted to make it so that, in our ``Counter`` example :ref:`from |
| 56 | +before <Batched Updates>`, each call to ``set_number`` did in fact increment |
| 57 | +``number`` by one causing the view to display ``0``, then ``3``, then ``6``, and so on? |
| 58 | + |
| 59 | +To accomplish this, instead of passing the next state value as in ``set_number(number + |
| 60 | +1)``, we may pass an **"updater function"** to ``set_number`` that computes the next |
| 61 | +state based on the previous state. This would look like ``set_number(lambda number: |
| 62 | +number + 1)``. In other words we need a function of the form: |
| 63 | + |
| 64 | +.. code-block:: |
| 65 | +
|
| 66 | + def compute_new_state(old_state): |
| 67 | + ... |
| 68 | + return new_state |
| 69 | +
|
| 70 | +In our case, ``new_state = old_state + 1``. So we might define: |
| 71 | + |
| 72 | +.. code-block:: |
| 73 | +
|
| 74 | + def increment(old_number): |
| 75 | + new_number = old_number + 1 |
| 76 | + return new_number |
| 77 | +
|
| 78 | +Which we can use to replace ``set_number(number + 1)`` with ``set_number(increment)``: |
| 79 | + |
| 80 | +.. idom:: _examples/set_state_function |
| 81 | + |
| 82 | +The way to think about how IDOM runs though this series of ``set_state(increment)`` |
| 83 | +calls is to imagine that each one updates the internally managed state with its return |
| 84 | +value, then that return value is being passed to the next updater function. Ultimately, |
| 85 | +this is functionally equivalent to the following: |
| 86 | + |
| 87 | +.. code-block:: |
| 88 | +
|
| 89 | + set_number(increment(increment(increment(number)))) |
| 90 | +
|
| 91 | +So why might you want to do this? Well, in this case, we're using the same function on |
| 92 | +each call to ``set_number``, but what if you had more function. Perhaps you might want |
| 93 | +to do introduce ``squared`` or ``decrement`` functions: |
| 94 | + |
| 95 | +.. code-block:: |
| 96 | +
|
| 97 | + set_number(increment) |
| 98 | + set_number(squared) |
| 99 | + set_number(decrement) |
| 100 | +
|
| 101 | +Which would equate to: |
| 102 | + |
| 103 | +.. code-block:: |
| 104 | +
|
| 105 | + set_number(decrement(squared(increment(number)))) |
| 106 | +
|
| 107 | +This example also presents a scenario with much simpler state. Consider an example where |
| 108 | +the state is more complex. In the scenario below, we need to represent and manipulate |
| 109 | +state that represents the position of a character in a scene. Then imagine that we want |
| 110 | +to allow the user to queue their actions and then apply them all at once. The simplest |
| 111 | +way to do this is to factor out the functions which manualuate this state into |
| 112 | +functions, add them to an ``actions`` queue when the user requests, and finally, once |
| 113 | +the user clicks "Apply Actions", iterator over the ``actions`` and call ``set_position`` |
| 114 | +with each action function: |
| 115 | + |
| 116 | +.. idom:: _examples/character_movement |
0 commit comments