Skip to content

Part 1: Introduction to React (Summary & Exercise solutions) | Full Stack Open Course from the University of Helsinki 2023/2024

Notifications You must be signed in to change notification settings

urmulu-riza/FullStackOpen-Part1

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 

Repository files navigation

part letter
1
a

a- Introduction to React

You may use the new generation frontend tool Vite or the default tool create-react-app to get started.

npx create-react-app part1
cd part1
npm start

The applicaiton runs on http://localhost:3000

The file index.js looks like this

import React from 'react';
import ReactDOM from 'react-dom/client';

import App from './App';

ReactDOM.createRoot(document.getElementById('root')).render(<App />);

and file App.js looks like this

const App = () => (
  <div>
    <p>Hello world</p>
  </div>
);

export default App;

Component

The file public/index.js looks like this :

<!DOCTYPE html>
<html lang="en">
  <head>
    content not shown ...
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

There are a few ways to define functions in JavaScript. Here we will use arrow functions, which are described in a newer version of JavaScript known as ECMAScript 6, also called ES6.

The first rule of frontend web development:

keep the console open all the time

Rendering a dynamic content inside of a component:

const App = () => {
  const now = new Date();
  return (
    <div>
      <p>Hello world, it is {now.toString()}</p>
    </div>
  );
};
export default App;

JSX

The layout of React components is mostly written using JSX. Although JSX looks like HTML, we are dealing with a way to write JavaScript. Under the hood, JSX returned by React components is compiled into JavaScript.

const App = () => {
  const now = new Date();
  return React.createElement(
    'div',
    null,
    React.createElement('p', null, 'Hello world, it is ', now.toString())
  );
};

The compilation is handled by Babel. JSX is "XML-like", which means that every tag needs to be closed. For example, a newline is an empty element, which in HTML can be written as follows:

<br />

but when writing JSX, the tag needs to be closed:

<br />

Multiple Components

A core philosophy of React is composing applications from many specialized reusable components.

Another strong convention is the idea of a root component called App at the top of the component tree of the application. Nevertheless, as we will learn in part 6, there are situations where the component App is not exactly the root, but is wrapped within an appropriate utility component.

props: passing data to components

It is possible to pass data to components using so-called props.

const Hello = ({ name, age }) => {
  // highlight-line
  return (
    <div>
      Hello {name}, you are {age} years old // highlight-line
    </div>
  );
};

The props are defined as follows:

const App = () => {
  const name = 'Peter'; // highlight-line
  const age = 10; // highlight-line
  return (
    <div>
      <h1>Greetings</h1>
      <Hello name="Maya" age={26 + 10} /> // highlight-line
      <Hello name={name} age={age} /> // highlight-line
    </div>
  );
};

Keep in mind that React component names must be capitalized.

Note that the content of a React component (usually) needs to contain one root element.

Using a root element is not the only working option. An array of components is also a valid solution:

const App = () => {
  return [<h1>Greetings</h1>, <Hello name="Maya" age={26 + 10} />, <Footer />];
};

However, when defining the root component of the application this is not a particularly wise thing to do, and it makes the code look a bit ugly.

Because the root element is stipulated, we have "extra" div elements in the DOM tree. This can be avoided by using fragments, i.e. by wrapping the elements to be returned by the component with an empty element:

const App = () => {
  const name = 'Peter';
  const age = 10;

  return (
    <>
      <h1>Greetings</h1>
      <Hello name="Maya" age={26 + 10} />
      <Hello name={name} age={age} />
      <Footer />
    </>
  );
};

It now compiles successfully, and the DOM generated by React no longer contains the extra div element.

Do not render objects

Consider an application that prints the names and ages of our friends on the screen:

const App = () => {
  const friends = [
    { name: 'Peter', age: 4 },
    { name: 'Maya', age: 10 },
  ];

  return (
    <div>
      <p>{friends[0]}</p>
      <p>{friends[1]}</p>
    </div>
  );
};

export default App;

The core of the problem is Objects are not valid as a React child, i.e. the application tries to render objects and it fails again.

In React, the individual things rendered in braces must be primitive values, such as numbers or strings.

The fix is ​​as follows

  ....
  return (
    <div>
      <p>
        {friends[0].name} {friends[0].age}
      </p>
      <p>
        {friends[1].name} {friends[1].age}
      </p>
    </div>
  );
  ....

React also allows arrays to be rendered if the array contains values ​​that are eligible for rendering (such as numbers or strings).

Famous software developer Robert "Uncle Bob" Martin has stated

"The only way to go fast, is to go well"

that is, according to Martin, careful progress with small steps is even the only way to be fast.

WARNING2 create-react-app automatically makes the project a git repository unless the application is created within an already existing repository. Most likely you do not want the project to become a repository, so run the command rm -rf .git in the root of the project.

b- Javascript

The official name of the JavaScript standard is ECMAScript. At this moment, the latest version is the one released in June of 2022 with the name ECMAScript®2022, otherwise known as ES13.

Browsers do not yet support all of JavaScript's newest features. Due to this fact, a lot of code run in browsers has been transpiled from a newer version of JavaScript to an older, more compatible version.

Today, the most popular way to do transpiling is by using Babel.

Node.js is a JavaScript runtime environment based on Google's Chrome V8 JavaScript engine and works practically anywhere - from servers to mobile phones. Let's practice writing some JavaScript using Node. The latest versions of Node already understand the latest versions of JavaScript, so the code does not need to be transpiled.

Where to run Node.js:

1.JS Bin.

2.the browser's developer tool console

3.Node.js console, which is opened by typing node in the command line

Variables

const x = 1;
let y = 5;

console.log(x, y); // 1, 5 are printed
y += 10;
console.log(x, y); // 1, 15 are printed
y = 'sometext';
console.log(x, y); // 1, sometext are printed
x = 4; // causes an error

const and let

The variable's data type can change during execution

var. See JavaScript Variables - Should You Use let, var or const? on Medium or Keyword: var vs. let on JS Tips or watch var, let and const - ES6 JavaScript Features for more information.

Arrays

const t = [1, -1, 3];

t.push(5);

console.log(t.length); // 4 is printed
console.log(t[1]); // -1 is printed

t.forEach((value) => {
  console.log(value); // numbers 1, -1, 3, 5 are printed, each to own line
});

Notable in this example is the fact that the contents of the array can be modified even though it is defined as a const. Because the array is an object, the variable always points to the same object. However, the content of the array changes as new items are added to it.

forEach calls the function for each of the items in the array, always passing the individual item as an argument. The function as the argument of forEach may also receive other arguments.

In the previous example, a new item was added to the array using the method push.

When using React, techniques from functional programming are often used. One characteristic of the functional programming paradigm is the use of immutable data structures. In React code, it is preferable to use the method concat, which creates a new array with the added item. This ensures the original array remains unchanged.

const m1 = t.map((value) => value * 2);

Based on the old array, map creates a new array, for which the function given as a parameter is used to create the items.

const m2 = t.map((value) => '<li>' + value + '</li>');
console.log(m2);
// [ '<li>1</li>', '<li>2</li>', '<li>3</li>' ] is printed

destructuring assignment.

const t = [1, 2, 3, 4, 5];

const [first, second, ...rest] = t;

console.log(first, second); // 1, 2 is printed
console.log(rest); // [3, 4, 5] is printed

Objects

const object3 = {
  name: {
    first: 'Dan',
    last: 'Abramov',
  },
  grades: [2, 3, 5, 3],
  department: 'Stanford University',
};
console.log(object1.name); // Arto Hellas is printed with dot notations
const fieldName = 'age';
console.log(object1[fieldName]); // 35 is printed with brackets []
object1.address = 'Helsinki';
object1['secret number'] = 12341;

The latter of the additions has to be done by using brackets because of the space. Naturally, objects in JavaScript can also have methods.

function declaration.

function product(a, b) {
  return a * b;
}

const result = product(2, 6);
// result is now 12

The other way to define the function is by using a function expression. In this case, there is no need to give the function a name and the definition may reside among the rest of the code:

const average = function (a, b) {
  return (a + b) / 2;
};

const result = average(2, 5);
// result is now 3.5

During this course, we will define all functions using the arrow syntax.

###Object methods and "this"

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  greet() {
    console.log('hello, my name is ' + this.name);
  }
}

const adam = new Person('Adam Ondra', 29);
adam.greet();

const janja = new Person('Janja Garnbret', 23);
janja.greet();

At the core, they are still objects based on JavaScript's prototypal inheritance. The type of both objects is actually Object, since JavaScript essentially only defines the types Boolean, Null, Undefined, Number, String, Symbol, BigInt, and Object.

The introduction of the class syntax was a controversial addition. Check out Not Awesome: ES6 Classes or Is “Class” In ES6 The New “Bad” Part? on Medium.

JavaScript materials

There exist both good and poor guides for JavaScript on the Internet. Most of the links on this page relating to JavaScript features reference Mozilla's JavaScript Guide.

It is highly recommended to immediately read A re-introduction to JavaScript (JS tutorial) on Mozilla's website.

If you wish to get to know JavaScript deeply there is a great free book series on the Internet called You-Dont-Know-JS.

Another great resource for learning JavaScript is javascript.info.

The free and highly engaging book Eloquent JavaScript takes you from the basics to interesting stuff quickly. It is a mixture of theory projects and exercises and covers general programming theory as well as the JavaScript language.

Namaste 🙏 JavaScript is another great and highly recommended free JavaScript tutorial in order to understand how JS works under the hood. Namaste JavaScript is a pure in-depth JavaScript course released for free on YouTube. It will cover the core concepts of JavaScript in detail and everything about how JS works behind the scenes inside the JavaScript engine.

egghead.io has plenty of quality screencasts on JavaScript, React, and other interesting topics. Unfortunately, some of the material is behind a paywall.

c- Component state, event handlers

allows us to destructure values from objects and arrays upon assignment.

Page re-rendering

Counter with props:

export default App = ({ counter }) => <div>{counter}</div>;
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

let counter = 1;
const refresh = () => {
  ReactDOM.createRoot(document.getElementById('root')).render(
    <App counter={counter} />
  );
};
setInterval(() => {
  refresh();
  counter += 1;
}, 1000);

Stateful component

import { useState } from 'react'; // highlight-line

const App = () => {
  const [counter, setCounter] = useState(0); // highlight-line

  // highlight-start
  setTimeout(() => setCounter(counter + 1), 1000);
  // highlight-end

  return <div>{counter}</div>;
};

export default App;

The function call adds state to the component and renders it initialized with the value of zero. The function returns an array that contains two items. We assign the items to the variables counter and setCounter by using the destructuring assignment syntax shown earlier.

The counter variable is assigned the initial value of state which is zero. The variable setCounter is assigned a function that will be used to modify the state.

The application calls the setTimeout function and passes it two parameters: a function to increment the counter state and a timeout of one second.

The function passed as the first parameter to the setTimeout function is invoked one second after calling the setTimeout function

When the state modifying function setCounter is called, React re-renders the component which means that the function body of the component function gets re-executed:

The second time the component function is executed it calls the useState function and returns the new value of the state: 1. Executing the function body again also makes a new function call to setTimeout, which executes the one-second timeout and increments the counter state again. Because the value of the counter variable is 1, incrementing the value by 1 is essentially the same as an expression setting the value of counter to 2.

Every time the setCounter modifies the state it causes the component to re-render. The value of the state will be incremented again after one second, and this will continue to repeat for as long as the application is running.

Event handling

const App = () => {
  const [counter, setCounter] = useState(0);

  // highlight-start
  const handleClick = () => {
    console.log('clicked');
  };
  // highlight-end

  return (
    <div>
      <div>{counter}</div>
      // highlight-start
      <button onClick={handleClick}>plus</button>
      // highlight-end
    </div>
  );
};

An event handler is a function

An event handler is supposed to be either a function or a function reference, and when we write:

<button onClick={setCounter(counter + 1)}>

the event handler is actually a function call. In many situations this is ok, but not in this particular situation. In the beginning, the value of the counter variable is 0. When React renders the component for the first time, it executes the function call setCounter(0+1), and changes the value of the component's state to 1. This will cause the component to be re-rendered, React will execute the setCounter function call again, and the state will change leading to another rerender...

Usually defining event handlers within JSX-templates is not a good idea.

Passing state - to child components

It's recommended to write React components that are small and reusable across the application and even across projects.

One best practice in React is to lift the state up in the component hierarchy. The documentation says:

Often, several components need to reflect the same changing data. We recommend lifting the shared state up to their closest common ancestor.

const App = () => {
  const [counter, setCounter] = useState(0);

  const increaseByOne = () => setCounter(counter + 1);
  //highlight-start
  const decreaseByOne = () => setCounter(counter - 1);
  //highlight-end
  const setToZero = () => setCounter(0);

  return (
    <div>
      <Display counter={counter} />
      // highlight-start
      <Button handleClick={increaseByOne} text="plus" />
      <Button handleClick={setToZero} text="zero" />
      <Button handleClick={decreaseByOne} text="minus" />
      // highlight-end
    </div>
  );
};

Changes in state cause rerendering

How application works:

When the application starts, the code in App is executed. This code uses a useState hook to create the application state, setting an initial value of the variable counter. This component contains the Display component - which displays the counter's value, 0 - and three Button components. The buttons all have event handlers, which are used to change the state of the counter.

When one of the buttons is clicked, the event handler is executed. The event handler changes the state of the App component with the setCounter function. Calling a function that changes the state causes the component to rerender. This causes its subcomponents Display and Button to also be re-rendered.

1.d Complex state

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>
  );
};

We can define the new state object a bit more neatly by using the object spread syntax that was added to the language specification in the summer of 2018:

const handleLeftClick = () => setClicks({ ...clicks, left: clicks.left + 1 });

const handleRightClick = () =>
  setClicks({ ...clicks, right: clicks.right + 1 });

The application appears to work. However, it is forbidden in React to mutate state directly, since it can result in unexpected side effects.

Storing all of the state in a single state object is a bad choice for this particular application; There are situations where it can be beneficial to store a piece of application state in a more complex data structure. The official React documentation contains some helpful guidance on the topic.

Handling arrays

const App = () => {
  const [left, setLeft] = useState(0);
  const [right, setRight] = useState(0);
  const [allClicks, setAll] = useState([]); // highlight-line

  // highlight-start
  const handleLeftClick = () => {
    setAll(allClicks.concat('L'));
    setLeft(left + 1);
  };
  // highlight-end

  // highlight-start
  const handleRightClick = () => {
    setAll(allClicks.concat('R'));
    setRight(right + 1);
  };
  // highlight-end

  return (
    <div>
      {left}
      <button onClick={handleLeftClick}>left</button>
      <button onClick={handleRightClick}>right</button>
      {right}
      <p>{allClicks.join(' ')}</p> // highlight-line
    </div>
  );
};

Adding the new item to the array is accomplished with the concat method, which does not mutate the existing array but rather returns a new copy of the array with the item added to it.

Update of the state is asynchronous

A state update in React happens asynchronously, i.e. not immediately but "at some point" before the component is rendered again.

We can fix the app as follows:

const App = () => {
  // ...
  const handleLeftClick = () => {
    setAll(allClicks.concat('L'));
    const updatedLeft = left + 1;
    setLeft(updatedLeft);
    setTotal(updatedLeft + right);
  };

  // ...
};

Conditional Rendering

The History component renders completely different React elements depending on the state of the application. This is called conditional rendering.

React also offers many other ways of doing 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>;
};

// highlight-start
const Button = ({ handleClick, text }) => (
  <button onClick={handleClick}>{text}</button>
);
// highlight-end

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}
      // highlight-start
      <Button handleClick={handleLeftClick} text="left" />
      <Button handleClick={handleRightClick} text="right" />
      // highlight-end
      {right}
      <History allClicks={allClicks} />
    </div>
  );
};

Debugging React applications

A large part of a typical developer's time is spent on debugging and reading existing code. Every now and then we do get to write a line or two of new code, but a large part of our time is spent trying to figure out why something is broken or how something works. Good practices and tools for debugging are extremely important for this reason.

NB When you use console.log for debugging, don't combine objects in a Java-like fashion by using the plus operator:

console.log('props value is ' + props);

If you do that, you will end up with a rather uninformative log message:

props value is [object Object]

Instead, separate the things you want to log to the console with a comma:

console.log('props value is', props);

In this way, the separated items will all be available in the browser console for further inspection.

You can pause the execution of your application code in the Chrome developer console's debugger, by writing the command debugger anywhere in your code.

The execution will pause once it arrives at a point where the debugger command gets executed.

You can also access the debugger without the debugger command by adding breakpoints in the Sources tab. Inspecting the values of the component's variables can be done in the Scope-section:

It is highly recommended to add the React developer tools extension to Chrome. It adds a new Components tab to the developer tools. The new developer tools tab can be used to inspect the different React elements in the application, along with their state and props:

Rules of Hooks

The useState function (as well as the useEffect function introduced later on in the course) must not be called from inside of a loop, a conditional expression, or any place that is not a function defining a component. This must be done to ensure that the hooks are always called in the same order, and if this isn't the case the application will behave erratically.

To recap, hooks may only be called from the inside of a function body that defines a React component:

Event Handling Revisited

<button onClick={console.log('clicked the button')}>button</button>

The message gets printed to the console once when the component is rendered but nothing happens when we click the button. Why does this not work even when our event handler contains a function console.log?

The issue here is that our event handler is defined as a function call which means that the event handler is assigned the returned value from the function, which in the case of console.log is undefined.

The console.log function call gets executed when the component is rendered and for this reason, it gets printed once to the console.

A function that returns a function

const App = () => {
  const [value, setValue] = useState(10);

  // highlight-start
  const hello = (who) => () => {
    console.log('hello', who);
  };
  // highlight-end

  return (
    <div>
      {value}
      <button onClick={hello()}>button</button>
    </div>
  );
};

We can use the same trick to define event handlers that set the state of the component to a given value. Let's make the following changes to our code:

const App = () => {
  const [value, setValue] = useState(10);

  // highlight-start
  const setToValue = (newValue) => () => {
    console.log('value now', newValue); // print the new value to console
    setValue(newValue);
  };
  // highlight-end

  return (
    <div>
      {value}
      // highlight-start
      <button onClick={setToValue(1000)}>thousand</button>
      <button onClick={setToValue(0)}>reset</button>
      <button onClick={setToValue(value + 1)}>increment</button>
      // highlight-end
    </div>
  );
};

Using functions that return functions is not required to achieve this functionality. Let's return the setToValue function which is responsible for updating state into a normal function:

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>
  );
};

Choosing between the two presented ways of defining your event handlers is mostly a matter of taste.

Passing Event Handlers to Child Components

Easy

Do Not Define Components Within Components

Never define components inside of other components. The method provides no benefits and leads to many unpleasant problems. 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.

Useful Reading

The internet is full of React-related material. However, we use the new style of React for

  • The official React documentation is worth checking out at some point, although most of it will become relevant only later on in the course. Also, everything related to class-based components is irrelevant to us;
  • Some courses on Egghead.io like Start learning React are of high quality, and the recently updated Beginner's Guide to React is also relatively good; both courses introduce concepts that will also be introduced later on in this course. NB The first one uses class components but the latter uses the new functional ones.

How to Round: Math.round() to round to the nearest integer. parseInt() to parse to the nearest whole number./Slowest Math.trunc() to completely strip the decimal. toFixed() to remove a certain amount of decimals.

How to create a zero filled JavaScript array of arbitrary length?

HTML tables basic

using-reduce-to-find-min-and-max-values

About

Part 1: Introduction to React (Summary & Exercise solutions) | Full Stack Open Course from the University of Helsinki 2023/2024

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published