Skip to content

Commit b18478c

Browse files
authored
Lesson 4 (#24)
* init lesson 4 - advanced react * Add source, readme, todos for lesson 4 * added README * added TODOs * added `src-solution` code * added `upload-data` logic * fix code review hints & typos * Added redux link + recap PropTypes
1 parent 020875c commit b18478c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1659
-0
lines changed

lesson-4/.editorconfig

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# EditorConfig helps developers define and maintain
2+
# consistent coding styles between different editors and IDEs.
3+
4+
root = true
5+
6+
[*]
7+
end_of_line = lf
8+
charset = utf-8
9+
trim_trailing_whitespace = true
10+
insert_final_newline = true
11+
indent_style = space
12+
indent_size = 2
13+
14+
[*.md]
15+
trim_trailing_whitespace = false

lesson-4/README.md

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Advanced React - Async
2+
3+
## Goal - understand (unidirectional) data flow
4+
5+
## TODO
6+
7+
* [recap] Component's life cycle hooks recap
8+
* [recap] State & Props
9+
* [recap] Children + [key](https://stackoverflow.com/questions/28329382/understanding-unique-keys-for-array-children-in-react-js#28329550) (re-render)
10+
* [recap] PropTypes - Runtime TypeSafety
11+
* [recap] Promises + Async
12+
* [concept] HOC - Higher order components
13+
* [concept] FAC - Function as children / [Render callback](http://reactpatterns.com/#render-callback)
14+
* [new] Effects
15+
* [new] Local (Components) state vs. Centralized (Atom) state
16+
* [new] Error handling / Loading (the state machine)
17+
18+
## Problem - "Idea Journal"
19+
20+
* make it work with server - CRUD
21+
* display loading state indicator
22+
* validate server payload via propTypes
23+
24+
## Exercises
25+
26+
1. display total number of notes in header // passing state down
27+
1. prevent saving an empty note (button must become disabled)
28+
1. add propTypes for `Header.js` and `Note.js` components
29+
1. replace the `default` with your github username in `./src/config/api.js`
30+
1. run `node upload-data.js`
31+
32+
1. implement "load notes from server" (will be step-by-step walk through) 😈
33+
1. fetch notes in `componentDidMount` of App.js, transform and save notes to state
34+
1. in App.js display `<Spinner />` instead of `<NoteList />` during server call
35+
1. in App.js display 0 notes if there are no notes on server
36+
1. in App.js display an `errorMessage`
37+
1. implement <EditNoteModal />
38+
1. implement note deletion
39+
1. add propTypes everywhere
40+
41+
## Learning resources
42+
43+
* [React Forms](https://reactjs.org/docs/forms.html)
44+
* [You Might Need Redux](http://redux.js.org/)
45+
* [You Might Not Need Redux](https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367)
46+
* [Michael Jackson - Never Write Another HoC](https://www.youtube.com/watch?v=BcVAq3YFiuc)
47+
* [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)

lesson-4/package.json

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "idea-journal",
3+
"version": "0.1.0",
4+
"private": true,
5+
"dependencies": {
6+
"prop-types": "15.6.0",
7+
"react": "16.0.0",
8+
"react-dom": "16.0.0",
9+
"react-modal": "3.0.3",
10+
"request": "2.83.0",
11+
"shortid": "2.2.8"
12+
},
13+
"devDependencies": {
14+
"prettier": "1.7.4",
15+
"react-scripts": "1.0.14"
16+
},
17+
"scripts": {
18+
"start": "react-scripts start",
19+
"build": "react-scripts build",
20+
"test": "react-scripts test --env=jsdom",
21+
"eject": "react-scripts eject"
22+
},
23+
"prettier": {
24+
"printWidth": 100,
25+
"tabWidth": 2,
26+
"useTabs": false,
27+
"semi": false,
28+
"bracketSpacing": true,
29+
"jsxBracketSameLine": true,
30+
"singleQuote": true,
31+
"trailingComma": "es5"
32+
}
33+
}

lesson-4/public/favicon.ico

3.78 KB
Binary file not shown.

lesson-4/public/index.html

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
6+
<meta name="theme-color" content="#000000">
7+
<!--
8+
manifest.json provides metadata used when your web app is added to the
9+
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
10+
-->
11+
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
12+
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
13+
<!--
14+
Notice the use of %PUBLIC_URL% in the tags above.
15+
It will be replaced with the URL of the `public` folder during the build.
16+
Only files inside the `public` folder can be referenced from the HTML.
17+
18+
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
19+
work correctly both with client-side routing and a non-root public URL.
20+
Learn how to configure a non-root public URL by running `npm run build`.
21+
-->
22+
<title>React App</title>
23+
</head>
24+
<body>
25+
<noscript>
26+
You need to enable JavaScript to run this app.
27+
</noscript>
28+
<div id="root"></div>
29+
<!--
30+
This HTML file is a template.
31+
If you open it directly in the browser, you will see an empty page.
32+
33+
You can add webfonts, meta tags, or analytics to this file.
34+
The build step will place the bundled scripts into the <body> tag.
35+
36+
To begin the development, run `npm start` or `yarn start`.
37+
To create a production bundle, use `npm run build` or `yarn build`.
38+
-->
39+
</body>
40+
</html>

lesson-4/public/manifest.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"short_name": "React App",
3+
"name": "Create React App Sample",
4+
"icons": [
5+
{
6+
"src": "favicon.ico",
7+
"sizes": "192x192",
8+
"type": "image/png"
9+
}
10+
],
11+
"start_url": "./index.html",
12+
"display": "standalone",
13+
"theme_color": "#000000",
14+
"background_color": "#ffffff"
15+
}
+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import React from 'react'
2+
import Header from './Header'
3+
import NoteList from './NoteList'
4+
import Footer from './Footer'
5+
import Spinner from './Spinner'
6+
7+
import { FIREBASE_URL } from '../config/api'
8+
import '../styles/App.css'
9+
10+
class App extends React.Component {
11+
state = {
12+
notes: [],
13+
errorMessage: '',
14+
}
15+
16+
async fetchNotes() {
17+
try {
18+
const result = await fetch(`${FIREBASE_URL}/notes.json`)
19+
const notes = await result.json()
20+
21+
this.setState({
22+
notes: Object.keys(notes).map(key => ({
23+
uuid: key,
24+
...notes[key],
25+
})),
26+
})
27+
} catch (err) {
28+
this.setState({
29+
errorMessage: err.message ? err.message : err,
30+
})
31+
}
32+
}
33+
34+
componentDidMount() {
35+
this.fetchNotes()
36+
}
37+
38+
addNoteToList = note => {
39+
this.setState({
40+
notes: [...this.state.notes, note],
41+
})
42+
}
43+
44+
removeNoteFromList = noteId => () => {
45+
const { notes } = this.state
46+
const newNotes = notes.filter(note => {
47+
return note.uuid !== noteId
48+
})
49+
this.setState({
50+
notes: newNotes,
51+
})
52+
}
53+
54+
editNote = async note => {
55+
try {
56+
const result = await fetch(`${FIREBASE_URL}/notes/${note.noteId}.json`, {
57+
method: 'put',
58+
headers: {
59+
Accept: 'application/json',
60+
'Content-Type': 'application/json',
61+
},
62+
body: JSON.stringify({
63+
title: note.title,
64+
text: note.text,
65+
}),
66+
})
67+
68+
if (result.ok) {
69+
this.fetchNotes()
70+
}
71+
} catch (err) {
72+
console.error('editNote: ',err)
73+
this.setState({
74+
errorMessage: `Error during note update`,
75+
})
76+
}
77+
}
78+
79+
render() {
80+
const { errorMessage, notes } = this.state
81+
return (
82+
<div className="App">
83+
<div>
84+
<Header noteCount={notes.length} onAddNote={this.addNoteToList} />
85+
{errorMessage}
86+
{notes.length === 0 ? (
87+
<Spinner />
88+
) : (
89+
<NoteList
90+
notes={notes}
91+
editNote={this.editNote}
92+
removeNoteFromList={this.removeNoteFromList}
93+
/>
94+
)}
95+
</div>
96+
<Footer />
97+
</div>
98+
)
99+
}
100+
}
101+
102+
export default App
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import React from 'react'
2+
import Modal from 'react-modal'
3+
import '../styles/EditNoteModal.css'
4+
5+
class EditNoteModal extends React.Component {
6+
constructor(props) {
7+
super(props)
8+
this.state = {
9+
modalIsOpen: false,
10+
note: {
11+
noteId: props.noteId,
12+
text: props.text,
13+
title: props.title,
14+
},
15+
}
16+
}
17+
18+
toggleModal = () => {
19+
this.setState({
20+
modalIsOpen: !this.state.modalIsOpen,
21+
})
22+
}
23+
24+
handleChange = field => e => {
25+
let note = this.state.note
26+
note[field] = e.target.value
27+
this.setState({ note })
28+
}
29+
30+
handleFormSubmit = e => {
31+
e.preventDefault()
32+
this.props.editNote(this.state.note)
33+
this.toggleModal()
34+
}
35+
36+
render() {
37+
const { modalIsOpen } = this.state
38+
return (
39+
<div className="EditNoteModal">
40+
<a onClick={this.toggleModal}>edit</a>
41+
<Modal
42+
isOpen={modalIsOpen}
43+
onRequestClose={this.toggleModal}
44+
className="EditNoteModal-modal-window"
45+
overlayClassName="EditNoteModal-modal-overlay"
46+
>
47+
<h2>Edit a note</h2>
48+
<form onSubmit={this.handleFormSubmit}>
49+
<div>
50+
<label htmlFor="title">Title</label>
51+
<input
52+
type="text"
53+
id="title"
54+
value={this.state.note.title}
55+
onChange={this.handleChange('title')}
56+
/>
57+
</div>
58+
<div className="EditNoteModal-textarea">
59+
<label htmlFor="text">Text</label>
60+
<textarea
61+
rows="4"
62+
id="text"
63+
value={this.state.note.text}
64+
onChange={this.handleChange('text')}
65+
/>
66+
</div>
67+
<button type="submit">Submit</button>
68+
</form>
69+
</Modal>
70+
</div>
71+
)
72+
}
73+
}
74+
75+
export default EditNoteModal
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import React from 'react'
2+
import '../styles/Footer.css'
3+
4+
class Footer extends React.Component {
5+
render() {
6+
return (
7+
<div className="Footer">
8+
<b>MSD Code Academy 2017</b>
9+
</div>
10+
)
11+
}
12+
}
13+
14+
export default Footer
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import React from 'react'
2+
import PropTypes from 'prop-types'
3+
4+
import NewNoteModal from './NewNoteModal'
5+
import logo from '../logo.png'
6+
import '../styles/Header.css'
7+
8+
class Header extends React.Component {
9+
render() {
10+
const { ...props } = this.props
11+
return (
12+
<div className="Header">
13+
<div className="Header-logo">
14+
<img src={logo} alt="logo" />
15+
<b>IDEA JOURNAL</b>
16+
<span>(containing {this.props.noteCount} ideas)</span>
17+
</div>
18+
<NewNoteModal {...props} />
19+
</div>
20+
)
21+
}
22+
}
23+
24+
Header.propTypes = {
25+
noteCount: PropTypes.number.isRequired
26+
}
27+
28+
export default Header

0 commit comments

Comments
 (0)