React.js 2023 - A (Re)Introduction
- Setting up a React project with Vite 4.2
- Creating Components
- Working with Props
- Content Projection
- Conditional Rendering
- Loops
- Events
- State and Reducer Hooks
- Effects - Lifecycle for Functional Components
- Context API - Share Reactive Data among Components
- Error Boundary
Setting up a React project with Vite 4.2
npm create vite@latest
cd into/dir
npm install && npm run dev
VITE v4.2.1 ready in 19597 ms
➜ Local: http://localhost:5173/
To build a browser compatible version of your application run the following command and copy the generated files from the /dist
directory into the public dir of your webserver:
npm run build
Creating Components
Create a file HelloThere.jsx
in the sub directory components
:
import React from "react";
// functional component
function HelloWorld() {
return <h3>Hello World!</h3>
}
// arrow function
const ILikeArrows = () => {
return <p>I am Artemis.</p>
}
// class component
class ClassyComponent extends React.Component {
render() {
return (
<>
<HelloWorld />
<ILikeArrows />
</>
)
}
}
export default ClassyComponent
This component can be imported into App.jsx
and rendered by:
import ClassyComponent from './components/HelloThere'
function App() {
return (
<div className="App">
<div>
<ClassyComponent />
</div>
...
</div>
)
}
export default App
Working with Props
// working with props
function Paragraph(props) {
return (
<p>In ancient { props.origin }, { props.name } is the { props.profession }. She was heavily identified with Selene, the personification of the Moon, and Hecate, another lunar deity, and was thus regarded as one of the most prominent lunar deities in mythology, alongside the aforementioned two. The goddess { props.equivalent } is her Roman equivalent.</p>
)
}
function SelfIntroduction() {
return (
<>
<Paragraph
origin = "Greek mythology"
name = "Artemis"
profession = "goddess of the hunt"
equivalent = "Diana" />
</>
)
}
export default SelfIntroduction
Content Projection
// content projection
function Card(props) {
return (
<div className="card">
{ props.children }
</div>
)
}
function Image() {
const image = { url: "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b7/Diana_of_Versailles.jpg/314px-Diana_of_Versailles.jpg" }
return (
<Card>
<img src={ image.url } />
</Card>
);
}
export default Image
Conditional Rendering
// functional component
function ConditionalImage({ goddess }) {
const diana = { url: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Jean-Fran%C3%A7ois_de_Troy_-_Diane_suprise_par_Act%C3%A9on%2C_1734.jpg/800px-Jean-Fran%C3%A7ois_de_Troy_-_Diane_suprise_par_Act%C3%A9on%2C_1734.jpg" }
const artemis = { url: "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3e/Wall_painting_-_Artemis_and_Kallisto_-_Pompeii_%28VII_12_26%29_-_Napoli_MAN_111441.jpg/505px-Wall_painting_-_Artemis_and_Kallisto_-_Pompeii_%28VII_12_26%29_-_Napoli_MAN_111441.jpg" }
if (goddess == "diana") {
return <img src={ diana.url } />
} else {
return <img src={ artemis.url } />
}
}
const ConditionalTitle = (props) => {
return (
<div className="title">
{
(props.goddess == 'diana')
? <h3>The Roman Goddess Diana</h3>
: <h3>The Greek Goddess Artemis</h3>
}
</div>
)
}
const PaintingCollection = () => {
return (
<>
<h2>Paintings</h2>
<ConditionalTitle goddess='artemis' />
<ConditionalImage goddess='artemis' />
<ConditionalTitle goddess='diana' />
<ConditionalImage goddess='diana' />
</>
)
}
export default PaintingCollection
Loops
const ListItem = (props) => {
return <li>{props.name}</li>
}
function ListOfAliases() {
const data = [
{id: 1, goddess: 'Artemis'},
{id: 2, goddess: 'Diana'}
]
return (
<>
<h3>List of Aliases</h3>
<ul>
{ data.map(({ id, goddess }) =>
<ListItem key={id} name={goddess} />
)}
</ul>
</>
)
}
export default ListOfAliases
Events
function EventCatcher() {
const clickHandler = (event) => {
console.log(event)
}
return (
<>
<button onClick={clickHandler}>Log Click-Event</button>
</>
)
}
export default EventCatcher
function ActionButton({ onClick }) {
return <button onClick={onClick}>Log Click-Event</button>
}
function EventCatcher() {
const clickHandler = (event) => {
console.log(event)
}
return <ActionButton onClick={ clickHandler } />
}
export default EventCatcher
State and Reducer Hooks
import { useState, useReducer } from 'react'
const initialState = {count: 0}
function reducer(reducedState, action) {
switch (action.type) {
case 'increment':
return { count: reducedState.count + 1 }
case 'decrement':
return { count: reducedState.count - 1 }
default:
throw new Error()
}
}
// props are immutable to be able to modify
// state and redraw ui components use a state hook
function Stateful() {
const [count, setCount] = useState(0)
const [state, setState] = useState({ count: 0, goddess: 'Artemis'})
const handleClick = () => {
setState({
...state,
count: state.count + 1,
})
}
const [reducedState, dispatch] = useReducer(reducer, initialState)
return (
<>
<p>Diana: { count }</p>
<button onClick={ () => setCount(count +1)}>Like</button>
<p>{ state.goddess }: { state.count }</p>
<button onClick={ handleClick }>Like</button>
<p>God in general: { reducedState.count }</p>
<button onClick={ () => dispatch({type: 'increment'})}>Like</button>
<button onClick={ () => dispatch({type: 'decrement'})}>Dislike</button>
</>
)
}
export default Stateful
Effects - Lifecycle for Functional Components
No Dependencies
useEffect(() => {
//Runs on every render
});
import { useState, useEffect } from "react";
function Timer() {
const [count, setCount] = useState(1000);
useEffect(() => {
setTimeout(() => {
setCount((count) => count - 1);
}, 1000);
});
return <h1>Countdown: {count}s</h1>;
}
export default Timer
An empty Array
Update when component did mount:
useEffect(() => {
//Runs only on the first render
}, []);
Props or State Values
Update when component did update:
useEffect(() => {
//Runs on the first render
//And any time any dependency value changes
}, [prop, state]);
import { useState, useEffect } from "react"
function Counter() {
const [count, setCount] = useState(0);
const [calculation, setCalculation] = useState(0);
useEffect(() => {
setCalculation(() => Math.pow(count, 2));
}, [count]); // <- add the count variable here
return (
<>
<button onClick={() => setCount((c) => c + 1)}>Add Awesomeness</button>
<p>Awesomeness: {count}</p>
<p>Awesomeness²: {calculation}</p>
</>
);
}
export default Counter
Unmount Legacy
Update when component did unmount:
useEffect(() => {
//Runs when component get's destroyed
return () => console.log('Ciao!')
}, [prop, state]);
Context API - Share Reactive Data among Components
// without context
// pass prop down a chain of child components
function PropDrilling() {
const [count] = useState(44)
return <Child count={count} />
}
function Child({ count }) {
return <GrandChild count={count} />
}
function GrandChild({ count }) {
return <div>{count}</div>
}
// with context
// write prop to context to make it available to all components
const CountContext = createContext(0)
function PropContext() {
const [count] = useState(88)
return (
<CountContext.Provider value={count}>
<ChildContext count={count} />
</CountContext.Provider>
)
}
function ChildContext() {
return <GrandChildContext />
}
function GrandChildContext() {
const count = useContext(CountContext)
return <div>{count}</div>
}
Error Boundary
class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = { hasError: false }
}
// catch error
static getDerivedStateFromError(error) {
return { hasError: true }
}
componentDidCatch(error, errorInfo) {
console.log(error, errorInfo)
}
render() {
// there was a problem - provide fallback
if (this.state.hasError) {
return <p><em>Safe Fallback instead of a Crash</em></p>
}
// everything is fine - continue
return this.props.children
}
}
function BreakingComponent() {
return <p>{ anelusiveprop }</p>
}
function CaughtError() {
return (
<>
<ErrorBoundary>
<BreakingComponent />
</ErrorBoundary>
</>
)
}
export default CaughtError