Lesson 01: React Crash Course - strvcom/frontend-academy-2022 GitHub Wiki
Speaker: Michal Honc
- 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.
-
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
- Start with Javascript to add a basic button
- Converting vanilla Javascript code to ReactJS
Demo
- Prepare server
yarn global add serve
-
serve
within the folder
- Create basic HTML structure
<!DOCTYPE html>
<html>
<head>
<title>ReactJS Crash course</title>
</head>
<body></body>
</html>
- Add root element
<head>
<title>ReactJS Crash course</title>
</head>
<body>
+ <div id="root"></div>
</body>
- 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>
- 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
- 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>
- Rewrite
document
toReact
forbuttonElement
- const buttonElement = document.createElement('button')
+ const buttonElement = React.createElement('button')
- 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,
+ })
- In browser run the code which should result in [object Object]
- rootElement.append(buttonElement)
+ const root = ReactDOM.createRoot(rootElement)
+ root.render(buttonElement)
- 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",
+ })
- 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)
- 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
- 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">
- Show the compiled JS in the browser
- 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
- Replace
inputElement
with the same logic
- const jsInputElement = React.createElement('input', {
+ const inputElement = <input placeholder="Enter a value" type="text" name="value">
- 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
- 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 : ''}`
- With that in mind we can use same interpolation process with JSX
const formElement = (
- <form onSubmit="???">
+ <form onSubmit={handleOnSubmit}>
- ???inputElement
- ???buttonElement
+ {inputElement}
+ {buttonElement}
</form>
)
- Since crucial aspect of programming is reusing logic we can abstract JSX into a React component
- 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} />
+ }
- 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>
)
- 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}`)
}
- 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}
- If you remember we can use created elements from
React.createElement
as JSX
+ const jsElement = React.createElement('div', {}, 'children')
+ const jsxElement = <div>children</div>
- 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
- 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>
)
- 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} />
+ )
}
- 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
- 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
<> </>
- 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
- 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)
}
- 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])
}
- 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
- 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>
- 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?!
- Back to presentation with explanation why React needs keys
- Adding index as key make the warning go away but doesn't fix the issue
- 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 🚀 🤘