Last active
July 18, 2018 15:05
-
-
Save desinas/8f369e840e37db8ebcc650743599218c to your computer and use it in GitHub Desktop.
part of Contacts app after bootstrapping w/ Create React app of Udacity React nano-degree
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from 'react'; | |
import { Route } from 'react-router-dom' | |
import ListContacts from './ListContacts' | |
import CreateContact from './CreateContact' | |
import * as ContactsAPI from './utils/ContactsAPI' | |
class App extends Component { | |
state = { | |
contacts: [] | |
} | |
componentDidMount() { | |
ContactsAPI.getAll().then((contacts) => { | |
this.setState({ contacts }) | |
}) | |
} | |
removeContact = (contact) => { | |
this.setState((state) => ({ | |
contacts: state.contacts.filter((c) => c.id !== contact.id) | |
})) | |
ContactsAPI.remove(contact) | |
} | |
createContact(contact) { | |
ContactsAPI.create(contact).then(contact => { | |
this.setState(state => ({ | |
contacts: state.contacts.concat([ contact ]) | |
})) | |
}) | |
} | |
render() { | |
return ( | |
<div> | |
<Route exact path='/' render={() => ( | |
<ListContacts | |
onDeleteContact={this.removeContact} | |
contacts={this.state.contacts} | |
/> | |
)}/> | |
<Route path='/create' render={({ history }) => ( | |
<CreateContact | |
onCreateContact={(contact) => { | |
this.createContact(contact) | |
history.push('/') | |
}} | |
/> | |
)}/> | |
</div> | |
) | |
} | |
} | |
export default App;; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from 'react'; | |
import { Link } from 'react-router-dom' | |
import ImageInput from './ImageInput' | |
import serializeForm from 'form-serialize' | |
class CreateContact extends Component { | |
handleSubmit = (e) => { | |
e.preventDefault() | |
const values = serializeForm(e.target, { hash: true }) | |
if (this.props.onCreateContact) | |
this.props.onCreateContact(values) | |
} | |
render() { | |
return ( | |
<div> | |
<Link className='close-create-contact' to='/'>Close</Link> | |
<form onSubmit={this.handleSubmit} className='create-contact-form'> | |
<ImageInput | |
className='create-contact-avatar-input' | |
name='avatarURL' | |
maxHeight={64} | |
/> | |
<div className='create-contact-details'> | |
<input type='text' name='name' placeholder='Name'/> | |
<input type='text' name='email' placeholder='Email'/> | |
<button>Add Contact</button> | |
</div> | |
</form> | |
</div> | |
) | |
} | |
} | |
export default CreateContact |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
import PropTypes from 'prop-types' | |
const readFileAsDataURL = (file) => | |
new Promise(resolve => { | |
const reader = new FileReader() | |
reader.onload = (event) => { | |
resolve(event.target.result) | |
} | |
reader.readAsDataURL(file) | |
}) | |
const resizeImage = (imageURL, canvas, maxHeight) => | |
new Promise(resolve => { | |
const image = new Image() | |
image.onload = () => { | |
const context = canvas.getContext('2d') | |
if (image.height > maxHeight) { | |
image.width *= maxHeight / image.height | |
image.height = maxHeight | |
} | |
context.clearRect(0, 0, canvas.width, canvas.height) | |
canvas.width = image.width | |
canvas.height = image.height | |
context.drawImage(image, 0, 0, image.width, image.height) | |
resolve(canvas.toDataURL('image/jpeg')) | |
} | |
image.src = imageURL | |
}) | |
/** | |
* A custom <input> that dynamically reads and resizes image files before | |
* submitting them to the server as data URLs. Also, shows a preview of the image. | |
*/ | |
class ImageInput extends React.Component { | |
static propTypes = { | |
className: PropTypes.string, | |
name: PropTypes.string, | |
maxHeight: PropTypes.number | |
} | |
state = { | |
value: '' | |
} | |
handleFileChange = (event) => { | |
const file = event.target.files[0] | |
if (file && file.type.match(/^image\//)) { | |
readFileAsDataURL(file).then(originalURL => { | |
resizeImage(originalURL, this.canvas, this.props.maxHeight).then(url => { | |
this.setState({ value: url }) | |
}) | |
}) | |
} else { | |
this.setState({ value: '' }) | |
} | |
} | |
handleFormReset = () => { | |
this.setState({ value: '' }) | |
} | |
componentDidMount() { | |
this.canvas = document.createElement('canvas') | |
this.fileInput.form.addEventListener('reset', this.handleFormReset) | |
} | |
componentWillUnmount() { | |
this.fileInput.form.removeEventListener('reset', this.handleFormReset) | |
} | |
render() { | |
const { className, name } = this.props | |
const { value } = this.state | |
const style = { | |
position: 'relative' | |
} | |
if (value) { | |
style.backgroundImage = `url("${value}")` | |
style.backgroundRepeat = 'no-repeat' | |
style.backgroundPosition = 'center' | |
style.backgroundSize = 'cover' | |
} | |
return ( | |
<div className={className} style={style}> | |
<input type="hidden" name={name} value={value} /> | |
<input | |
ref={node => this.fileInput = node} | |
type="file" | |
onChange={this.handleFileChange} | |
style={{ | |
position: 'absolute', | |
top: 0, | |
left: 0, | |
width: '100%', | |
height: '100%', | |
opacity: 0 | |
}} | |
/> | |
</div> | |
) | |
} | |
} | |
export default ImageInput |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import ReactDOM from 'react-dom'; | |
import { BrowserRouter } from 'react-router-dom' | |
import App from './App'; | |
import registerServiceWorker from './registerServiceWorker'; | |
import './index.css'; | |
ReactDOM.render( | |
<BrowserRouter><App /></BrowserRouter>, | |
document.getElementById('root') | |
); | |
registerServiceWorker(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from 'react'; | |
import { Link } from 'react-router-dom' | |
import PropTypes from 'prop-types' | |
import escapeRegExp from 'escape-string-regexp' | |
import sortBy from 'sort-by' | |
class ListContacts extends Component { | |
static propTypes = { | |
contacts: PropTypes.array.isRequired, | |
onDeleteContact: PropTypes.func.isRequired | |
} | |
state = { | |
query: '' | |
} | |
updateQuery = (query) => { | |
this.setState({ query: query.trim() }) | |
} | |
clearQuery = () => { | |
this.setState({ query: '' }) | |
} | |
render() { | |
const { contacts, onDeleteContact } = this.props | |
const { query } = this.state | |
let showingContacts | |
if (query) { | |
const match = new RegExp(escapeRegExp(query), 'i') | |
showingContacts = contacts.filter((contact) => match.test(contact.name)) | |
} else { | |
showingContacts = contacts | |
} | |
showingContacts.sort(sortBy('name')) | |
return ( | |
<div className='list-contacts'> | |
<div className='list-contacts-top'> | |
<input | |
className='search-contacts' | |
type='text' | |
placeholder='Search contacts' | |
value={query} | |
onChange={(event) => this.updateQuery(event.target.value)} | |
/> | |
<Link | |
to='/create' | |
className='add-contact' | |
>Add Contact</Link> | |
</div> | |
{showingContacts.length !== contacts.length && ( | |
<div className='showing-contacts'> | |
<span>Now showing {showingContacts.length} of {contacts.length} total</span> | |
<button onClick={this.clearQuery}>Show all</button> | |
</div> | |
)} | |
<ol className='contact-list'> | |
{showingContacts.map((contact) => ( | |
<li key={contact.id} className='contact-list-item'> | |
<div className='contact-avatar' style={{ | |
backgroundImage: `url(${contact.avatarURL})` | |
}}/> | |
<div className='contact-details'> | |
<p>{contact.name}</p> | |
<p>{contact.email}</p> | |
</div> | |
<button onClick={() => onDeleteContact(contact)} className='contact-remove'> | |
Remove | |
</button> | |
</li> | |
))} | |
</ol> | |
</div> | |
) | |
} | |
} | |
export default ListContacts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const api = process.env.REACT_APP_CONTACTS_API_URL || 'http://localhost:5001' | |
let token = localStorage.token | |
if (!token) | |
token = localStorage.token = Math.random().toString(36).substr(-8) | |
const headers = { | |
'Accept': 'application/json', | |
'Authorization': token | |
} | |
export const getAll = () => | |
fetch(`${api}/contacts`, { headers }) | |
.then(res => res.json()) | |
.then(data => data.contacts) | |
export const remove = (contact) => | |
fetch(`${api}/contacts/${contact.id}`, { method: 'DELETE', headers }) | |
.then(res => res.json()) | |
.then(data => data.contact) | |
export const create = (body) => | |
fetch(`${api}/contacts`, { | |
method: 'POST', | |
headers: { | |
...headers, | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify(body) | |
}).then(res => res.json()) |
Repo commit - revision #19
- Save new contact to the server: So there is a contact form. Serialized data and passed it up to the parent component. The last to do to have a fully functional app is to save the contact to the server. To do that some changing must be done at the component in file App.js. At the Route component with
path="/create"
changing the component to render, so that to make a composition of components to render together. In addition passed history in the render we can use it tohistory.push('/')
so that to make the back key of the browser to work properly. Also in render we have onCreateContact prop where we call createContact( contact ) method.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Repo commit - revision #18
e.prevent.default()
the browser no longer going to submit this form and then serialize this with JSserializeForm(e.target, {hash: true})
and thenthis.props.onCreateContact(values)
In order to serialize the form data we'll use the form-serialize package to output this information as a regular JavaScript object for the app to use
npm install --save form-serialize