const renderArticle = (
title: string,
author: string,
content: string
) => {
const articleEl = document.createElement("article");
const titleEl = document.createElement("h2");
titleEl.innerText = title;
articleEl.appendChild(titleEl);
const authorEl = document.createElement("p");
authorEl.setAttribute("class", "author");
authorEl.innerText = "By " + author;
articleEl.appendChild(authorEl);
const contentEl = document.createElement("div");
contentEl.innerHTML = content;
articleEl.appendChild(contentEl);
return articleEl;
};
What does this function do? Do you like it?
What if we could generate our HTML elements directly from JavaScript, but with an HTML-like syntax?
const renderArticle = (
title: string,
author: string,
content: string
) => {
return (
<article>
<h2>{title}</h2>
<p className="author">By {author}</p>
<div>{content}</div>
</article>
);
};
Display a random book title form the following list:
const books: string[] = [
"Anna Karenina",
"To Kill a Mockingbird",
"The Great Gatsby",
"One Hundred Years of Solitude",
"A Passage to India",
"Invisible Man",
"Don Quixote",
"Beloved",
"Mrs. Dalloway",
"Things Fall Apart",
"Jane Eyre",
"The Color Purple",
];
03-react/examples/01-hello-react
example to a folder 03-react/01-random-book
in your exercises repository.npm install
.npm run build
(to compile once) or npm run build:watch
(to automatically recompile on every change).main.ts
, write a getRandomBook()
function that returns a random book from the array. Your function should have an explicit return type annotation.App
component to display a random book on every page load.main.ts
, write a getRandomColor()
function that returns a random color. Your function should have an explicit return type annotation.getRandomColor()
function to also randomly change the background-color
of your book title.function App() {
const userConnected: boolean = false;
if (userConnected) {
return <button>Show my profile</button>;
}
return <button>Log in</button>;
}
function App() {
const userConnected: boolean = false;
return (
<div className={userConnected ? "guest" : "user"}></div>
);
}
.map()
const books: string[] = [
"Anna Karenina",
"To Kill a Mockingbird",
];
const App = () => (
<ul>
{books.map(title => <li>{title}</li>))}
</ul>
);
When rendering a list, each element of the list should have unique key
prop.
const books: string[] = [
"Anna Karenina",
"To Kill a Mockingbird",
];
const App = () => (
<ul>
{books.map(title => <li key={title}>{title}</li>))}
</ul>
);
function Comment() {
return (
<article>
<h2>My comment</h2>
<p className="author">By Matt</p>
<p>I think that…</p>
</article>
);
}
function App() {
return <Comment />;
}
interface CommentProps {
title: string;
author: string;
content: string;
}
function Comment(props: CommentProps) {
return (
<article>
<h2>{props.title}</h2>
<p className="author">By {props.author}</p>
<p>{props.content}</p>
</article>
);
}
function App() {
return (
<Comment
title="Evidence"
author="René"
content="Cogito, ergo sum"
/>
);
}
interface CommentInfo {
title: string;
author: string;
content: string;
}
interface CommentProps {
comment: CommentInfo;
}
function Comment(props: CommentProps) {
return (
<article>
<h2>{props.comment.title}</h2>
<p className="author">By {props.comment.author}</p>
<p>{props.comment.content}</p>
</article>
);
}
function App() {
const discourse: CommentInfo = {
title: "Evidence",
author: "René",
content: "Cogito, ergo sum",
};
return <Comment comment={discourse} />;
}
Display the following list of users in two sections Adults
and Kids
:
const users = [
{
id: 1,
avatar:
"https://cdn.fakercloud.com/avatars/nicollerich_128.jpg",
first_name: "Claire",
last_name: "Price",
age: 12,
city: "North Kirstin",
ip: "161.208.247.105",
isAdmin: false,
},
{
id: 2,
avatar:
"https://cdn.fakercloud.com/avatars/edobene_128.jpg",
first_name: "Carrie",
last_name: "Mayer",
age: 62,
city: "Everettetown",
ip: "123.224.60.146",
isAdmin: true,
},
{
id: 3,
avatar:
"https://cdn.fakercloud.com/avatars/rodnylobos_128.jpg",
first_name: "Jessy",
last_name: "Kassulke",
age: 8,
city: "Lake Fordhaven",
ip: "164.89.151.58",
isAdmin: false,
},
{
id: 4,
avatar:
"https://cdn.fakercloud.com/avatars/charliegann_128.jpg",
first_name: "Amalia",
last_name: "Rogahn",
age: 108,
city: "South Russberg",
ip: "162.25.24.120",
isAdmin: false,
},
{
id: 5,
avatar:
"https://cdn.fakercloud.com/avatars/jmfsocial_128.jpg",
first_name: "Kristoffer",
last_name: "Wuckert",
age: 66,
city: "Lake Angelina",
ip: "85.179.25.149",
isAdmin: false,
},
{
id: 6,
avatar:
"https://cdn.fakercloud.com/avatars/craigrcoles_128.jpg",
first_name: "Kiera",
last_name: "Rohan",
age: 36,
city: "East Beverly",
ip: "27.34.243.77",
isAdmin: false,
},
{
id: 7,
avatar:
"https://cdn.fakercloud.com/avatars/bruno_mart_128.jpg",
first_name: "Emmie",
last_name: "Hand",
age: 74,
city: "New Maryjaneberg",
ip: "85.232.129.225",
isAdmin: true,
},
{
id: 8,
avatar:
"https://cdn.fakercloud.com/avatars/woodsman001_128.jpg",
first_name: "Cameron",
last_name: "Veum",
age: 16,
city: "Altadena",
ip: "168.252.158.225",
isAdmin: false,
},
{
id: 9,
avatar:
"https://cdn.fakercloud.com/avatars/markwienands_128.jpg",
first_name: "Tremaine",
last_name: "Sanford",
age: 10,
city: "Lenexa",
ip: "38.83.7.48",
isAdmin: false,
},
{
id: 10,
avatar:
"https://cdn.fakercloud.com/avatars/ah_lice_128.jpg",
first_name: "Ronaldo",
last_name: "Weissnat",
age: 83,
city: "Baumbachtown",
ip: "50.218.254.52",
isAdmin: false,
},
{
id: 11,
avatar:
"https://cdn.fakercloud.com/avatars/zaki3d_128.jpg",
first_name: "Ephraim",
last_name: "Goyette",
age: 63,
city: "North Martina",
ip: "147.105.193.65",
isAdmin: false,
},
{
id: 12,
avatar:
"https://cdn.fakercloud.com/avatars/boxmodel_128.jpg",
first_name: "Clay",
last_name: "Kunde",
age: 113,
city: "South Damon",
ip: "209.104.243.157",
isAdmin: false,
},
{
id: 13,
avatar:
"https://cdn.fakercloud.com/avatars/VinThomas_128.jpg",
first_name: "Melisa",
last_name: "Leannon",
age: 18,
city: "Hilo",
ip: "103.82.167.168",
isAdmin: false,
},
{
id: 14,
avatar:
"https://cdn.fakercloud.com/avatars/miguelmendes_128.jpg",
first_name: "Clovis",
last_name: "Medhurst",
age: 15,
city: "Harveybury",
ip: "168.223.235.220",
isAdmin: false,
},
{
id: 15,
avatar:
"https://cdn.fakercloud.com/avatars/mandalareopens_128.jpg",
first_name: "Mylene",
last_name: "Renner",
age: 49,
city: "Arlington",
ip: "223.89.148.36",
isAdmin: false,
},
{
id: 16,
avatar:
"https://cdn.fakercloud.com/avatars/ma_tiax_128.jpg",
first_name: "Marcos",
last_name: "Ferry",
age: 47,
city: "Strackehaven",
ip: "74.94.165.210",
isAdmin: false,
},
{
id: 17,
avatar:
"https://cdn.fakercloud.com/avatars/balakayuriy_128.jpg",
first_name: "Brain",
last_name: "Mohr",
age: 54,
city: "Carrollton",
ip: "11.121.113.44",
isAdmin: false,
},
{
id: 18,
avatar:
"https://cdn.fakercloud.com/avatars/aleclarsoniv_128.jpg",
first_name: "Bella",
last_name: "VonRueden",
age: 18,
city: "Columbia",
ip: "224.144.68.251",
isAdmin: true,
},
{
id: 19,
avatar:
"https://cdn.fakercloud.com/avatars/andrewarrow_128.jpg",
first_name: "Franz",
last_name: "Raynor",
age: 28,
city: "Garrickchester",
ip: "91.159.111.88",
isAdmin: false,
},
{
id: 20,
avatar:
"https://cdn.fakercloud.com/avatars/carlosgavina_128.jpg",
first_name: "Celestino",
last_name: "Bailey",
age: 61,
city: "Aronport",
ip: "242.25.16.144",
isAdmin: false,
},
];
03-react/examples/01-hello-react
example to a folder 03-react/02-users-list
in your exercises repository.npm install
.npm run build
(to compile once) or npm run build:watch
(to automatically recompile on every change).UserInfo
interfaceWrite a UserInfo
interface, and add an explicit type annotation to the users
constant.
Update the App
component so that it displays all users (no component and no filtering yet).
User
componentExtract the code that shows a single user to a User
component in src/components/User
. The component should have a single prop of type UserInfo
.
Display the users in two separate sections depending on their age.
Next to the name of each user, display a badge if it is an admin.
// This is a shorthand:
const [hello, world] = ["Hello", "World"];
// for this:
const myArray = ["Hello", "World"];
const hello = myArray[0];
const world = myArray[1];
// This is a shorthand:
const { firstname, lastname } = {
firstname: "Ada",
lastname: "Lovelace",
};
// for this:
const myObject = { firstname: "Ada", lastname: "Lovelace" };
const firstname = myObject.firstname;
const lastname = myObject.lastname;
onClick
propTo listen on clicks:
<button onClick={() => console.log("Button clicked!")}>
Click me!
</button>
onChange
propTo listen on input changes:
return (
<input
type="text"
onChange={(e) =>
console.log("New value: " + e.target.value)
}
/>
);
onChange
eventAs in the DOM API, the handler takes an event object as parameter, with a target
property referring the to event target (HTML element on which the event was triggered).
When you write the handler inline as in the previous slide, you do not need a parameter type annotation. However, if you want to declare your function elsewhere, you will need to tell TypeScript that the event is of type React.ChangeEvent<HTMLInputElement>
:
const handleChange = (
e: React.ChangeEvent<HTMLInputElement>
) => console.log("New value: " + e.target.value);
return <input type="text" onChange={handleChange} />;
For now, let’s assume React.ChangeEvent<HTMLInputElement>
represents “an object with a target
property of type HTMLInputElement
”. We will learn about generics later.
useState
hookconst App = () => {
// `0` is the initial value, before `setCounter` is ever called.
const [counter, setCounter] = React.useState(0);
// We can read the state value using `counter`, or
// change it by calling `setCounter(newValue)`.
return (
<main>
<p>{counter}</p>
<button onClick={() => setCounter(counter + 1)}>
Increment
</button>
</main>
);
};
React will take care of automatically re-rendering the component when the state changes.
In this case, the view will be updated when calling setCounter()
.
On every click, display a different book, with a different background color.
03-react/examples/01-hello-react
example to a folder 03-react/03-random-book-on-click
in your exercises repository.npm install
.npm run build
(to compile once) or npm run build:watch
(to automatically recompile on every change).Reproduce your degrees converter in React.
03-react/examples/01-hello-react
example to a folder 03-react/04-converter
in your exercises repository.npm install
.npm run build
(to compile once) or npm run build:watch
(to automatically recompile on every change).fetch
API and promisesthen()
and catch()
fetch("https://api.quotable.io/random")
.then((response) => {
console.log("HTTP Response status: " + response.status);
return response.json();
})
.then((data) => console.log(data));
async
and await
const getMyQuote = async () => {
const response = await fetch(
"https://api.quotable.io/random"
);
console.log("HTTP Response status: " + response.status);
const data = await response.json();
console.log(data);
};
const url = new URL("https://api.example.com");
url.searchParams.append("q", "Hello World");
url.searchParams.append("sort", "year");
console.log(url.toString());
response.json()
response.json()
returns an object of type any
:
const data: any = await response.json();
This means that you can assign it to any type, and TypeScript will not check it. This is because TypeScript could not possibly know what will be returned by your request. Therefore, your need to be extra careful to assign it to the correct type.
useState()
You can explicitly set the type of a React state value:
const [value, setValue] = React.useState<boolean>(true);
On your page, there should be a text input and a Search images
button. When clicking on the button, you should show 10 related images found with the Pixabay API.
03-react/examples/05-random-quote
example to a folder 03-react/05-images-search
in your exercises repository.npm install
.npm run build
(to compile once) or npm run build:watch
(to automatically recompile on every change).Find which API method you will need to call to get your results. Try an example in your browser and study the JSON response.
PixbaySearch
typeWrite a PixbaySearch
interface corresponding to the data you expect back from your request to the API.
Note: you only need to declare the object properties that you will use.
PixbaySearch | null
To create a state variable of type PixbaySearch | null
, which an initial value of null
, use:
const [data, setData] =
React.useState<PixbaySearch | null>(null);
Add a minimal view that just shows your data
state variable as JSON (JSON.strinfify(data)
) in a <pre>
element, and a button that will call the API and update the state using setData(...)
.
Write the actual code displaying your data
:
const pow2 = (n: number): number => n * n;
Here, parentheses are optional.
const pow2 = (): string => "Hello world";
const add = (a: number, b: number): number => a + b;
Here, parentheses are mandatory.
const greet = (name: string): string => {
const result = `Hello ${name}!`;
return result;
};
To write several statements in the body of the function, we must add brackets around the body and a return statement.
const greet = (
name: string,
transform: (name: string) => string
): string => {
const transformedName = transform(name);
const result = `Hello ${transformedName}!`;
return result;
};
The syntax for function types is similar to array functions
Think how to specify the type of f
in these functions:
map(array: number[], f: ???)
: apply f
to each item.filter(array: number[], f: ???)
: only keep elements where f(element)
returns true
.reduce(array: number[], f: ???)
: f
takes two parameters, the current accumulated value, and an item. It is uses to reduce the array to a single value.reduce
usageUse Array.prototype.reduce()
to:
map()
Example implementation of map()
for an array of numbers:
const map = (
numbers: number[],
f: (n: number) => number
) => {
const result: number[] = [];
for (const n of numbers) {
result.push(f(n));
}
return result;
};
map([1, 2, 3], (x) => x * x);
// Returns [1, 4, 9]
map([1, 2, 3], (x) => x * 2);
// Returns [2, 4, 6]
filter()
Example implementation of filter()
for an array of numbers:
const filter = (
numbers: number[],
f: (n: number) => boolean
) => {
const result: number[] = [];
for (const n of numbers) {
if (f(n)) {
result.push(n);
}
}
return result;
};
filter([1, -5, 6, -9, 2, 3], (x) => x >= 0);
// Returns [1, 6, 2, 3]
filter([0, 5, 8, 2, 9, 3], (x) => x % 2 === 0);
// Returns [0, 8, 2]
reduce()
Example implementation of reduce()
for an array of numbers:
const reduce = (
numbers: number[],
f: (a: number, b: number) => number,
initialValue: number
) => {
let acc = initialValue;
for (const n of numbers) {
acc = f(acc, n);
}
return acc;
};
reduce([1, 2, 3], (a, b) => a + b, 0);
// Returns 6
reduce(
[2, 1, 3],
(a, b) => Math.min(a, b),
Number.MAX_SAFE_INTEGER
);
// Returns 1
const [numbers, setNumbers] = React.useState([1, 2, 3]);
numbers[2] = 42;
setNumbers(numbers); // THIS DOES NOT WORK!
[] === []; // false
[1, 2, 3] === [1, 2, 3]; // false
{"firstname": "Ada"} === {"firstname": "Ada"}; // false
{} === {}; // false
const a = [1, 2, 3];
const b = a;
a === b; // true
b.push(42)
a === b // true
const a = [1, 2, 3];
const b = [...a, 6];
function append<T>(array: T[], value: T) {
return [...array, value];
}
function append<T>(array: T[], value: T) {
return [value, ...array];
}
function remove<T>(array: T[], index: number) {
return [
...array.slice(0, index),
...array.slice(index + 1),
];
}
function replace<T>(array: T[], value: T, index: number) {
return [
...array.slice(0, index),
value,
...array.slice(index + 1),
];
}
const ada = { firstname: "Ada", lastname: "Lovelace" };
const ada2 = { ...ada, age: 42 };
Write a <Form />
component with a single state variable of the following type:
interface UserForm {
firstname: string;
lastname: string;
age: number;
}
Your component should show one <input>
for each property of the UserForm
interface, a submit button. On form submission (using onSubmit
event), your show the content of the fields to the page.
Please work on a 03-react/06-form
directory in your exercises repository.
Reproduce your todo list with React.
03-react/examples/01-hello-world
example to a folder 03-react/07-todo-list
in your exercises repository.npm install
.npm run build
(to compile once) or npm run build:watch
(to automatically recompile on every change).State
typeDefine a TodoItem
type representing an individual todo list item. It should an object with the following attributes:
created
(a number),done
(a boolean),title
(a string).The created
attribute will hold the timestamp of the creation time of the item and will be used as a unique ID. See Date.now()
.
State
typeDefine an initial example state in a initialTodos
variable of type TodoItem[]
.
todos
state variableEdit the App
component so that it contains a state variable todos
, with the initial value initialTodos
.
Render the todos
variable into a list, with a checkbox for each item.
Add event handlers so that the checkbox can be checked/unchecked, and that the state is updated accordingly.
Add a form with a single input allowing to add an item.
Save the state of your app to local storage.
Add a <select>
element allowing to filter items by “All”, “To do” or “Done”.
useEffect
hookuseEffect(() => console.log("Component did mount."), []);
const [text, setText] = React.useStat("");
useEffect(
() => console.log("State variable 'text' changed."),
[text]
);
export default function App() {
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
</nav>
{/* A <Switch> looks through its children <Route>s and
renders the first one that matches the current URL. */}
<Switch>
<Route path="/about">About</Route>
<Route path="/">Home</Route>
</Switch>
</div>
</Router>
);
}
function User() {
const params = useParams<{ id: string }>();
return <p>{param.id}</p>;
}
export default function App() {
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/user/1">User 1</Link>
</li>
<li>
<Link to="/user/2">User 2</Link>
</li>
</ul>
</nav>
<Switch>
<Route path="/user/:id" children={<User />}>
About
</Route>
<Route path="/">Home</Route>
</Switch>
</div>
</Router>
);
}
/participant/:name
, show information about a single participant with GitHub username :name
.03-react/08-participants
in your exercises repository. You can start from the 07-animals
example and edit it (please remove the awful styles!).