|
| 1 | +--- |
| 2 | +templateKey: "blog-post" |
| 3 | +title: "Why Components -> State -> setState() in React" |
| 4 | +date: 2020-03-26 |
| 5 | +featuredpost: false |
| 6 | +description: >- |
| 7 | + A webpage as a collection of React Components and a Component as an implementation of Functionality and UI with State as a medium |
| 8 | +keywords: |
| 9 | + - javascript |
| 10 | + - react |
| 11 | +image: ./images/header.png |
| 12 | +link: /why-components-state-and-setstate-in-react |
| 13 | +category: |
| 14 | + - Tutorial |
| 15 | +tags: |
| 16 | + - javascript |
| 17 | + - react |
| 18 | +author: Kalidas |
| 19 | +--- |
| 20 | + |
| 21 | +_A webpage as a collection of React Components and a Component as an implementation of Functionality and UI with State as a medium_ |
| 22 | + |
| 23 | +## Why Components |
| 24 | + |
| 25 | +Imagine you have to build a wall of a hundred feet dimensions. You can start feeling overwhelmed on how it’s going to span out before even you start. |
| 26 | + |
| 27 | +Instead, you can focus on laying brick one by one. One after the other. Once you’ve laid all the required bricks, you would be standing in front of a magnificent wall built by you! |
| 28 | + |
| 29 | +Visualize the wall you built, as a single complex webpage and every single brick you laid, as a React Component. |
| 30 | + |
| 31 | +React Components aids you to visualize complex UI as a collection of independent and reusable bricks. Rather than focusing on an entire complex UI, you can focus on implementing each Component in isolation. Once all the required components are implemented, they can be connected to form the desired webpage (UI). |
| 32 | + |
| 33 | +## Why State in Component |
| 34 | + |
| 35 | +Assume a simple webpage with a button having the following business logic |
| 36 | + |
| 37 | +1. When the button is clicked, the webpage should show a spinner and fetch data from a URL. |
| 38 | +2. When the data is fetched, the spinner should be hidden and fetched data should be displayed. |
| 39 | + |
| 40 | +<iframe height="448" style="width: 100%;" scrolling="no" title="React - Display API Data With Spinner" src="https://codepen.io/kalidasm/embed/RwNXQxw?height=448&theme-id=dark&default-tab=js,result" frameborder="no" allowtransparency="true" allowfullscreen="true"> |
| 41 | + See the Pen <a href='https://codepen.io/kalidasm/pen/RwNXQxw'>React - Display API Data With Spinner</a> by Kalidas M |
| 42 | + (<a href='https://codepen.io/kalidasm'>@kalidasm</a>) on <a href='https://codepen.io'>CodePen</a>. |
| 43 | +</iframe> |
| 44 | + |
| 45 | +#### Minimalistic version of how it can be implemented in React |
| 46 | + |
| 47 | +```jsx |
| 48 | + state = { |
| 49 | + data: {}, |
| 50 | + loading: false, |
| 51 | + } |
| 52 | + |
| 53 | + enableSpinner = () => { |
| 54 | + this.setState({ |
| 55 | + loading: true, |
| 56 | + }); |
| 57 | + } |
| 58 | + |
| 59 | + disableSpinner = () => { |
| 60 | + this.setState({ |
| 61 | + loading: false, |
| 62 | + }) |
| 63 | + } |
| 64 | + |
| 65 | + handleButtonClick = async (_event) => { |
| 66 | + this.enableSpinner(); |
| 67 | + |
| 68 | + const apiData = await this.fetchAPIData(); |
| 69 | + |
| 70 | + this.setState({ |
| 71 | + data: apiData, |
| 72 | + }, () => { |
| 73 | + this.disableSpinner(); |
| 74 | + }) |
| 75 | + } |
| 76 | + |
| 77 | + render() { |
| 78 | + const { loading, data } = this.state; |
| 79 | + |
| 80 | + return ( |
| 81 | + <body> |
| 82 | + { |
| 83 | + loading ? ( |
| 84 | + <Spinner /> |
| 85 | + ) : ( |
| 86 | + <div> |
| 87 | + <button onClick={this.handleButtonClick} /> |
| 88 | + {data} |
| 89 | + </div> |
| 90 | + ) |
| 91 | + } |
| 92 | + </body> |
| 93 | + ); |
| 94 | + } |
| 95 | +``` |
| 96 | + |
| 97 | +Notice how the **functionality** `handleButtonClick, fetchAPIData, enableSpinner, disableSpinner` and **visual representation of the functionality** `render()` has been implemented in complete isolation inside the Component. Inside a Component, this isolated implementation of the functionality and the visual representation is made possible using State. |
| 98 | + |
| 99 | +Based on the internal/user events such as button clicks, the Functionality Manager `handleButtonClick()` _will operate on the state_. Whenever the state has been changed, the Visual Manager `render()` will _consume the updated state and paint UI_ on the screen accordingly (re-rendering). |
| 100 | + |
| 101 | +#### Functionality |
| 102 | + |
| 103 | +Before initiating an API call, set the loading to true and after fetching the data, set the API Data to state and set loading to false. |
| 104 | + |
| 105 | +#### Visual Representation (UI) |
| 106 | + |
| 107 | +In `render()` function, when loading is true, show the spinner. When loading is false, hide the spinner, display the button and API Data. |
| 108 | + |
| 109 | +In this way, the functionality does not need to focus on visual representation and vice-versa. |
| 110 | + |
| 111 | +> #### Component’s State acts as a medium between Functionality Manager and Visual Manager |
| 112 | +> |
| 113 | +> #### A component can be viewed as a combination of functionality and visual representation. A webpage can be viewed as a collection of components. |
| 114 | +
|
| 115 | +_How State Connects Functionality and Rendering_ |
| 116 | + |
| 117 | +### Characteristics of Data which deserves State |
| 118 | + |
| 119 | +- **Unpredictability **— When a dataset cannot be computed from other parts of the dataset and when a dataset is received from an external source. |
| 120 | + |
| 121 | +Example: _(Name entered by the user in a Text Field)_. It’s not possible to foretell what the user is going to enter. Hence, the input from the text field deserves the Component’s State. |
| 122 | + |
| 123 | +Another example is _API Data from our example_. The Component can never predict what data it is going to receive from the remote endpoint. |
| 124 | + |
| 125 | +- **Changes Over Time** — When a dataset is not constant throughout. When a dataset changes its value over a period of time. |
| 126 | + |
| 127 | +Example: _(`state.loading` from our example)_. The loading state is based on the initiation (true) and completion (false) of API Request. The value of loading state changes between false and true over a period of time. |
| 128 | + |
| 129 | +Even though the data is predictable (true or false at any point in time), the Component can never know the exact time when the loading will be set to true (when the end-user clicks the button) and it can also never know the exact time when the loading will be set to false (when the API Request will return the response). Hence, the loading data deserves the state. |
| 130 | + |
| 131 | +--- |
| 132 | + |
| 133 | +## Why setState() |
| 134 | + |
| 135 | +Why we need to use `setState(newState)` function instead of changing the values by assigning the new values directly to `this.state = newState` ? |
| 136 | + |
| 137 | +Internally, React operates on Component as a JavaScript Object. Updating the Component’s State triggers the re-rendering of the Component — Means React has to listen to the changes on `this.state` and invoke render function once the contents of `this.state` has been changed. |
| 138 | + |
| 139 | +Is it possible to listen to the changes made in a JavaScript object key *without creating a function? — *Unfortunately, no :( |
| 140 | + |
| 141 | +Assume there is a simple JavaScript object with a key named state . Whenever the value of `state` has been changed, a function named `render` has to be called. |
| 142 | + |
| 143 | +> A [JavaScript Setter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set) has to be used to execute a function whenever a specific property of an object is attempted to be changed. |
| 144 | +
|
| 145 | +```javascript |
| 146 | +const componentInstance = { |
| 147 | + _state: null, |
| 148 | + |
| 149 | + set state(value) { |
| 150 | + this._state = value |
| 151 | + this.render() |
| 152 | + }, |
| 153 | + |
| 154 | + render: function() { |
| 155 | + console.log("Updated State:", this._state) |
| 156 | + }, |
| 157 | +} |
| 158 | + |
| 159 | +componentInstance.state = { message: "Hello" } |
| 160 | +componentInstance.state = { message: "Hello World" } |
| 161 | +``` |
| 162 | + |
| 163 | + |
| 164 | + |
| 165 | +With this analogy, one can treat setState as an updater and listener for the changes in this.state. |
| 166 | + |
| 167 | +Now, you may be wondering why not React define a setter so that `this.state` can be updated directly like how `componentInstance.state = {}` has been updated. |
| 168 | + |
| 169 | +Based on the execution context, React’s `setState()` will update the state synchronously or asynchronously. Also, the React Component goes through a few [lifecycle methods](https://reactjs.org/docs/react-component.html#the-component-lifecycle) before rendering or re-rendering. Hence, let’s explore `setState()` in detail. |
| 170 | + |
| 171 | +### Synchronous or Asynchronous — Dilemma |
| 172 | + |
| 173 | +Let’s take a counter application example. |
| 174 | + |
| 175 | +<iframe height="428" style="width: 100%;" scrolling="no" title="Multiple setState calls - Differential Nature" src="https://codepen.io/kalidasm/embed/BayXBzK?height=428&theme-id=dark&default-tab=js,result" frameborder="no" allowtransparency="true" allowfullscreen="true"> |
| 176 | + See the Pen <a href='https://codepen.io/kalidasm/pen/BayXBzK'>Multiple setState calls - Differential Nature</a> by Kalidas M |
| 177 | + (<a href='https://codepen.io/kalidasm'>@kalidasm</a>) on <a href='https://codepen.io'>CodePen</a>. |
| 178 | +</iframe> |
| 179 | + |
| 180 | +```jsx |
| 181 | +incrementCounterBy3 = () => { |
| 182 | + this.setState({ |
| 183 | + count: this.state.count + 1, |
| 184 | + }) |
| 185 | + this.setState({ |
| 186 | + count: this.state.count + 1, |
| 187 | + }) |
| 188 | + this.setState({ |
| 189 | + count: this.state.count + 1, |
| 190 | + }) |
| 191 | +} |
| 192 | +``` |
| 193 | + |
| 194 | +Each `setState()` is fetching the existing count in the state and increment the count by 1. As there are three `setState()` calls, the count state should be incremented by 3 (0 + 1 + 1 + 1). And that is what exactly happens with |
| 195 | + |
| 196 | +```jsx |
| 197 | + componentDidMount(){ |
| 198 | + setTimeout(() => { |
| 199 | + this.incrementCounterBy3(); |
| 200 | + }, 2000); |
| 201 | + } |
| 202 | +``` |
| 203 | + |
| 204 | +But, when you try to increment the counter by 3 by clicking the button, it always increments the value by 1. |
| 205 | + |
| 206 | +```jsx |
| 207 | + handleIncrement = () => { |
| 208 | + this.incrementCounterBy3(); |
| 209 | + }; |
| 210 | + |
| 211 | + render() { |
| 212 | + return( |
| 213 | + /* Other Code */ |
| 214 | + |
| 215 | + <button onClick={this.handleIncrement}>Increment by 3</button> |
| 216 | + |
| 217 | + /* Other Code */ |
| 218 | + ); |
| 219 | + } |
| 220 | +``` |
| 221 | + |
| 222 | +Both scenarios use `incrementCounterBy3()` to update the state. But the results are different. |
| 223 | + |
| 224 | +#### Why? |
| 225 | + |
| 226 | +The nature of the `setState()` is purely dependent on the execution context. _State Updates inside event handlers context are batched by default._ The button click produced an incorrect response because the `setState()` is executed under the `onClick` event context. |
| 227 | + |
| 228 | +The reason [why `setState()` is asynchronous inside the event handlers](https://github.com/facebook/react/issues/11527#issuecomment-360199710) may require us to dive into Parent and Child components and how `props` are also used to re-render the components. Hence, we can conclude asynchronous state updates prevent components from unnecessary re-rendering and improve rendering performance. |
| 229 | + |
| 230 | +#### How State is Updated During Batching |
| 231 | + |
| 232 | +When the button `Increment By 3` is clicked, it calls `handleIncrement()` which in turn calls `incrementCounterBy3()` . This function call, in turn, calls three `setState()` statements. As the state update has been batched, this is how React would update the state. |
| 233 | + |
| 234 | +```jsx |
| 235 | +this.state = { |
| 236 | + count: this.state.count + 1, |
| 237 | + count: this.state.count + 1, |
| 238 | + count: this.state.count + 1, |
| 239 | +} |
| 240 | +``` |
| 241 | + |
| 242 | +When an object assignment contains repetitive single key assignments, the latest key assignment will take over. |
| 243 | + |
| 244 | +_Latest key assignment is considered when the same key is assigned multiple times_ |
| 245 | + |
| 246 | +For more clarity, |
| 247 | + |
| 248 | +_Latest key assignment is considered when the same key is assigned multiple times_ |
| 249 | + |
| 250 | +### Dealing with asynchronous setState() |
| 251 | + |
| 252 | +As the nature of `setState()` is always dependent on its execution context, it’s always safe to assume `setState()` as asynchronous. If we assume that way, how to handle multiple `setState()` calls and produce the intended result? |
| 253 | + |
| 254 | +_If the Component’s state has to be updated based on the previous state, **an updater function** should be used in the `setState()` rather than a piece of the state object._ |
| 255 | + |
| 256 | +```jsx |
| 257 | + this.setState((prevState) => ( |
| 258 | + count: prevState.count + 1, |
| 259 | + )); |
| 260 | +``` |
| 261 | + |
| 262 | +> For simplicity, the updater function can be treated as a pure function which receives state object as an argument and returns a new object to be merged with the existing state. |
| 263 | +
|
| 264 | +```jsx |
| 265 | +const incrementStateCount = prevState => { |
| 266 | + return { |
| 267 | + count: prevState.count + 1, |
| 268 | + } |
| 269 | +} |
| 270 | + |
| 271 | +this.setState(incrementStateCount) |
| 272 | +``` |
| 273 | + |
| 274 | +Applying the updater function concept in our counter app example makes multiple `setState()` calls function like a charm. |
| 275 | + |
| 276 | +<iframe height="265" style="width: 100%;" scrolling="no" title="Multiple setState calls - With Updater Functions" src="https://codepen.io/kalidasm/embed/rNaXWEo?height=265&theme-id=dark&default-tab=js,result" frameborder="no" allowtransparency="true" allowfullscreen="true"> |
| 277 | + See the Pen <a href='https://codepen.io/kalidasm/pen/rNaXWEo'>Multiple setState calls - With Updater Functions</a> by Kalidas M |
| 278 | + (<a href='https://codepen.io/kalidasm'>@kalidasm</a>) on <a href='https://codepen.io'>CodePen</a>. |
| 279 | +</iframe> |
| 280 | + |
| 281 | +Passing a function to the `setState()` makes sure React always queues the multiple state updates one by one, rather than batching the updates altogether. |
| 282 | + |
| 283 | +## Key Takeaways |
| 284 | + |
| 285 | +- **Components as Building Blocks** — Rather than focusing on a complex webpage as a single UI entity, components enables the developer to treat a webpage as a collection of independent components. |
| 286 | +- **Component’s State as a medium between Functionality and UI **— The state of the component makes it easy to develop the functionality and rendering as independent entities inside a component. Functionality updates the state whereas rendering consumes the updated state. |
| 287 | +- **Characteristics of Data Deserving State** — Unpredictability and Changes Over Time |
| 288 | +- **Updating the state using setState() **— React updates the state synchronously or asynchronously based on the execution context to prevent unnecessary re-rendering. Delegating this state update decision to React using `setState()` |
| 289 | + |
| 290 | +```jsx |
| 291 | +article.setState({ |
| 292 | + end: true, |
| 293 | +}) |
| 294 | +``` |
0 commit comments