options:
component names always start with a capital letter
(to distinguish them from ordinary HTML elements)
components will usually be defined as the default export in their own files
// TodoItem.tsx
export default function TodoItem() {
// ...
}
components will often have an accompanying stylesheet file
// TodoItem.tsx
import './TodoItem.css';
parent components can pass down data from their state to child components via props (unidirectional data flow)
if we know a component's props and state, we will know how it is rendered
if multiple components need to access the same state:
the state is stored in a component higher up in the component tree and passed down via props
(see: React docs: sharing state between components)
often, the main parts of the state will be stored in the top-level component (e.g. in App
)
child components can trigger events that cause the state in their parent component to update
example:
<Rating value={prodRating} />
props are passed to function components via a props parameter:
type Props = { value: number };
function Rating(props: Props) {
// we can access "props.value"
}
alternative notation with object destructuring:
type Props = { value: number };
function Rating({ value }: Props) {
// we can access "value" directly
}
implementation of a Rating component:
type Props = { value: number };
function Rating({ value }: Props) {
const starIds = [1, 2, 3, 4, 5];
return (
<div className="Rating">
{starIds.map((id) => (
<span key={id}>{id <= value ? '★' : '☆'}</span>
))}
</div>
);
}
exercise: create a progress bar component:
<ProgressBar percentage={75} color="lightgreen" />
example with one "slot":
<Notification type="error">
<h1>Error</h1>
<p>Changes could not be saved</p>
</Notification>
example with two named "slots":
<Notification
type="error"
header={<h1>Error</h1>}
body={<p>Changes could not be saved</p>}
/>
equivalent notations:
<Notification type="error">
<div>foo</div>
</Notification>
<Notification type="error" children={<div>foo</div>} />
anything passed in between opening and closing tags will be received as props.children
implementation of a Notification
component:
type Props = {
type: 'info' | 'warning' | 'error';
children: ReactNode;
};
function Notification({ type, children }: Props) {
return (
<div className={`Notification Notification--${type}`}>
{children}
</div>
);
}
Exercise: Card component with Styling (example result with CSS declarations)
<Card>
<h1>heading</h1>
<p>content</p>
</Card>
Task: In the todo application, extract a Statistics
component that displays information like this:
5 todos (3 incomplete, 2 completed)
potential simple file structure:
Task: Create simple reusable components that are mainly used for styling
examples:
AppBar
(examples: MUI, react-bootstrap)Alert
(examples: MUI, react-bootstrap)Avatar
or LetterAvatar
(example: MUI)List
and ListItem
(example: MUI)These components should not provide any interactivity (have no events)
Props:
A component (e.g. App) may pass data (from its state) down to a child component (e.g. Rating)
Events:
A sub-component may trigger an event which will cause the state of a parent to update
Event handlers are defined as functions and passed down via props
Event names conventionally start with on
, e.g. onChange
, onClose
, ...
Example:
<Rating
value={prodRating}
onChange={(newRating) => setProdRating(newRating)}
/>
example property types for a rating component:
type Props = {
value: number;
onChange?: (value: number) => void;
};
function Rating({ value, onChange }: Props) {
const starIds = [1, 2, 3, 4, 5];
return (
<div className="Rating">
{starIds.map((id) => (
<span
onClick={() => {
if (onChange) {
onChange(id);
}
}}
key={id}
>
{id <= value ? '★' : '☆'}
</span>
))}
</div>
);
}
Using the Rating component:
const [prodRating, setProdRating] = useState(3);
<Rating
value={prodRating}
onChange={(newRating) => setProdRating(newRating)}
/>
Task: Create reusable interactive components (with some styling)
examples:
Button
(examples: MUI, react-bootstrap)TextInput
(examples: MUI, react-bootstrap)Pagination
(examples: MUI, react-bootstrap)Task: split the todo app into smaller components that communicate via props and events (e.g. AddTodo, TodoItem, TodoList)
Task: draft the structure of props, event and state for various components (e.g. Calendar, ColorPicker, BarChart, Tabs)