Lesson 01: React Crash Course - strvcom/frontend-academy-2022 GitHub Wiki

Speaker: Michal Honc

Resources


React

  • Open source library from Meta for building user interfaces
  • React is simple. Not easy, simple
  • It's a library. Not a framework. Apart some tools like JSX, you are using only vanilla javascript.

React vs React-DOM

  • Up until React version 0.14 ReactDOM was shipped within React

  • React is library for building user interfaces

  • Whereas ReactDOM is complimentary library to React which connects React and the browser DOM

  • Why did they extract ReactDOM?

    • React library by itself is then applicable to more environments than just the browser
    • Thats why we have React Native which is leveraging React under macOS, Android and Windows

DEMO: Javascript

  • Start with Javascript to add a basic button
  • Converting vanilla Javascript code to ReactJS

Demo

  1. Prepare server
  • yarn global add serve
  • serve within the folder
  1. Create basic HTML structure
<!DOCTYPE html>
<html>
  <head>
    <title>ReactJS Crash course</title>
  </head>
  <body></body>
</html>
  1. Add root element
<head>
    <title>ReactJS Crash course</title>
</head>
<body>
+   <div id="root"></div>
</body>
  1. Add button element
<body>
    <div id="root"></div>
+
+   <script>
+       const rootElement = document.getElementById('root')
+       const buttonElement = document.createElement('button')
+
+       function handleOnClick(event) {
+           alert('Button clicked')
+       }
+
+       buttonElement.innerHTML = 'Click me'
+       buttonElement.className = 'button'
+       buttonElement.addEventListener('click', handleOnClick)
+
+       rootElement.append(buttonElement)
+   </script>
</body>

DEMO: ReactJS without JSX

  • Now we will convert vanilla javascript to ReactJS code that does the same
  • First we need to add ReactJS to the page
    • In normal scenario you would have a proper compiler
    • To save time and space we will use Unpackage to load UMD versions of our package (ReactJS)
    • We'll load development version that is not minified
  1. Add unpackage scripts to body before our ReactJS code to inject React
<body>
    <div id="root"></div>

+   <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
+   <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>

    <script>
  1. Rewrite document to React for buttonElement
-   const buttonElement = document.createElement('button')
+   const buttonElement = React.createElement('button')
  1. Convert JS attributes to React createElement as second argument
-   const buttonElement = React.createElement('button')
-   buttonElement.innerHTML = 'Click me'
-   buttonElement.className = 'button'
-   buttonElement.addEventListener('click', handleOnClick)
+   const buttonElement = React.createElement('button', {
+       className: 'button',
+       children: 'Click me',
+       onClick: handleOnClick,
+   })
  1. In browser run the code which should result in [object Object]
-   rootElement.append(buttonElement)
+   const root = ReactDOM.createRoot(rootElement)
+   root.render(buttonElement)
  1. We would want to add an input element that can be submitted
    const buttonElement = React.createElement('button', {
        className: 'button',
        children: 'Click me',
        onClick: handleOnClick,
    })
+
+   const inputElement = React.createElement('input', {
+       placeholder: 'Enter value',
+       type: "text",
+       name: "value",
+   })
  1. To comply HTML semantic and how the browser works with forms we should wrap both elements in form element
    const buttonElement = document.createElement('button')

-   function handleOnClick(event) {
-       alert('Button clicked')
-   }

    const buttonElement = React.createElement('button', {
        className: 'button',
        children: 'Click me',
-       onClick: handleOnClick,
    })

+   function handleOnSubmit(event) {
+       event.preventDefault()
+       const formData = new FormData(event.target)
+       const value = formData.get('value')
+
+       console.log(value)
+   }
+
+   const formElement = React.createElement('form', {
+       onSubmit: handleOnSubmit,
+       children: '???',
+   })

    const root = ReactDOM.createRoot(rootElement)
-   root.render(buttonElement)
+   root.render(formElement)
  1. But how do we add multiple children to formElement? Lets do some nesting
  • We can add it as array of children to children which throws some error regarding keys
  • or we can add each child as individual params in createElement which fixes the error for now
+   const formElement = React.createElement('form', {
+       onSubmit: handleOnSubmit,
-       children: '???',
+   }, inputElement, buttonElement)
  • With that we have fully functional form written in React

DEMO: ReactJS with JSX

  1. We will add standalone compiler written in JS and change script type to let Babel know what to compile
     <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
     <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
+    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

-    <script>
+    <script type="text/babel">
  1. Show the compiled JS in the browser
  2. Add JSX button element which is equivalent to createElement which will be working with current setup
+   const buttonElement = <button className="button">Click me</button>
-   const jsButtonElement = React.createElement('button', {
  • Such syntax is a visual aid
  1. Replace inputElement with the same logic
-   const jsInputElement = React.createElement('input', {
+   const inputElement = <input placeholder="Enter a value" type="text" name="value">
  1. Now rewrite formElement without interpolation
-      const formElement = React.createElement('form', {
-        onSubmit: handleOnSubmit,
-      }, inputElement, buttonElement)

+      const formElement = (
+        <form onSubmit="???">
+           ???inputElement
+           ???buttonElement
+        </form>
+      )
  • We added curly braces as prevention for automatic semicolon insertion pitfalls
  1. How do we get Javascript into JSX? Interpolation
  • imagine it like Javascript interpolation with template literals
const name = 'Michal'
const surname = 'Honc'
const fullName = `${name} ${surname}`
  • with that we can insert expressions into a string (not statements)
const name = 'Michal'
const surname = 'Honc'
- const fullName = `${name} ${surname}`
+ const fullName = `${name} ${if (surname.length > 2) { .. }}`
+ const fullName = if (surname.length > 2) { .. }
+ const fullName = `${name} ${surname.length > 2 ? surname : ''}`
  1. With that in mind we can use same interpolation process with JSX
    const formElement = (
-        <form onSubmit="???">
+        <form onSubmit={handleOnSubmit}>
-           ???inputElement
-           ???buttonElement
+           {inputElement}
+           {buttonElement}
         </form>
       )

DEMO: Components

  • Since crucial aspect of programming is reusing logic we can abstract JSX into a React component
  1. Lets make a function that use parameters and passes them to JSX
    const inputElement = <input placeholder="Enter a value" type="text" name="value" />
+      function getInputElement({ name }) {
+        return <input placeholder={`Enter a ${name}`} id={name} type="text" name={name} />
+      }
  1. We can now call this function in formElement which will result in the same code
       const formElement = (
         <form onSubmit={handleOnSubmit}>
-          {inputElement}
-          {buttonElement}
+          {getInputElement({ name: 'name' })}
+          {getInputElement({ name: 'surname' })}
         </form>
       )
  1. We have to also modify the handleOnSubmit function for new input
       function handleOnSubmit(event) {
         event.preventDefault()
         const formData = new FormData(event.target)
-        const value = formData.get('value')
+        const name = formData.get('name')
+        const surname = formData.get('surname')

-        console.log(value)
+        console.log(`${name} ${surname}`)
       }
  1. For the React.createElement we can also pass the function that creates the div so we can create element that way
+          const inputElement = React.createElement(getInputElement, { name: 'name' })

-          {getInputElement({ name: 'name' })}
+          {inputElement}
  1. If you remember we can use created elements from React.createElement as JSX
+       const jsElement = React.createElement('div', {}, 'children')
+       const jsxElement = <div>children</div>
  1. So for this case, we should be able to traverse the JSX element
        const inputElement = React.createElement(getInputElement, { name: 'name' })
+       // <getInputElement name="name" />        

       const formElement = (
         <form onSubmit={handleOnSubmit}>
-          {getInputElement({ name: 'name' })}
+          <getInputElement name="name" />  
+          {getInputElement({ name: 'surname' })}
         </form>
       )
  • check in browser if it works
  • In the DOM inspector we will see instead of React element
  • React component cannot be used as lowercased elements since that collides with HTML components
  1. Lets make the first letter uppercase
-       const inputElement = React.createElement(getInputElement, { name: 'name' })
-       function getInputElement({ name }) {
+       function InputElement({ name }) {
         return <input placeholder={`Enter a ${name}`} id={name} type="text" name={name} />
       }        

       const formElement = (
         <form onSubmit={handleOnSubmit}>
-          <getInputElement name="name" />  
+          <InputElement name="name" />
+          <InputElement name="surname" />  
-          {getInputElement({ name: 'surname' })}
+          <button className="button">Submit</button>
         </form>
       )

DEMO: Fragment

  1. Lets add input label
-      function InputElement({ name }) {
-        return <input placeholder={`Enter a ${name}`} id={name} type="text" name={name} />
+      function InputElement({ name, placeholder, label }) {
+        return (
+            <label htmlFor={name}>{label}</label>
+            <input placeholder={`Enter a ${name}`} id={name} type="text" name={name} />
+        )
       }
  1. Lets add according props
       const formElement = (
         <form onSubmit={handleOnSubmit}>
-          <InputElement name="name" />
-          <InputElement name="surname" />
+          <InputElement name="name" placeholder="Michal" label="Name: " />
+          <InputElement name="surname" placeholder="Honc" label="Surname: " />
           <button className="button">Submit</button>
         </form>
       )
  • lets run it
  • it fails because I am adding two elements on the same level without a parent
  1. Why is that? Lets convert it to createElement
       function InputElement({ name, placeholder, label }) {
         return (
             <label htmlFor={name}>{label}</label>
             <input placeholder={`Enter a ${name}`} id={name} type="text" name={name} />
         )
       }

+       const InputElement = React.createElement(???, { name, placeholder, label})

       function InputElement({ name, placeholder, label }) {
         return (
+             <React.Fragment>
                <label htmlFor={name}>{label}</label>
                <input placeholder={`Enter a ${name}`} id={name} type="text" name={name} />
+             </React.Fragment>
         )
       }

+         <div>
           <InputElement name="name" placeholder="Michal" label="Name: " />
+         </div>
+         <div>
             <InputElement name="surname" placeholder="Honc" label="Surname: " />
+         </div>
  • This can be written as <> </>

DEMO: State with arrays

  • Switch to create-react-app or wrap code in root function so we can call React hooks
function App() {
+ let user = null

  function handleOnSubmit(event) {
    event.preventDefault();
    const formData = new FormData(event.target);
    const name = formData.get("name");
    const surname = formData.get("surname");
    const newUser = { id: name + surname, name, surname };

-    console.log(`${name} ${surname}`)
+    user = newUser
  }

+  <pre>
+   {JSON.stringify(user, null, 4)}
+  </pre>
  • that wont do nothing on submit since its no reactive
  1. Lets fix it with reactive state
+ import { useState } from 'react'

function App() {
- let user = null
+ const [user, setUser] = useState(null)

  function handleOnSubmit(event) {
    event.preventDefault();
    const formData = new FormData(event.target);
    const name = formData.get("name");
    const surname = formData.get("surname");
    const newUser = { id: name + surname, name, surname };

-    user = newUser
+    setUser(newUser)
  }
  1. Add more users to test array rendering
function App() {
- const [user, setUser] = useState(null)
+ const [users, setUsers] = useState([])

  function handleOnSubmit(event) {
    event.preventDefault();
    const formData = new FormData(event.target);
    const name = formData.get("name");
    const surname = formData.get("surname");
    const newUser = { id: name + surname, name, surname };

-    setUser(newUser)
+    setUsers((prevUsers) => [...prevUsers, newUser])
  }
  1. Now that gives us an array. What to do with that?
+      <ol>
+        {users.map((user) => (
+          <li>
+            <span>{`${user.name} ${user.surname}`}</span>
+          </li>
+        ))}
+      </ol>
  • map works because it returns new array
  • forEach returns undefined and changes current array
  • we see an error in console for keys.. does not affect anything
  1. Lets add an option to delete user
+  function removeUser(id) {
+    setUsers((prevUsers) => {
+      return prevUsers.filter((prevUser) => prevUser.id !== id);
+    });
+  }
       <ol>
         {users.map((user) => (
           <li>
             <span>{`${user.name} ${user.surname}`}</span>
+            <button onClick={() => removeUser(user.id)}>‚ùå</button>
           </li>
         ))}
       </ol>
  1. Lets add an input that add possibility to fix typo in surname
       <ol>
         {users.map((user) => (
           <li>
             <span>{`${user.name} ${user.surname}`}</span>
+            <input defaultValue={user.surname} />
             <button onClick={() => removeUser(user.id)}>‚ùå</button>
           </li>
         ))}
       </ol>
  • Without changing the input defaultValue delete and add new items..

  • Everything should work just fine

  • Then lets fix a typo at some middle surname

  • Delete anything above the update surname

  • Input are now wrong?!

  1. Back to presentation with explanation why React needs keys
  • Adding index as key make the warning go away but doesn't fix the issue
  1. Fix it with key
       <ol>
         {users.map((user) => (
-          <li>
+          <li key={user.id}>
             <span>{`${user.name} ${user.surname}`}</span>
             <input defaultValue={user.surname} />
             <button onClick={() => removeUser(user.id)}>‚ùå</button>
           </li>
         ))}
       </ol>
  • Now it works correctly 🚀 🤘
⚠️ **GitHub.com Fallback** ⚠️