Built at: 2024-10-16T09:55:25.150Z Skip to content

part1d

complex state

two seperate state
const App = () => {
const [left, setLeft] = useState(0);
const [right, setRight] = useState(0);
return (
<div>
{left}
<button onClick={() => setLeft(left + 1)}>left</button>
<button onClick={() => setRight(right + 1)}>right</button>
{right}
</div>
);
};
combined in one object
const App = () => {
const [clicks, setClicks] = useState({
left: 0,
right: 0,
});
const handleLeftClick = () => {
const newClicks = {
left: clicks.left + 1,
right: clicks.right,
};
setClicks(newClicks);
};
const handleRightClick = () => {
const newClicks = {
left: clicks.left,
right: clicks.right + 1,
};
setClicks(newClicks);
};
return (
<div>
{clicks.left}
<button onClick={handleLeftClick}>left</button>
<button onClick={handleRightClick}>right</button>
{clicks.right}
</div>
);
};
object spread syntax
const handleLeftClick = () => {
const newClicks = {
...clicks,
left: clicks.left + 1,
};
setClicks(newClicks);
};
const handleRightClick = () => {
const newClicks = {
...clicks,
right: clicks.right + 1,
};
setClicks(newClicks);
};
refactoring
const handleLeftClick = () => setClicks({ ...clicks, left: clicks.left + 1 });
const handleRightClick = () =>
setClicks({ ...clicks, right: clicks.right + 1 });

Do not mutate state directly in React, it can result in unexpcected side effect.

use seperate state, and handling arrays
const App = () => {
const [left, setLeft] = useState(0);
const [right, setRight] = useState(0);
const [allClicks, setAll] = useState([]);
const handleLeftClick = () => {
setAll(allClicks.concat("L"));
setLeft(left + 1);
};
const handleRightClick = () => {
setAll(allClicks.concat("R"));
setRight(right + 1);
};
return (
<div>
{left}
<button onClick={handleLeftClick}>left</button>
<button onClick={handleRightClick}>right</button>
{right}
<p>{allClicks.join(" ")}</p>
</div>
);
};

Update of the state is asynchronous

codes that have bugs(the total number is incorrect)
const App = () => {
const [left, setLeft] = useState(0);
const [right, setRight] = useState(0);
const [allClicks, setAll] = useState([]);
const [total, setTotal] = useState(0);
const handleLeftClick = () => {
setAll(allClicks.concat("L"));
setLeft(left + 1);
setTotal(left + right);
};
const handleRightClick = () => {
setAll(allClicks.concat("R"));
setRight(right + 1);
setTotal(left + right);
};
return (
<div>
{left}
<button onClick={handleLeftClick}>left</button>
<button onClick={handleRightClick}>right</button>
{right}
<p>{allClicks.join(" ")}</p>
<p>total {total}</p>
</div>
);
};
fixing
const handleLeftClick = () => {
setAll(allClicks.concat("L"));
const updatedLeft = left + 1;
setLeft(updatedLeft);
setTotal(updatedLeft + right);
};
conditional rendering
const History = (props) => {
if (props.allClicks.length === 0) {
return <div>the app is used by pressing the buttons</div>;
}
return <div>button press history: {props.allClicks.join(" ")}</div>;
};
const App = () => {
// ...
return (
<div>
{left}
<button onClick={handleLeftClick}>left</button>
<button onClick={handleRightClick}>right</button>
{right}
<History allClicks={allClicks} />
</div>
);
};

Debugging React applications

The first rule of web development
Keep the browser’s development console open at all times.

Don’t write more code when an error occured, fix the problem immediately.

debugger in the code can be helpful
You can also use breakpoints in the Sources tab
react-devtools for Edge/Chrome is also very helpful

Inspect the variables value in console

Rules of Hooks

The useState function(as well as useEffect etc) must NOT be called from inside of a loop, a conditional expression, or any place that is not a function defining a component.

const App = () => {
// these are ok
const [age, setAge] = useState(0)
const [name, setName] = useState('Juha Tauriainen')
if ( age > 10 ) {
// this does not work!
const [foobar, setFoobar] = useState(null)
}
for ( let i = 0; i < age; i++ ) {
// also this is not good
const [rightWay, setRightWay] = useState(false)
}
const notGood = () => {
// and this is also illegal
const [x, setX] = useState(-1000)
}
return (
//...
)
}

Event Handling Revisited

Defining event handlers directly in the attribute of the button is not necessarily the best possible idea.
Write a handler for it.

function that returns function
const App = () => {
const [value, setValue] = useState(10);
const hello = (who) => {
return () => {
console.log("hello", who);
};
};
return (
<div>
{value}
<button onClick={hello("world")}>button</button>
<button onClick={hello("react")}>button</button>
<button onClick={hello("function")}>button</button>
</div>
);
};
two ways of defining handlers, 1
const App = () => {
const [value, setValue] = useState(10);
const setToValue = (newValue) => {
console.log("value now", newValue);
setValue(newValue);
};
return (
<div>
{value}
<button onClick={() => setToValue(1000)}>thousand</button>
<button onClick={() => setToValue(0)}>reset</button>
<button onClick={() => setToValue(value + 1)}>increment</button>
</div>
);
};
two ways of defining handlers, 2
const App = () => {
const [value, setValue] = useState(10);
const setToValue = (newValue) => () => {
// function that returns funtion
console.log("value now", newValue); // print the new value to console
setValue(newValue);
};
return (
<div>
{value}
<button onClick={setToValue(1000)}>thousand</button>
<button onClick={setToValue(0)}>reset</button>
<button onClick={setToValue(value + 1)}>increment</button>
</div>
);
};

Passing event handlers to child components

const Button = (props) => (
<button onClick={props.handleClick}>
{props.text}
</button>
)
// then in App
<Button handleClick={() => setToValue(1000)} text="thousand" />
<Button handleClick={() => setToValue(0)} text="reset" />
<Button handleClick={() => setToValue(value + 1)} text="increment" />

Do not define components within components

It seems ok.
The biggest problems are because React treats a component defined inside of another component as a new component in every render.
This makes it impossible for React to optimize the component.

Web programmers oath

  • I will have my browser developer console open all the time
  • I progress with small steps
  • I will write lots of console.log statements to make sure I understand how the code behaves and to help pinpointing problems
  • If my code does not work, I will not write more code. Instead I start deleting the code until it works or just return to a state when everything was still working
  • When I ask for help in the course Discord or Telegram channel or elsewhere I formulate my questions properly, see here how to ask for help

Utilization of Large language models
such as ChatGPT, Claude and Github Copilot
on easy jobs which you totally understand
or on learning and then double confirm it.

Exercises 1.6 - 1.14.

1.6 unicafe step 1

Feedback system with three options: good, neutual, bad

  • ui

    • 3 buttons

    • statistics: good number, neutual number, bad number

      1.7 unicafe step 2

Add: the total number of collected feedback, the average score(good: 1, neutral: 0, bad: -1) and the percentage of positive feedback.

1.8 unicafe step 3

Refactor to have a Statistics component.

1.9 unicafe step 4

Display statistics only once feedback has been gathered.

1.10 unicafe step 5

Two new components:

  • Button

  • StatisticLine

    1.11* unicafe step 6

Display the statistics in an Html table.

Ref: https://developer.mozilla.org/en-US/docs/Learn/HTML/Tables/Basics


1.12* anecdotes step 1

Expand the following application by adding a button that can be clicked to display a random anecdote from the field of software engineering:

import { useState } from "react";
const App = () => {
const anecdotes = [
"If it hurts, do it more often.",
"Adding manpower to a late software project makes it later!",
"The first 90 percent of the code accounts for the first 90 percent of the development time...The remaining 10 percent of the code accounts for the other 90 percent of the development time.",
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand.",
"Premature optimization is the root of all evil.",
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.",
"Programming without an extremely heavy use of console.log is same as if a doctor would refuse to use x-rays or blood tests when diagnosing patients.",
"The only way to go fast, is to go well.",
];
const [selected, setSelected] = useState(0);
return <div>{anecdotes[selected]}</div>;
};
export default App;

1.13* anecdotes step 2

Vote button for the displayed anecdote.

ref:
create a zero-filled array of the desired length

1.14* anecdotes step 3

Show Anecdote with most votes


My solutions

My solutions of unicafe:
(Please complete your own solutions before click here.)

1.6 unicafe step 1

terminal or explorer
cd exercises/part1
mkdir unicafe
cd unicafe
copy 2 files from my practice to unicafe folder: .gitignore, package.json
copy 2 folders also: pubic, src(only includes index.js and App.js)
pnpm i (just because this is much faster than npm)
pnpm start

Basic 3 options feedback system

index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
App.js
import { useState } from "react";
const App = () => {
// save clicks of each button to its own state
const [good, setGood] = useState(0);
const [neutual, setNeutual] = useState(0);
const [bad, setBad] = useState(0);
const handleGoodClick = () => {
const updatedGood = good + 1;
setGood(updatedGood);
};
const handleNeutualClick = () => {
const updatedNeutual = neutual + 1;
setNeutual(updatedNeutual);
};
const handleBadClick = () => {
const updatedBad = bad + 1;
setBad(updatedBad);
};
return (
<div>
<h2>give feedback</h2>
<button onClick={handleGoodClick}>good</button>
<button onClick={handleNeutualClick}>neutual</button>
<button onClick={handleBadClick}>bad</button>
<h2>statistics</h2>
<p>
good {good} <br />
neutual {neutual} <br />
bad {bad} <br />
</p>
</div>
);
};
export default App;

1.7 unicafe step 2

add total feedbacks, average score, the percentage of positive
// App.js
import { useState } from "react";
const App = () => {
// save clicks of each button to its own state
const [good, setGood] = useState(0);
const [neutual, setNeutual] = useState(0);
const [bad, setBad] = useState(0);
const [total, setTotal] = useState(0);
const handleGoodClick = () => {
const updatedGood = good + 1;
setGood(updatedGood);
setTotal(updatedGood + neutual + bad);
};
const handleNeutualClick = () => {
const updatedNeutual = neutual + 1;
setNeutual(updatedNeutual);
setTotal(updatedNeutual + good + bad);
};
const handleBadClick = () => {
const updatedBad = bad + 1;
setBad(updatedBad);
setTotal(updatedBad + good + neutual);
};
return (
<div>
<h2>give feedback</h2>
<button onClick={handleGoodClick}>good</button>
<button onClick={handleNeutualClick}>neutual</button>
<button onClick={handleBadClick}>bad</button>
<h2>statistics</h2>
<p>
good {good} <br />
neutual {neutual} <br />
bad {bad} <br />
all {total} <br />
average {total && (good * 1 + neutual * 0 + bad * -1) / total} <br />
positive {total && (good / total) * 100} % <br />
</p>
</div>
);
};
export default App;

1.8 unicafe step 3

statistics component
// App.js
import { useState } from "react";
const Statistics = (props) => {
const { good, neutual, bad, total } = props;
return (
<>
<h2>statistics</h2>
<p>
good {good} <br />
neutual {neutual} <br />
bad {bad} <br />
all {total} <br />
average {total && (good * 1 + neutual * 0 + bad * -1) / total} <br />
positive {total && (good / total) * 100} % <br />
</p>
</>
);
};
const App = () => {
// save clicks of each button to its own state
const [good, setGood] = useState(0);
const [neutual, setNeutual] = useState(0);
const [bad, setBad] = useState(0);
const [total, setTotal] = useState(0);
const handleGoodClick = () => {
const updatedGood = good + 1;
setGood(updatedGood);
setTotal(updatedGood + neutual + bad);
};
const handleNeutualClick = () => {
const updatedNeutual = neutual + 1;
setNeutual(updatedNeutual);
setTotal(updatedNeutual + good + bad);
};
const handleBadClick = () => {
const updatedBad = bad + 1;
setBad(updatedBad);
setTotal(updatedBad + good + neutual);
};
return (
<div>
<h2>give feedback</h2>
<button onClick={handleGoodClick}>good</button>
<button onClick={handleNeutualClick}>neutual</button>
<button onClick={handleBadClick}>bad</button>
<Statistics good={good} neutual={neutual} bad={bad} total={total} />
</div>
);
};
export default App;

1.9 unicafe step 4

conditional rendering whether has feedbacks or not
// App.js
import { useState } from "react";
const Statistics = (props) => {
const { good, neutual, bad, total } = props;
if (total === 0) {
return (
<>
<h2>statistics</h2>
<p>No feedback given</p>
</>
);
}
return (
<>
<h2>statistics</h2>
<p>
good {good} <br />
neutual {neutual} <br />
bad {bad} <br />
all {total} <br />
average {total && (good * 1 + neutual * 0 + bad * -1) / total} <br />
positive {total && (good / total) * 100} % <br />
</p>
</>
);
};
const App = () => {
// save clicks of each button to its own state
const [good, setGood] = useState(0);
const [neutual, setNeutual] = useState(0);
const [bad, setBad] = useState(0);
const [total, setTotal] = useState(0);
const handleGoodClick = () => {
const updatedGood = good + 1;
setGood(updatedGood);
setTotal(updatedGood + neutual + bad);
};
const handleNeutualClick = () => {
const updatedNeutual = neutual + 1;
setNeutual(updatedNeutual);
setTotal(updatedNeutual + good + bad);
};
const handleBadClick = () => {
const updatedBad = bad + 1;
setBad(updatedBad);
setTotal(updatedBad + good + neutual);
};
return (
<div>
<h2>give feedback</h2>
<button onClick={handleGoodClick}>good</button>
<button onClick={handleNeutualClick}>neutual</button>
<button onClick={handleBadClick}>bad</button>
<Statistics good={good} neutual={neutual} bad={bad} total={total} />
</div>
);
};
export default App;

1.10 unicafe step 5

Button and StatisticLine components
// App.js
import { useState } from "react";
const Statistics = (props) => {
// console.log(props)
const { text, value } = props;
// console.log(value)
if (value === 0) {
return (
<>
No {props.text} feedback given <br />
</>
);
}
return (
<>
{text} {value} <br />
</>
);
};
const App = () => {
// save clicks of each button to its own state
const [good, setGood] = useState(0);
const [neutual, setNeutual] = useState(0);
const [bad, setBad] = useState(0);
const [total, setTotal] = useState(0);
const handleGoodClick = () => {
const updatedGood = good + 1;
setGood(updatedGood);
setTotal(updatedGood + neutual + bad);
};
const handleNeutualClick = () => {
const updatedNeutual = neutual + 1;
setNeutual(updatedNeutual);
setTotal(updatedNeutual + good + bad);
};
const handleBadClick = () => {
const updatedBad = bad + 1;
setBad(updatedBad);
setTotal(updatedBad + good + neutual);
};
return (
<div>
<h2>give feedback</h2>
<button onClick={handleGoodClick}>good</button>
<button onClick={handleNeutualClick}>neutual</button>
<button onClick={handleBadClick}>bad</button>
<h2>statistics</h2>
<div>
<Statistics text="good" value={good} />
<Statistics text="neutual" value={neutual} />
<Statistics text="bad" value={bad} />
average {total && (good * 1 + neutual * 0 + bad * -1) / total} <br />
positive {total && (good / total) * 100} % <br />
</div>
</div>
);
};
export default App;

1.11* unicafe step 6

Statistics in table
// App.js
import { useState } from "react";
const Statistics = (props) => {
const { text, value } = props;
if (value === 0) {
return (
<tr>
<td>{text}</td>
<td>
No {text} feedback given <br />
</td>
</tr>
);
}
return (
<tr>
<td>{text}</td>
<td>{value}</td>
</tr>
);
};
const App = () => {
// save clicks of each button to its own state
const [good, setGood] = useState(0);
const [neutual, setNeutual] = useState(0);
const [bad, setBad] = useState(0);
const [total, setTotal] = useState(0);
const handleGoodClick = () => {
const updatedGood = good + 1;
setGood(updatedGood);
setTotal(updatedGood + neutual + bad);
};
const handleNeutualClick = () => {
const updatedNeutual = neutual + 1;
setNeutual(updatedNeutual);
setTotal(updatedNeutual + good + bad);
};
const handleBadClick = () => {
const updatedBad = bad + 1;
setBad(updatedBad);
setTotal(updatedBad + good + neutual);
};
return (
<div>
<h2>give feedback</h2>
<button onClick={handleGoodClick}>good</button>
<button onClick={handleNeutualClick}>neutual</button>
<button onClick={handleBadClick}>bad</button>
<h2>statistics</h2>
<div>
<table>
<tbody>
<Statistics text="good" value={good} />
<Statistics text="neutual" value={neutual} />
<Statistics text="bad" value={bad} />
<tr>
<td>all</td>
<td>{total}</td>
</tr>
<tr>
<td>average</td>
<td>{total && (good * 1 + neutual * 0 + bad * -1) / total}</td>
</tr>
<tr>
<td>positive</td>
<td>{total && (good / total) * 100} %</td>
</tr>
</tbody>
</table>
</div>
</div>
);
};
export default App;
My solutions of anecdotes:
(Please complete your own solutions before click here.)

1.12* anecdotes step 1

The most quick way(my way) of starting a new project while just completed one(to keep the dependencies updated).

terminal or explorer
cd exercises/part1
mkdir anecdotes
cd anecdotes
copy 3 files from the last project into anecdotes: .gitignore, package.json, README.md
copy 2 folders also: public, src(index.js, App.js), the index.js will keep the same.
code .
update README
ctrl + `
pnpm i
pnpm start

ref:
Solve console.log twice issue in react app

randomly show an anecdote
// App.js
import { useState } from "react";
// ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
function getRandomInt(max) {
return Math.floor(Math.random() * max);
}
const App = () => {
const anecdotes = [
"If it hurts, do it more often.",
"Adding manpower to a late software project makes it later!",
"The first 90 percent of the code accounts for the first 90 percent of the development time...The remaining 10 percent of the code accounts for the other 90 percent of the development time.",
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand.",
"Premature optimization is the root of all evil.",
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.",
"Programming without an extremely heavy use of console.log is same as if a doctor would refuse to use x-rays or blood tests when diagnosing patients.",
"The only way to go fast, is to go well.",
];
const [selected, setSelected] = useState(0);
// console.log(getRandomInt(anecdotes.length))
const handleClick = () => setSelected(getRandomInt(anecdotes.length));
// console.log(selected)
return (
<div>
{anecdotes[selected]} <br />
<button onClick={handleClick}>next anecdote</button>
</div>
);
};
export default App;

1.13* anecdotes step 2

vote button and show how many votes for the displaying anecdote
// App.js
import { useState } from "react";
function getRandomInt(max) {
return Math.floor(Math.random() * max);
}
const anecdotes = [
"If it hurts, do it more often.",
"Adding manpower to a late software project makes it later!",
"The first 90 percent of the code accounts for the first 90 percent of the development time...The remaining 10 percent of the code accounts for the other 90 percent of the development time.",
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand.",
"Premature optimization is the root of all evil.",
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.",
"Programming without an extremely heavy use of console.log is same as if a doctor would refuse to use x-rays or blood tests when diagnosing patients.",
"The only way to go fast, is to go well.",
];
const points = new Array(anecdotes.length).fill(0);
// const point = new Uint8Array(anecdotes.length) may also work, but the last line is more "discribe itself"
// ref: https://stackoverflow.com/questions/20222501/how-to-create-a-zero-filled-javascript-array-of-arbitrary-length
const App = () => {
const [selected, setSelected] = useState(0);
const [pointstate, setPointstate] = useState(points);
const handleVote = () => {
const copy = [...pointstate];
// console.log(copy)
copy[selected] += 1;
setPointstate(copy);
// console.log(copy)
};
const handleNext = () => setSelected(getRandomInt(anecdotes.length));
return (
<div>
{anecdotes[selected]} <br />
has {pointstate[selected]} votes <br />
<button onClick={handleVote}>vote</button>
<button onClick={handleNext}>next anecdote</button>
</div>
);
};
export default App;

1.14* anecdotes step 3

show most voted anecdote
import { useState } from "react";
function getRandomInt(max) {
return Math.floor(Math.random() * max);
}
const anecdotes = [
"If it hurts, do it more often.",
"Adding manpower to a late software project makes it later!",
"The first 90 percent of the code accounts for the first 90 percent of the development time...The remaining 10 percent of the code accounts for the other 90 percent of the development time.",
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand.",
"Premature optimization is the root of all evil.",
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.",
"Programming without an extremely heavy use of console.log is same as if a doctor would refuse to use x-rays or blood tests when diagnosing patients.",
"The only way to go fast, is to go well.",
];
const points = new Array(anecdotes.length).fill(0);
const App = () => {
const [selected, setSelected] = useState(0);
const [pointstate, setPointstate] = useState(points);
const [mostvotedindex, setMostvotedindex] = useState(0);
const handleVote = () => {
const copy = [...pointstate];
// console.log("vote before", copy)
copy[selected] += 1;
// console.log("vote after", copy)
setPointstate(copy);
setMostvotedindex(maxIndex(copy));
};
const handleNext = () => {
const nextIndex = getRandomInt(anecdotes.length);
// console.log("nextIdex", nextIndex)
setSelected(nextIndex);
};
const maxIndex = (pointArray) => {
// console.log("pointArray", pointArray)
const copy = [...pointArray];
let max = 0;
let i;
for (i = 1; i < copy.length; i++) {
if (copy[i] > copy[max]) max = i;
}
// console.log("maxIndex", max)
return max;
};
return (
<div>
<h2>Anecdote of the day</h2>
{anecdotes[selected]} <br />
has {pointstate[selected]} votes <br />
<button onClick={handleVote}>vote</button>
<button onClick={handleNext}>next anecdote</button>
<h2>Anecdote with most votes</h2>
{anecdotes[mostvotedindex]} <br />
has {pointstate[mostvotedindex]} votes <br />
</div>
);
};
export default App;