components may have an internal state
state can be referenced in the template
the view will automatically update if state entries change
state hook in function components:
import { useState } from 'react';
function Slideshow() {
const [imgId, setImgId] = useState(0);
const [imgWidth, setImgWidth] = useState(300);
const [imgHeight, setImgHeight] = useState(200);
return ...
};
const [imgId, setImgId] = useState(0);
useState
may be called (repeatedly) inside the component function to declare state entries
parameter: initial state
array of return values: current state, setter function
guideline: use the minimal state (i.e. no redundant data)
other data can be computed based on the state
Examples:
Re-implement the slideshow component we saw before; try not to look at the old code
add some features to the slideshow, e.g.:
avoid this for now:
setTimeout
, setInterval
)"uncontrolled" input in React:
<input />
React does not know the input's content and cannot change it
connecting an input to the state:
const [inputText, setInputText] = useState('');
<input
value={inputText}
onChange={(event) => setInputText(event.target.value)}
/>
explanation:
value={inputText}
binds from the state to the input value
onChange={(event) => setInputText(event.target.value)}
updates the state whenever the input value changes
why event.target.value
?
event.target
represents the input elementevent.target.value
is the new value of the input elementExample: Input that also displays the number of characters:
function App() {
const [text, setText] = useState('');
return (
<div>
<input
value={text}
onChange={(event) => setText(event.target.value)}
/>
<button onClick={() => setText('')}>clear</button>
<p>This string has {text.length} characters.</p>
</div>
);
}
other options for working with input state:
FormData
)text and text area:
<input
value={title}
onChange={(event) => setTitle(event.target.value)}
/>
checkbox:
<input
type="checkbox"
checked={accept}
onChange={(event) => setAccept(event.target.checked)}
/>
Basic advice for numeric input fields:
store the content as a string (not as a number)
Reasoning: possible contents of a numeric input field (while the user is typing):
""
"-"
"-3"
"-3."
"-3.0"
example: numeric input with direct "result":
https://codesandbox.io/s/numeric-input-direct-results-5vde88
Create a component called NewsletterSignup
with three state entries:
email
, repeatEmail
, acceptTerms
demo of finished version: https://codesandbox.io/s/newsletter-form-pvgs6l
note: enabling / disabling a button in JSX: <button disabled={...}>...</button>
extra: add a dropdown to choose a language for the newsletter
Immutability: important concept in functional programing and with React
Data is not modified directly - instead, new data is derived from existing data (and may replace the existing data)
if there are arrays or objects in the state we could try and mutate them directly
don't do this - React will not notice the changes and may not rerender the view
state should be considered as immutabe (unchangeable)
When a state setter is called:
React will compare the old state reference and the new state reference
If the reference is the same, the component will not be rerendered
options for getting updated data from previous data:
topics:
mechanisms:
...
).map
, .filter
)spread syntax for arrays:
const numbers = [1, 2, 3];
const moreNumbers = [...numbers, 4, 5, 6];
// moreNumbers: [1, 2, 3, 4, 5, 6]
spread syntax for objects:
const person = {
firstName: 'Joe',
lastName: 'Doe',
age: 31,
};
const newPerson = { ...person, email: 'j@d.com', age: 32 };
// {firstName: 'Joe', lastName: 'Doe', email: 'j@d.com', age: 32}
the .map
method:
const myNumbers = [1, 2, 3, 4];
const tripledNumbers = myNumbers.map((n) => 3 * n);
// [3, 6, 9, 12]
const squareRoots = myNumbers.map(Math.sqrt);
// [1, 1.41, 1.73, 2]
the .filter
method:
const myNumbers = [1, 2, 3, 4];
const evenNumbers = myNumbers.filter((n) => n % 2 === 0);
// [2, 4]
mechanisms:
Working with todos
Task: Complete some functions that manage todos without mutating them
exercise: create the following pure function that handles a todo item:
const todo1 = { id: 1, title: 'foo', completed: false };
function changeTitle(todo, newTitle) {
// TODO: FINISH IMPLEMENTATION HERE
}
const todo2 = changeTitle(todo1, 'bar');
console.log(todo2);
// { id: 1, title: 'bar', completed: false}
solution:
function changeTitle(todo, newTitle) {
return { ...todo, title: newTitle };
}
exercises: create pure functions that handle an array of todos
const todos = [
{ id: 1, title: 'foo', completed: false },
{ id: 5, title: 'bar', completed: true },
{ id: 7, title: 'baz', completed: false },
];
Complete this code so it adds a new incomplete todo with a given title and a new id:
function addTodo(todos, newTitle) {
let maxId = 0;
for (let todo of todos) {
maxId = Math.max(maxId, todo.id);
}
// TODO: FINISH IMPLEMENTATION HERE
}
// add a todo with title 'qux'
const todosA = addTodo(todos, 'qux');
console.log(todosA);
possible solution:
function addTodo(todos, newTitle) {
let maxId = 0;
for (let todo of todos) {
maxId = Math.max(maxId, todo.id);
}
return [
...todos,
{ id: maxId + 1, title: newTitle, completed: false },
];
}
Complete this code so it removes a specific todo by its id:
function removeTodo(todos, id) {
// TODO: FINISH IMPLEMENTATION HERE
}
const todosB = removeTodo(todos, 1);
console.log(todosB);
possible solution:
function removeTodo(todos, id) {
return todos.filter((todo) => todo.id !== id);
}
Complete this code so it sets the completed status of a specific todo
function changeTodoCompleted(todos, id, completed) {
// TODO: FINISH IMPLEMENTATION HERE
}
// change the completed status of todo 1 to true
const todosC = changeTodoCompleted(todos, 1, true);
console.log(todosC);
possible solution:
function changeTodoCompleted(todos, id, completed) {
return todos.map((todo) => {
if todo.id === id {
return {...todo, completed: completed}
} else {
return todo;
}
})
}
newsletter signup: update the newsletter signup to use one state object instead of three separate state entries:
const [data, setData] = useState({
email: '',
repeatEmail: '',
acceptTerms: false,
});
todos: write more pure functions that handle todos:
changeTodoTitle
toggleTodo
(switch between completed and not completed)updateTodo
(change title
and completed
in one go)removeAllCompoletedTodos