ReactJS Course - Lesson 03 - Props and States
Tags: react, khoahocreactj, reactjstutorial, nextjsvietnam
Published: 2023-07-21 11:21:34

**Note**: All source code for this course is publicly available on github at [ReactJS Tutorial 2023](https://github.com/misostack/reactjs-tutorial-2023/tree/lession03)

## Lesson 03

1. Learn the theory of props, state, hooks and illustration
2. Learn the theory of how to organize data structures in applications and illustrate
3. Summary

In the previous article, you have learned the structure of a ReactJS application which is a combination of components.

### Learn about props, state, hooks through examples

1. Design button "New Link" and LinkFormComponent

- Install bootstrap
- Handle Event when pressing "New Link" button will open Modal without input form for new link
- In the input form for the new link, it is required to enter the link, the title part if the user leaves it blank, use the entered link as the title.
- When clicking Close/Esc will close the modal. When reopening the Modal for New Link, the form data must be empty.
- When you click Save Changes in the main screen, it will display the JSON format of the added links.

> Using bootstrap

```sh
npm install bootstrap sass --save
```

> index.html

```html
<!DOCTYPE html>
<html lang="en">
   <head>
     <meta charset="UTF-8" />
     <link rel="icon" type="image/svg+xml" href="/vite.svg" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>%VITE_APP_NAME% - %MODE%</title>
   </head>
   <body>
     <div id="root" class="container"></div>
     <script type="module" src="/src/main.jsx"></script>
   </body>
</html>
```

> main.jsx

```jsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
// Bootstrap Bundle JS
import "bootstrap/dist/js/bootstrap.bundle.min";
// reset css
import "./index.scss";

ReactDOM.createRoot(document.getElementById("root")).render(
   <React.StrictMode>
     <App />
   </React.StrictMode>
);
```

> index.scss

```scss
$primary: #176d16;
$danger: #ff4136;

@import "node_modules/bootstrap/scss/bootstrap";
```

#### Event Handler in ReactJS

```jsx
<a
   href="https://nextjsvietnam.com"
   className="btn btn-primary"
   onClick={(e) => {
     e.preventDefault();
     alert(`You're going to redirect to ${e.target.href}`);
   }}
>
   Event Handler
</a>
```

#### Props

> In ReactJS, components often use props to communicate with each other. Parent components will usually pass data to child components via props. In addition, props also includes HTML attributes, values in Javascript such as: objects, arrays, including functions.

```jsx
import { useState } from "react";
import enviroment from "./shared/environment";

const Counter = ({ number, children, ...props }) => {
   return (
     <>
       <a {...props}>
         Counter Name:{children}
         <br />
         Value: {number}
       </a>
     </>
   );
};

const Lesson003 = () => {
   const values = [1, 2, 3, 4, 5, 6, 7, 8, 9];
   return (
     <>
       {values.map((v, index) => (
         <Counter key={index} number={v} className="btn btn-primary">
           Counter {index + 1}
         </Counter>
       ))}
     </>
   );
};

export default Lesson003;
```
There are 3 points to keep in mind here when using props:

1. All attributes passed as attributes of a tag/component are included in props. However, use the spread operator to select certain properties, this makes it easy for the editor to assist you in the coding process.
2. In the above example you will see the appearance of **chidren**, this keyword means that the content inside the 2 opening and closing tags of the component will be assigned to the prop with the key **children**.
3. Props are readonly, never try to change the value of props (immutable). Always use the underlying state when a variable type is needed to change the value each time the user interacts.

One note, when rendering a list in ReactJS, we will see the following error appear on the console.

![image](https://user-images.githubusercontent.com/31009750/253806885-a6677a9d-8787-4e55-8109-6d127052ea0e.png)

The reason is that in the design of ReactJS, in order to optimize rendering and DOM updates, when rendering a list of items, assigning a unique key to each element in the array, will help ReactJS optimize rendering when an item is added/removed/updated.

#### State

> It is essentially the component's memory, representing the current state of the component and will change when the user interacts with the component

Consider the following example

```jsx
import enviroment from "./shared/environment";

const Lesson003 = () => {
   let counter = 1;
   console.log("Before render");
   return (
     <>
       <h2>State and Event</h2>
       <h3>Counter: {counter}</h3>
       <button
         type="button"
         className="btn btn-primary"
         onClick={(e) => {
           e.preventDefault();
           console.log(`Increase counter from ${counter} to ${counter + 1}`);
           counter += 1;
           console.log("After counter increased!", counter);
         }}
       >
         Increase counter
       </button>
     </>
   );
};

export default Lesson003;
```

As we have seen, the event handler is very similar to handling an event on the DOM, just a little different in syntax, is that instead of calling the function immediately, in JSX you need to assign an event to a function. This function can be an anonymous function as in the example, or a previously declared function.

```html
<button onclick="myFunction()">Click me</button>
```

Looking at the above code, you will think, when the counter variable changes, on the screen now, you will see its new value. However, keep an eye on the console.log and the results of each click of the Increase counter button.

![image](https://user-images.githubusercontent.com/31009750/253735920-7b913847-85a2-4888-815a-505d0a130743.png)

The expected result is that after each counter mutate, console.log will record an extra line of "Before render". And the counter on the interface will also be updated.

The answer is, React Component has no reason to re-render, it just re-renders if and only when **state** changes. Below is the syntax to declare and use state in ReactJS.

```jsx
import { useState } from "react";
import enviroment from "./shared/environment";

const Lesson003 = () => {
   const [counter, setCounter] = useState(0);
   console.log("Before render, counter:", counter);
   return (
     <>
       <h2>State and Event</h2>
       <h3>Counter: {counter}</h3>
       <button
         type="button"
         className="btn btn-primary"
         onClick={(e) => {
           e.preventDefault();
           console.log(`Increase counter from ${counter} to ${counter + 1}`);
           setCounter(counter + 1);
           console.log("After counter increased!", counter);
         }}
       >
         Increase counter
       </button>
     </>
   );
};

export default Lesson003;
```

![image](https://user-images.githubusercontent.com/31009750/253736315-136a0174-6b34-4d4f-af22-078c7f7d083e.png)
After rewriting the original code, using the syntax in React to declare and update the state. We notice three things:

1. The state cannot be updated directly, only the provided setState function can be used.
2. When the setState function is called, the value of state does not change immediately.
3. After the Component is re-rendered, we will see the new value of the state.
4. If the state is an object, it should be noted that the new value of the state must form a new object, then React will recognize the changed state and re-render (because the object in javascript is always referenced to a fixed address at initialization and during the value change process, this reference address remains unchanged).

Suppose, we call the state update function consecutively, what will happen:

```jsx
import { useState } from "react";
import enviroment from "./shared/environment";

const Lesson003 = () => {
   const [counter, setCounter] = useState(0);
   console.log("Before render, counter:", counter);
   return (
     <>
       <h2>State and Event</h2>
       <h3>Counter: {counter}</h3>
       <button
         type="button"
         className="btn btn-primary"
         onClick={(e) => {
           e.preventDefault();
           console.log(`Increase counter from ${counter} to ${counter + 1}`);
           setCounter(counter + 1);
           setCounter(counter + 2);
           setCounter(counter + 3);
           console.log("After counter increased!", counter);
         }}
       >
         Increase counter
       </button>
     </>
   );
};

export default Lesson003;
```

Maybe 6 or so? As a result, only setCounter(counter+3) is executed.

![image](https://user-images.githubusercontent.com/31009750/253736507-c6fa9749-1c63-4c54-8bde-82219b5baf51.png)

Explanation: In an event, when the state update happens, ReactJS will collect these updates and execute it once, at this time the state counter will be a fixed value in each update event, so even though the previous state update events of +1,+2 are performed, but at step number +3, the counter value is still taken from the original value, so the result will be as we have seen.
Let's test updating 2 separate states in the same event and check how many times ReactJS will render.

```jsx
import { useState } from "react";
import enviroment from "./shared/environment";

const Lesson003 = () => {
   const [counter, setCounter] = useState(0);
   const [counter2, setCounter2] = useState(1);
   console.log("Before render, counter:", counter, "counter2:", counter2);
   return (
     <>
       <h2>State and Event</h2>
       <h3>Counter: {counter}</h3>
       <h3>Counter2: {counter2}</h3>
       <button
         type="button"
         className="btn btn-primary"
         onClick={(e) => {
           e.preventDefault();
           console.log(`Increase counter from ${counter} to ${counter + 2}`);
           console.log(`Increase counter from ${counter2} to ${counter2 + 3}`);
           setCounter(counter + 2);
           setCounter2(counter2 + 3);
         }}
       >
         Increase counter
       </button>
     </>
   );
};

export default Lesson003;
```

![image](https://user-images.githubusercontent.com/31009750/253738458-3e7f09c2-0a0b-4964-b24c-443a612386f2.png)

If instead of using state value directly in component, and update state using callback function like below

```jsx
import { useState } from "react";
import enviroment from "./shared/environment";

const Lesson003 = () => {
   const [counter, setCounter] = useState(0);
   const [counter2, setCounter2] = useState(1);
   console.log("Before render, counter:", counter, "counter2:", counter2);
   return (
     <>
       <h2>State and Event</h2>
       <h3>Counter: {counter}</h3>
       <h3>Counter2: {counter2}</h3>
       <button
         type="button"
         className="btn btn-primary"
         onClick={(e) => {
           e.preventDefault();
           console.log(`Increase counter from ${counter} to ${counter + 2}`);
           console.log(`Increase counter from ${counter2} to ${counter2 + 3}`);
           setCounter((counter) => counter + 2);
           setCounter((counter) => counter + 4);
           setCounter2((counter2) => counter2 + 3);
         }}
       >
         Increase counter
       </button>
     </>
   );
};

export default Lesson003;
```

The results are exactly as described.

![image](https://user-images.githubusercontent.com/31009750/253738818-448d77fe-f25f-4bae-a0a9-b0ec5f8a5078.png)

Further testing with setTimeOut,
```jsx
import { useState } from "react";
import enviroment from "./shared/environment";

const Lesson003 = () => {
   const [counter, setCounter] = useState(0);
   const [counter2, setCounter2] = useState(1);
   console.log("Before render, counter:", counter, "counter2:", counter2);
   return (
     <>
       <h2>State and Event</h2>
       <h3>Counter: {counter}</h3>
       <h3>Counter2: {counter2}</h3>
       <button
         type="button"
         className="btn btn-primary"
         onClick={(e) => {
           e.preventDefault();
           console.log(`Increase counter from ${counter} to ${counter + 2}`);
           console.log(`Increase counter from ${counter2} to ${counter2 + 3}`);
           setCounter((counter) => counter + 2);
           setCounter((counter) => counter + 4);
           setTimeout(() => {
             setCounter((counter) => counter + 6);
           }, 0);
           setCounter2((counter2) => counter2 + 3);
         }}
       >
         Increase counter
       </button>
     </>
   );
};

export default Lesson003;
```

As a result, we have 2 renders, because the update of state counter + 6 takes place in another process.

![image](https://user-images.githubusercontent.com/31009750/253738943-b6630f8f-dc06-4755-a0d8-2cfa12e5ea98.png)

Same test with object for statestate

```jsx
import { useState } from "react";
import enviroment from "./shared/environment";

const Lesson003 = () => {
   const [stateObject, setStateObject] = useState({
     a: {
       b: {
         c: 1,
       },
     },
   });
   console.log("Before render, stateObject.a.b.c:", stateObject.a.b.c);
   return (
     <>
       <h2>State and Event</h2>
       <h3>StateObject: {JSON.stringify(stateObject)}</h3>
       <button
         type="button"
         className="btn btn-primary"
         onClick={(e) => {
           e.preventDefault();
           setStateObject((obj) => {
             console.log(obj.a.b.c, "obj === stateObject", obj === stateObject);
             obj.a.b.c += 1;
             return obj;
           });
         }}
       >
         Increase counter
       </button>
     </>
   );
};

export default Lesson003;
```
As a result, even though we see the value of c has changed, in fact the UI still does not re-render.

![image](https://user-images.githubusercontent.com/31009750/253748442-c7c9b1f3-a71b-48e8-9377-7fa9a0a65f1f.png)

Therefore, mutating the object directly and the resulting change still refer to the same object, will make the React Component not aware that it will have to re-render, because it performs the comparison between the two objects.
Therefore, it is imperative that you do not change this object directly, but rather create an object consisting of the values of the old object mixed with the new changed value.

```jsx
import { useState } from "react";
import enviroment from "./shared/environment";

const Lesson003 = () => {
   const [stateObject, setStateObject] = useState({
     a: {
       b: {
         c: 1,
       },
     },
   });
   console.log("Before render, stateObject.a.b.c:", stateObject.a.b.c);
   return (
     <>
       <h2>State and Event</h2>
       <h3>StateObject: {JSON.stringify(stateObject)}</h3>
       <button
         type="button"
         className="btn btn-primary"
         onClick={(e) => {
           e.preventDefault();
           setStateObject((obj) => {
             console.log(obj.a.b.c, "obj === stateObject", obj === stateObject);
             return {
               ...obj,
               a: {
                 b: {
                   c: obj.a.b.c + 1,
                 },
               },
             };
           });
         }}
       >
         Increase counter
       </button>
     </>
   );
};

export default Lesson003;
```

![image](https://user-images.githubusercontent.com/31009750/253748682-a14a9340-d363-4063-aee4-e3c2457c04c7.png)

Now both on the UI, and on the console, the value of c is updated accordingly.

> How to get the value of form input

```jsx
import { useState } from "react";

const Lesson003 = () => {
   const [input, setInput] = useState("");
   const [items, setItems] = useState([]);

   return (
     <>
       <h2>State Array</h2>
       <div className="mb-3">
         <label htmlFor="exampleFormControlInput1" className="form-label">
           Item name
         </label>
         <input
           value={input}
           onChange={(e) => {
             setInput(e.target.value);
           }}
           type="email"
           className="form-control"
           id="exampleFormControlInput1"
           placeholder="name"
         />
       </div>
       <button
         type="button"
         className="btn btn-primary"
         onClick={(e) => {
           e.preventDefault();
           setItems((items) => {
             return [...items, input];
           });
         }}
       >
         Add new item
       </button>
       <ul>
         {items.map((item, index) => (
           <li key={index}>{item}</li>
         ))}
       </ul>
     </>
   );
};

export default Lesson003;
```

![image](https://user-images.githubusercontent.com/31009750/253852462-a2dbe6dc-e78a-486a-a47b-7c45952bf739.png)

So we can see that by using state, and the onChange event, you will be able to synchronize the user input and the state of this input. Thanks to the mechanism of ReactJS, in the picture you only see the re-rendered part is the content of the input, not the whole, which makes the application so smooth.

> Let's try with state as array

```jsx
<button
   type="button"
   className="btn btn-primary"
   onClick={(e) => {
     e.preventDefault();
     setItems((items) => {
       return [...items, input];
     });
   }}
>
   Add new item
</button>
```

Through the above examples, we can conclude the following:

![image](https://user-images.githubusercontent.com/31009750/253850183-5e54ca43-42e9-444a-8f06-6eeba6261bab.png)

> However, the above way of writing makes the code look quite complicated and difficult to maintain. You can use the immutable library to still ensure the immutable state of the state, but when you write the code, it looks like it's mutating.

```sh
npm install use-immer --save
```

```jsx
import { useState } from "react";
import { useImmer } from "use-immer";

const Lesson003 = () => {
   const [input, setInput] = useState("");
   const [items, setItems] = useImmer([]);

   return (
     <>
       <h2>State Array</h2>
       <div className="mb-3">
         <label htmlFor="exampleFormControlInput1" className="form-label">
           Item name
         </label>
         <input
           value={input}
           onChange={(e) => {
             setInput(e.target.value);
           }}
           type="email"
           className="form-control"
           id="exampleFormControlInput1"
           placeholder="name"
         />
       </div>
       <button
         type="button"
         className="btn btn-primary"
         onClick={(e) => {
           e.preventDefault();
           setItems((items) => {
             items.push(input);
           });
         }}
       >
         Add new item
       </button>
       <ul>
         {items.map((item, index) => (
           <li key={index}>{item}</li>
         ))}
       </ul>
     </>
   );
};

export default Lesson003;
```

This new code looks much cleaner, doesn't it?

#### The process of rendering a ReactJS Component will proceed as follows:

> ### Trigger -> Render -> Commit

**Trigger:**

- When the application is initialized at this step, ReactJS will embed the application in the DOM node root, and call the **render** method of the components.
- Re-render: every time the component updates its state, it will re-render itself, which leads to its children components re-rendering as well.

**Render**:

As mentioned above, when the application initializes, React will call the root component to perform the rendering.
- For the next time, React will actually call the function components whose state update triggers this rendering.

**Commit**:

- When the application initializes, after the root component renders successfully, React will now append all the generated DOM nodes to the DOM tree.
- For subsequent iterations, React will do the comparison and only update the DOM tree if there is actually a change.

So you have an overview of state, props in ReactJS and how ReactJS re-renders components when state changes.

### Practice building a link management application

#### Application requirements

In real application, we need to solve more conundrums. Within the scope of this lesson, we seek to answer the following questions.

Suppose, you have to build a small application, to manage a list of links.
Each link can be: Website Link, Image(png/jpg/jpeg), Youtube Link.

The application will include a single screen, adding and editing tasks will open a modal that allows the user to enter/change information, after saving, the information in the list will be updated.

**Main features of the application include:**

- Add, Edit, Delete Link
- Display links in 3 formats: website (link form with entered title, if title is blank, use link), image (image format with entered title, if blank, use link), youtube link (iframe with embed link)
- Store application information on the user's machine, so that when the application is closed and reopened, the saved information still exists.

**Interface requirements are as follows**

> Home screen

![Homescreen](https://user-images.githubusercontent.com/31009750/253456804-a3f42811-9a61-438b-bd77-e5e00e128662.png)

> Modal add, edit

![Modal add, edit](https://user-images.githubusercontent.com/31009750/253457039-65cd0eb2-cfbd-4204-a757-c90d1fc3b834.png)

Feel free to read full course at [ReactJS Course 2023](https://nextjsvietnam.com/post/khoa-hoc-reactjs-bai-03/)

From [Khóa Học ReactJS](https://nextjsvietnam.com/post/khoa-hoc-reactjs-bai-03/)