Learn ReactJS Fundamentals: 7 core features
These learning notes are derived from “Learn React in 90 minutes” video series by Miguel Grinberg.
Part 1: Rendering
Sample Code: https://codesandbox.io/s/reactjs-part-12-stbdh0
- React will generate an HTML file at final process
- A component is a function (a new way — functional components) — return what you want to see on the rendered page. The main component is usually
app.js
- Example:
export default function App() {
const username = "Hoa Nguyen";
return (
<div className="App">
<h1>Hi! {username}</h1>
<h2>Part 1: Rendering</h2>
</div>
);
}
- We can mix HTML and JS together but we should separate and don’t mix them too much (separate the JS logic and HTML represent)
- This is not really Javascript, we render some of regular Javascript from it (jsx)
- Basics: If — Else clause and for loop
- Example
export default function App() {
const username = "Hoa";
const todos = ["Task 1", "Task 2", "Task 3"];
return (
<div className="App">
{/* If = Else (:) */}
{username ? <h2> Hi {username}! </h2> : <h2> Hi Stranger! </h2>}
<h2>Part 1: Rendering</h2>
{/* If only */}
{username && <a href="#logout">Logout</a>}
<br />
{/* For loop */}
<ul>
{todos.map((todo) => (
<li>{todo}</li>
))}
</ul>
</div>
);
}
Part 2: The State Hook
Sample Code: https://codesandbox.io/s/reactjs-part-12-stbdh0
- If we use a normal variable, every time the component is rendered, it will be the default value that you set
- const counter = 0;
- →
useState
function from React useState
is one of the most important things in React, anytime we want to display information on a page that need to updateuseState
will trigger all the updates made within the React app → refresh all the references within the page- Example
// useState return Variable and a setter function (how React detect change)
const [counter, setCounter] = React.useState(0);
const increase = () => {
setCounter(counter + 1);
};
const decrease = () => {
setCounter(counter - 1);
};
...
return (
<div className="App">
<h2>Part 2: The State Hook </h2>
<p>Counter: {counter}</p>
<div>
<button onClick={increase}>+1</button>
<button onClick={decrease}>-1</button>
</div>
</div>
);
}
Part 3: Sub-Components
Sample Code: https://codesandbox.io/s/reactjs-part-12-stbdh0
Main goal: Reusable component.
App.js
file
import React from "react";
import "./styles.css";
import Child from "./Child.js";
export default function App() {
// Part 3
// useState return Variable and a setter function (how React detect change)
const [counter, setCounter] = React.useState(1);
return (
<div className="App">
<h2>Part 3: The Sub-Components </h2>
<p>Counter: {counter}</p>
{/* Insert child component (props) */}
<Child step={1} counter={counter} setCounter={setCounter} />
<br />
<Child step={5} counter={counter} setCounter={setCounter} />
</div>
);
}
Child.js
import React from "react";
// Old way: export default function Child(props) => Use: props.counter, prop.setCounter
// Modern way: use bracket {counter, setCounter}
export default function Child({ step, counter, setCounter }) {
const increase = () => {
setCounter(counter + step);
};
const decrease = () => {
setCounter(counter - step);
};
return (
<div>
<button onClick={increase}>+{step}</button>
<button onClick={decrease}>-{step}</button>
</div>
);
}import React from "react";
import "./styles.css";
import Child from "./Child.js";
export default function App() {
// Part 3
// useState return Variable and a setter function (how React detect change)
const [counter, setCounter] = React.useState(1);
return (
<div className="App">
<h2>Part 3: The Sub-Components </h2>
<p>Counter: {counter}</p>
{/* Insert child component (props) */}
<Child step={1} counter={counter} setCounter={setCounter} />
<br />
<Child step={5} counter={counter} setCounter={setCounter} />
</div>
);
}
- Minimise the component
App.js
<Child step={1} setCounter={setCounter} />
<br />
<Child step={5} setCounter={setCounter} />
Child.js
const increase = () => {
setCounter((x) => x + step);
};
const decrease = () => {
setCounter((x) => x - step);
};
Part 4: The Effect Hook
Sample Code: https://codesandbox.io/s/reactjs-part-12-stbdh0
- When using
useEffect()
, you will set a second response with an empty list[]
React.useEffect(() => {
}, []);
- It uses to define what to trigger when its value changes
React.useEffect(() => {
fetch("https://freecurrencyapi.net/api/v2/latest?apikey=xxxx&base_currency="+currency)
.then((res) => res.json())
.then((data) => {
// console.log(data);
setRates(data.data);
});
}, [currency]);
- Example of
useEffect()
import React from "react";
import "./styles.css";
export default function App() {
// Part 4
// const rates = {
// GDP: 1.22,
// EUR: 0.9
// };
const [rates, setRates] = React.useState({});
const [currency, setCurrency] = React.useState('USD');
// React Effect function
React.useEffect(() => {
fetch("https://freecurrencyapi.net/api/v2/latest?apikey=xxx&base_currency="+currency)
.then((res) => res.json())
.then((data) => {
// console.log(data);
setRates(data.data);
});
}, [currency]);
const setUSD = () => setCurrency('USD');
const setEUR = () => setCurrency('EUR')
return (
<div className="App">
<h2>Part 4: The Effect Hook</h2>
<button onClick={setUSD}>USD</button> <button onClick={setEUR}>EUR</button>
<h3>{currency} Exchange Rate</h3>
{Object.keys(rates).map((currency) => (
<li>
{currency}: {rates[currency]}
</li>
))}
</div>
);
}
Part 5: The Ref Hook
Sample Code: https://codesandbox.io/s/reactjs-part-12-stbdh0
- Main goal: To interact with some libraries that are not built with React (non-React component)
import React from "react";
import "./styles.css";
export default function App() {
const text = React.useRef();
const onFocus = () => { text.current.style.background = "#ddf";}
const onBlur = () => {text.current.style.background = "#fff";}
React.useEffect(() => {
// Trick: Run a function when component is removed
const myText = text.current;
// use .current to get current Ref
console.log(text.current);
// text.current.focus();
// text.current.style.background = "#ddd";
myText.addEventListener('focus', onFocus);
myText.addEventListener('blur', onBlur);
return () => {
// Cleanup the component (Remove all EventListener) when it is being removed
myText.removeEventListener('focus', onFocus);
myText.removeEventListener('blur', onBlur);
}
}, []);
return (
<div className="App">
<h1>Part 5: The Ref Hook</h1>
{/* Use plain JS input */}
<input type="text" ref={text} />
</div>
);
}
- Cleanup the component (Remove all
EventListener
) when it is being removed
return () => {
// Cleanup the component (Remove all EventListener) when it is being removed
myText.removeEventListener('focus', onFocus);
myText.removeEventListener('blur', onBlur);
}
- Trick: Use a temporary variable
- const myText = text.current;
Part 6: The Context Hook
Sample Code: https://codesandbox.io/s/reactjs-part-12-stbdh0
- Scenario: We need to share the
username
between theLoginForm
and theHeader
- If using
State
variable with location high enough for all sub-component can access
const [username, setUsername] = React.useState(null);
- When we need to share (pass) information between multiple components
- → Using
Context
to create a shared state that is accessible to a group of components without having to explicitly pass those elements of the states as props - Make the
context
accessible to all components by adding a wrapper<UserContext.Provider>
const currentUser = {
username: 'hoant',
};
...
return (
...
<UserContext.Provider value={currentUser}>
<Navigation />
<Header />
</UserContext.Provider>
);
App.js
import React from "react";
import "./styles.css";
import Navigation from "./Navigation.js";
import Header from "./Header.js";
// Make it exportable (public) access to other components
export const UserContext = React.createContext();
export default function App() {
// A messy way: Use State var and pass to all sub-components
const [username, setUsername] = React.useState(null);
// Share the currentUser to the Context
const currentUser = {
username: username,
// Add a function to log the user in (perform login procedure)
// - can be use from the login form to do the login
// Receive the username (_username) and set the state to logged in user
loginUser: (_username) => {
setUsername(_username);
},
// Log out
logoutUser: () => {
setUsername(null);
}
};
return (
<div className="App">
<h1>Part 6: The Context Hook</h1>
{/* Add Wrapper to make context accessible to all children */}
{/* Give currentUser value to the context */}
<UserContext.Provider value={currentUser}>
<Navigation />
<Header />
</UserContext.Provider>
</div>
);
}
- Create
Context
and make it accessible to all sub-components byexport
export const UserContext = React.createContext();
- Add
loginUser
andlogoutUser
function tocurrentUser
const currentUser = {
username: username,
// Add a function to log the user in (perform login procedure)
// - can be use from the login form to do the login
// Receive the username (_username) and set the state to logged in user
loginUser: (_username) => {
setUsername(_username);
},
// Log out
logoutUser: () => {
setUsername(null);
}
};
Header.js
import React from "react";
// Import context from App.js
import { UserContext } from "./App.js";
export default function Header() {
const currentUser = React.useContext(UserContext);
console.log(currentUser);
return (
<div>
{/* If conditional to check if username is set */}
{currentUser.username ? (
<p> Welcome, {currentUser.username}!</p>
) : (
<p> Please login</p>
)}
</div>
);
}
Navigation.js
import React from "react";
import LoginForm from "./LoginForm.js";
export default function Navigation() {
return (
<div>
<LoginForm />
</div>
);
}
LoginForm.js
import React from "react";
import { UserContext } from "./App.js";
export default function LoginForm() {
// Using Ref to extract the username whatever we type in the form when it is submitted
const username = React.useRef();
const currentUser = React.useContext(UserContext);
const onSubmit = (ev) => {
// ev = EvenHandler
// Prevent the browser submit the form as network request or HTTP request
// We handling the form in client side -> Prevent browser submission happening
ev.preventDefault();
console.log(username.current.value);
// Pass the current value of username from
currentUser.loginUser(username.current.value);
};
const logOut = () => {
currentUser.logoutUser();
};
return (
<div>
{currentUser.username == null ? (
<form onSubmit={onSubmit}>
<input type="text" ref={username} />
<input type="submit" value="Login" />
</form>
) : (
<button onClick={logOut}>Logout</button>
)}
</div>
);
}
Part 7: Memoization
(Advanced topic)
Sample Code: https://codesandbox.io/s/reactjs-part-12-stbdh0
A way to optimise the application to run faster by doing fewer renders (make sure that component is only rendered when they really need to be)
→ Prevent the component’s rendering if its internal states do not change
E.g. Prevent reset
(in Child2.js
) to be rendered when not necessary
Child2.js
import React from "react";
// Pass setCounter as a prop
export default function Child2({ setCounter }) {
console.log("Child2");
const reset = () => {
setCounter(0);
};
return <button onClick={reset}>Reset</button>;
}
It will be rendered every time the button is clicked
→ Prevent rendering child2 by using memo
funtion
- Ideas: Tell react remember the first version of the component when it is created and keep reusing it instead of generating new versions of the component that need to be re-rendered
- → Wrap the
Child2
function in thememo
const Child2 = ({ setCounter }) => {
console.log("Child2");
const reset = () => {
setCounter(0);
};
return <button onClick={reset}>Reset</button>;
}
export default Child2;
⇒ Wrap it
const Child2 = React.memo(({ setCounter }) => {
console.log("Child2");
const reset = () => {
setCounter(0);
};
return <button onClick={reset}>Reset</button>;
});
export default Child2;
Result: The Child2
is rendered once and never again
The Component will be re-rendered if any of its props or state change
- Improve: Keep the state in the component that owns the state
<Child2 setCounter={setCounter} />
// Change to
const reset = () => {
setCounter(0);
};
<Child2 reset={reset} />
On Child2
:
const Child2 = React.memo(({ setCounter }) => {
console.log("Child2");
const reset = () => {
setCounter(0);
};
return <button onClick={reset}>Reset</button>;
});
// Change to
const Child2 = React.memo(({ reset }) => {
console.log("Child2");
return <button onClick={reset}>Reset</button>;
});
However, we were back at rendering the child component every time
- Why?
- Each time the parent component re-renders, it will create a new
reset
⇒ the internal state change ⇒ the Child2.js will re-render
const reset = () => { setCounter(0);};
⇒ Use useCallback()
- React will be remember the reset and always bringing back to the original function
const reset = React.useCallback(() =>{setCounter(0);}, []);