Skip to content

Commit 1713119

Browse files
committed
part 7
1 parent 608cc79 commit 1713119

7 files changed

Lines changed: 139 additions & 110 deletions

File tree

src/components/InfoBanner3.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,20 @@ const InfoBanner = ({ visible, onHide }) => {
4949
<div style={textStyle}>
5050
<div style={{ marginBottom: 20 }}>
5151
<div style={{ marginBottom: 10 }}>
52-
Material of Part 11 has been moved to <a style={linkStyle} href="https://courses.mooc.fi/org/uh-cs/courses/full-stack-open-continuous-integration">https://courses.mooc.fi/org/uh-cs/courses/full-stack-open-continuous-integration</a>
52+
<b>Changes in part 7 (6th April 2026):</b>
53+
<ul style={{ marginTop: 6, paddingLeft: 20 }}>
54+
<li><i>React router, UI libraries and Styled components moved to part 5</i></li>
55+
<li><i>Webpack replaced with esbuild</i></li>
56+
<li><i>Error boundaries and keeping the frontend and backend in a single repository covered</i></li>
57+
<li><i>Some of the hook exercises have changed</i></li>
58+
<li><i>Two new exercises for the blog list</i></li>
59+
</ul>
5360
</div>
54-
<div style={{ marginBottom: 10 }}>
55-
The content and exercises are still same, there is a change how exercises are submitted.
61+
<div style={{ marginBottom: 10 }}>
62+
There may still be some minor changes coming to the blog list exercise set in the next few days.
5663
</div>
5764
<div>
58-
The old content is still found <a style={linkStyle} href="https://github.com/fullstack-hy2020/fullstack-hy2020.github.io/tree/7599b17c02b056fcad4f12d8708f0e07980b7564/src/content/11">here</a>.
65+
The old content is still found <a style={linkStyle} href="https://github.com/fullstack-hy2020/fullstack-hy2020.github.io/tree/7599b17c02b056fcad4f12d8708f0e07980b7564/src/content/7/en">here</a>.
5966
</div>
6067
</div>
6168
</div>

src/components/layout.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import PropTypes from 'prop-types';
1313
import SkipToContent from './SkipToContent/SkipToContent';
1414

1515
const BANNER_TO_KEY = 'part_09_changes';
16-
const BANNER3_TO_KEY = 'part_11_changes';
16+
const BANNER3_TO_KEY = 'part_7_changes';
1717
const BANNER2_TO_KEY = 'part_6_changes';
1818

1919
const Layout = (props) => {
@@ -45,7 +45,7 @@ const Layout = (props) => {
4545
useEffect(() => {
4646
const key = localStorage.getItem(BANNER3_TO_KEY);
4747
if (!key) {
48-
const relevant = window.location.href.includes('en/part14') || window.location.href.includes('osa14');
48+
const relevant = window.location.href.includes('en/part7') || window.location.href.includes('osa7');
4949
setVisible3(relevant);
5050
}
5151
}, []);

src/content/7/en/part7.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ The seventh part of the course covers several topics. We begin by looking at how
1212
- <i>React router, UI libraries and Styled components moved to part 5</i>
1313
- <i>Webpack replaced with esbuild</i>
1414
- <i>Error boundaries and keeping the frontend and backend in a single repository covered</i>
15-
- <i>Two new exercises</i>
15+
- <i>Some of the exercices changed</i>
1616

1717
</div>

src/content/7/en/part7a.md

Lines changed: 93 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ lang: en
77

88
<div class="content">
99

10-
The exercises in this part of the course differ a bit from the ones before. As usual, there are some exercises related to the theory of the <i>this</i> chapter. The other chapters of this part do not have separate exercises.
10+
The exercises in this part of the course differ a bit from the ones before. As usual, there are some exercises related to the theory of this chapter. The other chapters of this part do not have separate exercises.
1111

12-
The rest of the exercises in this part are found [here](/en/part7/exercises_extending_the_bloglist).
12+
In addition, this part contains a larger exercise series that extends the BlogList application built in parts 4 and 5. Those exercises are found [here](/en/part7/exercises_extending_the_bloglist).
1313

1414
### React Hooks
1515

@@ -588,13 +588,22 @@ The internet is starting to fill up with more and more helpful material related
588588

589589
<div class="tasks">
590590

591-
### Exercises 7.1.-7.5.
591+
### Exercises 7.1.-7.6.
592592

593-
#### 7.1: useField hook
593+
Let's once again return to working with anecdotes. Use the app found in the repository https://github.com/fullstack-hy2020/routed-anecdotes as the starting point for the exercises.
594+
595+
If you clone the project into an existing git repository, remember to delete the git configuration of the cloned application:
596+
597+
cd routed-anecdotes // go first to directory of the cloned repository
598+
rm -rf .git
599+
The application starts the usual way, but first, you need to install its dependencies:
594600

595-
Implement a <i>useField</i> custom hook in the file <i>src/hooks/index.js</i>. The hook should manage the state of a single form input field and return an object with the following properties: <i>type</i>, <i>value</i>, and <i>onChange</i>.
601+
npm install
602+
npm run dev
596603

597-
One natural place to save the custom hooks of your application is in the <i>/src/hooks/index.js</i> file.
604+
#### 7.1: useField hook
605+
606+
Copy the <i>useField</i> custom hook in the file <i>src/hooks/index.js</i>. The hook should manage the state of a single form input field and return an object with the following properties: <i>type</i>, <i>value</i>, and <i>onChange</i>.
598607

599608
If you use the [named export](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export#Description) instead of the default export:
600609

@@ -633,13 +642,13 @@ const App = () => {
633642
}
634643
```
635644

636-
Create a simple form that uses the hook for at least two input fields and displays the current values below the form.
645+
Use the hook in the anecdote creation form.
637646

638647
#### 7.2: useField with reset
639648

640649
Add a button to the form that clears all input fields:
641650

642-
![browser anecdotes with reset button](../../images/7/61ea.png)
651+
![browser anecdotes with reset button](../../images/7/e2.png)
643652

644653
Expand the <i>useField</i> hook so that it exposes a <i>reset</i> function for clearing the field value.
645654

@@ -653,7 +662,7 @@ We will return to this warning in the next exercise.
653662

654663
If your solution did not cause a warning to appear in the console, you have already finished this exercise.
655664

656-
If you see the _Invalid value for prop \`reset\` on \<input\> tag_ warning in the console, make the necessary changes to get rid of it.
665+
If you see the <i>Invalid value for prop \`reset\` on \<input\> tag</i> warning in the console, make the necessary changes to get rid of it.
657666

658667
The reason for this warning is that after making the changes to your application, the following expression:
659668

@@ -686,108 +695,111 @@ One simple fix would be to not use the spread syntax and write all of the forms
686695

687696
If we were to do this, we would lose much of the benefit provided by the <i>useField</i> hook. Instead, come up with a solution that fixes the issue, but is still easy to use with the spread syntax.
688697

689-
#### 7.4: Country hook
698+
#### 7.4: useAnecdotes, step1
690699

691-
Use the code from <https://github.com/fullstack-hy2020/country-hook> as your starting point.
700+
The project has a JSON server already configured. YOu can start it with:
692701

693-
The application can be used to search for a country's details from the service in <https://studies.cs.helsinki.fi/restcountries/>. If a country is found, its details are displayed:
702+
```bash
703+
npm run server
704+
```
694705

695-
![browser displaying country details](../../images/7/69ea.png)
706+
This starts a JSON Server backend that exposes the anecdotes collection as a REST resource at <i>http://localhost:3001/anecdotes</i>.
696707

697-
If no country is found, a message is displayed to the user:
708+
The existing <i>services/anecdotes.js</i> file contains the functions needed to communicate with the backend (except for the last exercise). Note that the service uses the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) instead of Axios for HTTP requests. If you are unfamiliar with Fetch, have a look at [part 6](/en/part6/complex_state_fetch_testing#fetch-api) before continuing.
698709

699-
![browser showing country not found](../../images/7/70ea.png)
700710

701-
The application is otherwise complete, but you have to implement a custom hook <i>useCountry</i> that fetches the details of the country whose name is given to the hook as a parameter. Use a <i>useEffect</i> hook inside <i>useCountry</i> for the HTTP request.
711+
The typical pattern for fetching data from a server in React looks like this:
702712

703-
Note that in this exercise it is essential to use useEffect's [second parameter](https://react.dev/reference/react/useEffect#parameters) array to control when the effect function is executed.
713+
```js
714+
import { useState, useEffect } from 'react'
715+
import anecdoteService from './services/anecdotes'
704716

705-
#### 7.5: Ultimate Hooks
717+
const App = () => {
718+
const [anecdotes, setAnecdotes] = useState([])
706719

707-
The code of the application responsible for communicating with the backend of the note application of the previous parts looks like this:
720+
useEffect(() => {
721+
anecdoteService.getAll().then(data => setAnecdotes(data))
722+
}, [])
708723

709-
```js
710-
import axios from 'axios'
711-
const baseUrl = '/api/notes'
724+
// ...
725+
}
726+
```
712727

713-
let token = null
728+
Implement a custom hook <i>useAnecdotes</i> that encapsulates this server communication. For this exercise it is enough for the hook to fetch all anecdotes Adding new ones can be handled in the next exercise.
714729

715-
const setToken = newToken => {
716-
token = `bearer ${newToken}`
717-
}
730+
The hook should be used like this:
718731

719-
const getAll = async () => {
720-
const response = await axios.get(baseUrl)
721-
return response.data
722-
}
732+
```js
733+
// ...
734+
import { useAnecdotes } from './hooks' // highlight-line
723735

724-
const create = async newObject => {
725-
const config = {
726-
headers: { Authorization: token },
727-
}
736+
const App = () => {
737+
const { anecdotes } = useAnecdotes() // highlight-line
728738

729-
const response = await axios.post(baseUrl, newObject, config)
730-
return response.data
731-
}
739+
const addAnecdote = () => {} // a dummy function to keep code from breaking
732740

733-
const update = async (id, newObject) => {
734-
const response = await axios.put(`${ baseUrl }/${id}`, newObject)
735-
return response.data
741+
return (
742+
<Router>
743+
<div>
744+
<h1>Software anecdotes</h1>
745+
<Menu />
746+
<Routes>
747+
<Route path="/" element={<AnecdoteList anecdotes={anecdotes} />} />
748+
<Route path="/create" element={<CreateNew addAnecdote={addAnecdote} />} />
749+
<Route path="/about" element={<About />} />
750+
</Routes>
751+
<Footer />
752+
</div>
753+
</Router>
754+
)
736755
}
737756

738-
export default { getAll, create, update, setToken }
757+
export default App
739758
```
740759

741-
We notice that the code is in no way specific to the fact that our application deals with notes. Excluding the value of the _baseUrl_ variable, the same code could be reused in the blog post application for dealing with the communication with the backend.
760+
**A hint:** it was previously mentioned that
742761

743-
Extract the code for communicating with the backend into its own _useResource_ hook. It is sufficient to implement fetching all resources and creating a new resource.
762+
> A helpful way to think about it (that is, how a hook works): imagine copy-pasting all the code from inside your custom hook directly into the component.
744763
745-
You can do the exercise in the project found in the <https://github.com/fullstack-hy2020/ultimate-hooks> repository. The <i>App</i> component for the project is the following:
764+
So now you should kind of do the opposite: copy-paste the relevant code from component to the hook. This includes both hooks <i>useState</i> and <i>useEffect</i>.
765+
766+
#### 7.5: useAnecdotes, step2
767+
768+
Extend the <i>useAnecdotes</i> hook so that it also supports creating new anecdotes. The hook should expose an <i>addAnecdote</i> function that sends the new anecdote to the server and updates the local state.
769+
770+
The hook should now be usable like this:
746771

747772
```js
748-
const App = () => {
749-
const content = useField('text')
750-
const name = useField('text')
751-
const number = useField('text')
773+
const { anecdotes, addAnecdote } = useAnecdotes()
774+
```
752775

753-
const [notes, noteService] = useResource('http://localhost:3005/notes')
754-
const [persons, personService] = useResource('http://localhost:3005/persons')
776+
Update the <i>App</i> component to pass <i>addAnecdote</i> to the <i>CreateNew</i> component instead of the dummy function.
755777

756-
const handleNoteSubmit = (event) => {
757-
event.preventDefault()
758-
noteService.create({ content: content.value })
759-
}
760-
761-
const handlePersonSubmit = (event) => {
762-
event.preventDefault()
763-
personService.create({ name: name.value, number: number.value})
764-
}
778+
#### 7.6: useAnecdotes, step3
765779

766-
return (
767-
<div>
768-
<h2>notes</h2>
769-
<form onSubmit={handleNoteSubmit}>
770-
<input {...content} />
771-
<button>create</button>
772-
</form>
773-
{notes.map(n => <p key={n.id}>{n.content}</p>)}
780+
Extend the <i>useAnecdotes</i> hook with a <i>deleteAnecdote</i> function that removes an anecdote from the server and updates the local state. Add a delete button next to each anecdote in the list.
774781

775-
<h2>persons</h2>
776-
<form onSubmit={handlePersonSubmit}>
777-
name <input {...name} /> <br/>
778-
number <input {...number} />
779-
<button>create</button>
780-
</form>
781-
{persons.map(n => <p key={n.id}>{n.name} {n.number}</p>)}
782-
</div>
782+
Also refactor the application so that neither the anecdote data nor the hook functions are passed down as props. Instead, the components that need them should call <i>useAnecdotes</i> directly. This means <i>App</i> no longer needs to act as an intermediary passing data and callbacks through the component tree.
783+
784+
After the refactoring, <i>App</i> should look like this:
785+
786+
```js
787+
const App = () => {
788+
return (
789+
<Router>
790+
<div>
791+
<h1>Software anecdotes</h1>
792+
<Menu />
793+
<Routes>
794+
<Route path="/" element={<AnecdoteList />} />
795+
<Route path="/create" element={<CreateNew />} />
796+
<Route path="/about" element={<About />} />
797+
</Routes>
798+
<Footer />
799+
</div>
800+
</Router>
783801
)
784802
}
785803
```
786804

787-
The _useResource_ custom hook returns an array of two items just like the state hooks. The first item of the array contains all of the individual resources and the second item of the array is an object that can be used for manipulating the resource collection, like creating new ones.
788-
789-
If you implement the hook correctly, it can be used for both notes and persons (start the server with the _npm run server_ command at port 3005).
790-
791-
![browser showing notes and persons](../../images/5/21e.png)
792-
793805
</div>

0 commit comments

Comments
 (0)