├── src
├── app.JPG
├── index.css
├── App.test.js
├── index.js
├── Note
│ ├── Note.css
│ └── Note.jsx
├── NoteForm
│ ├── NoteForm.css
│ └── NoteForm.jsx
├── App.css
├── App.js
├── logo.svg
└── registerServiceWorker.js
├── public
├── favicon.ico
├── manifest.json
└── index.html
├── package.json
├── .gitignore
├── README.md
└── LICENSE
/src/app.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesdoyle/react-firebase-notes-app/HEAD/src/app.JPG
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Lato|Permanent+Marker');
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesdoyle/react-firebase-notes-app/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render( , div);
8 | });
9 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import registerServiceWorker from './registerServiceWorker';
6 |
7 | ReactDOM.render( , document.getElementById('root'));
8 | registerServiceWorker();
9 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
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 | }
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reactnotes",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "firebase": "^4.1.3",
7 | "react": "^15.6.1",
8 | "react-dom": "^15.6.1",
9 | "react-scripts": "1.0.10"
10 | },
11 | "scripts": {
12 | "start": "react-scripts start",
13 | "build": "react-scripts build",
14 | "test": "react-scripts test --env=jsdom",
15 | "eject": "react-scripts eject"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
23 | /src/Static/img*.jpeg
24 | *.png
25 |
26 | /src/Config/config.js
27 |
--------------------------------------------------------------------------------
/src/Note/Note.css:
--------------------------------------------------------------------------------
1 | .note {
2 | padding:10px 10px 38px 34px;
3 | height: auto;
4 | margin: auto;
5 | margin-bottom: 8px;
6 | width: 60%;
7 | font-family: 'Lato', sans-serif;
8 | font-size: 1.4em;
9 | color: #222;
10 | background-color: #eed;
11 | border-radius: 3px;
12 | box-shadow: 43x 3px 2px #333;
13 | opacity: 1;
14 | }
15 |
16 | @keyframes fadeIn { from { opacity:0; } to { opacity:0.8; } }
17 | @keyframes fadeOut { from { opactiy:0.8; } to { opacity:0; } }
18 |
19 | .fade-in {
20 | opacity:0;
21 | animation:fadeIn ease-in 1;
22 | animation-fill-mode:forwards;
23 | animation-duration: 0.1s;
24 | }
25 |
26 | .noteContent{
27 | float:left;
28 | padding-right: 70px;
29 | padding-bottom: 10px;
30 | display: block;
31 | }
32 |
33 | .closebtn {
34 | color: #AA9;
35 | font-weight: bold;
36 | float: right;
37 | cursor: pointer;
38 | font-size: 1.3em;
39 | }
40 |
41 | .closebtn:hover {
42 | color: #AC2500;
43 | }
--------------------------------------------------------------------------------
/src/Note/Note.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './Note.css';
3 | import PropTypes from 'prop-types';
4 |
5 | class Note extends Component{
6 |
7 | constructor(props){
8 | super(props);
9 | this.noteContent = props.noteContent;
10 | this.noteId = props.noteId;
11 | this.handleRemoveNote = this.handleRemoveNote.bind(this);
12 | }
13 |
14 | handleRemoveNote(id){
15 | this.props.removeNote(id);
16 | }
17 |
18 | render(){
19 | return(
20 |
21 |
this.handleRemoveNote(this.noteId)}>
23 | ×
24 |
25 |
{ this.noteContent }
26 |
27 | )
28 | }
29 | }
30 |
31 | Note.propTypes = {
32 | noteContent: PropTypes.string
33 | }
34 |
35 | export default Note;
36 |
--------------------------------------------------------------------------------
/src/NoteForm/NoteForm.css:
--------------------------------------------------------------------------------
1 | .formWrapper{
2 | margin: auto;
3 | }
4 |
5 | .noteButton{
6 | border: none;
7 | margin: 0px;
8 | border-top-right-radius: 4px;
9 | border-bottom-right-radius: 4px;
10 | padding: 8px 24px;
11 | font-size: 1.3em;
12 | font-family: 'Droid Sans', sans-serif;
13 | background-color: #019875;
14 | color: #fff;
15 | transition: 0.2s ease-out;
16 | }
17 |
18 | .noteButton:hover{
19 | background-color: #02BA85;
20 | color: #fff;
21 | transition: 0.2s ease-out;
22 | }
23 |
24 | .noteInput{
25 | width: 800px;
26 | min-width: 800px;
27 | font-size: 1.3em;
28 | padding-left: 20px;
29 | border-top-left-radius: 4px;
30 | border-bottom-left-radius: 4px;
31 | font-family: 'Droid Sans', sans-serif;
32 | background-color: rgba(34,49,63,0.8);
33 | color: #fff;
34 | transition: 0.2s ease-out;
35 | }
36 |
37 | .noteInput:focus{
38 | background-color: #E4F1FE;
39 | color: #111;
40 | transition: 0.2s ease-in;
41 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-firebase-notes-app
2 | ## Summary
3 | A simple CRUD application to explore React and Firebase interaction. Implements a simple flexbox layout, and provides the ability for a user to add and remove notes from a Firebase database. I created a video tutorial for building the initial version of this app here: [Link to YouTube video](https://youtu.be/-RtJroTMDf4).
4 |
5 | ## Setup
6 | You'll need to get your connection strings from Firebase. Place a file called `config.js` in `src/Config/` that contains your Firebase config as a simple javascript object, exported as `DB_CONFIG`. For example,
7 |
8 | ```
9 | export const DB_CONFIG ={
10 | apiKey: your_api_key,
11 | authDomain: your_auth_domain,
12 | // etc..
13 | }
14 | ```
15 |
16 | ## Starting the app
17 | `npm start`
18 |
19 | ## Sample Screenshot
20 |
21 | 
22 |
23 |
24 |
25 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app).
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Wes Doyle
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | html{
2 | height: 100%;
3 | }
4 |
5 | body {
6 | width: 100%;
7 | min-height:100%;
8 | background:
9 | linear-gradient(-45deg, rgba(0,0,0,0.3), rgba(0, 40, 105, 0.8)),
10 | url('./Static/img/2.jpeg');
11 | background-attachment: fixed;
12 | background-size: cover;
13 | }
14 |
15 | * {
16 | height: 100%;
17 | box-sizing: border-box;
18 | margin: 0;
19 | padding: 0;
20 | border: 0;
21 | }
22 |
23 | .notesWrapper {
24 | display: flex;
25 | flex: 1 1 auto;
26 | flex-direction: column;
27 | height: 100%;
28 | }
29 |
30 | .notesHeader {
31 | display: flex;
32 | flex: 1 1 5%;
33 | min-height: 2.5em;
34 | max-height: 2em;
35 | font-size: 3.2em;
36 | color: #F4B350;
37 | font-family: 'Permanent Marker', cursive;
38 | text-align: center;
39 | align-content: center;
40 | padding:20px 0px 20px 0px;
41 | margin-bottom: 6px;
42 | background-color: rgba(0,0,0,0.5);
43 | text-shadow: 3px 3px 10px #000;
44 | display: block;
45 | }
46 |
47 | .notesBody {
48 | padding-top: 24px;
49 | display: flex;
50 | flex: 1 1 85%;
51 | flex-wrap: wrap;
52 | align-items: flex-start;
53 | align-content: flex-start;
54 | overflow: auto;
55 | }
56 |
57 | .notesFooter {
58 | display: flex;
59 | flex: 1 1 5%;
60 | background-color: rgba(0,0,0,0.5);
61 | }
--------------------------------------------------------------------------------
/src/NoteForm/NoteForm.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './NoteForm.css';
3 |
4 | class NoteForm extends Component{
5 | constructor(props){
6 | super(props);
7 | this.state = {
8 | newNoteContent: '',
9 | };
10 |
11 | this.handleUserInput = this.handleUserInput.bind(this);
12 | this.writeNote = this.writeNote.bind(this);
13 | }
14 |
15 | // When the user input changes, set the newNoteContent
16 | // to the value of what's in the input box.
17 | handleUserInput(e){
18 | this.setState({
19 | newNoteContent: e.target.value, // the value of the text input
20 | })
21 | }
22 |
23 | writeNote(){
24 | // call a method that sets the noteContent for a note to
25 | // the value of the input
26 | this.props.addNote(this.state.newNoteContent);
27 |
28 | // Set newNoteContent back to an empty string.
29 | this.setState({
30 | newNoteContent: '',
31 | })
32 | }
33 |
34 | render(){
35 | return(
36 |
37 |
41 | Add Note
43 |
44 | )
45 | }
46 | }
47 |
48 | export default NoteForm;
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
26 | You need to enable JavaScript to run this app.
27 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Note from './Note/Note';
3 | import NoteForm from './NoteForm/NoteForm';
4 | import { DB_CONFIG } from './Config/config';
5 | import firebase from 'firebase/app';
6 | import 'firebase/database';
7 | import './App.css';
8 |
9 | class App extends Component {
10 |
11 | constructor(props){
12 | super(props);
13 | this.addNote = this.addNote.bind(this);
14 | this.removeNote = this.removeNote.bind(this);
15 |
16 | this.app = firebase.initializeApp(DB_CONFIG);
17 | this.database = this.app.database().ref().child('notes');
18 |
19 | // We're going to setup the React state of our component
20 | this.state = {
21 | notes: [],
22 | }
23 | }
24 |
25 | componentWillMount(){
26 | const previousNotes = this.state.notes;
27 |
28 | // DataSnapshot
29 | this.database.on('child_added', snap => {
30 | previousNotes.push({
31 | id: snap.key,
32 | noteContent: snap.val().noteContent,
33 | })
34 |
35 | this.setState({
36 | notes: previousNotes
37 | })
38 | })
39 |
40 | this.database.on('child_removed', snap => {
41 | for(var i=0; i < previousNotes.length; i++){
42 | if(previousNotes[i].id === snap.key){
43 | previousNotes.splice(i, 1);
44 | }
45 | }
46 |
47 | this.setState({
48 | notes: previousNotes
49 | })
50 | })
51 | }
52 |
53 | addNote(note){
54 | this.database.push().set({ noteContent: note});
55 | }
56 |
57 | removeNote(noteId){
58 | console.log("from the parent: " + noteId);
59 | this.database.child(noteId).remove();
60 | }
61 |
62 | render() {
63 | return (
64 |
65 |
66 |
React & Firebase To-Do List
67 |
68 |
69 | {
70 | this.state.notes.map((note) => {
71 | return (
72 |
76 | )
77 | })
78 | }
79 |
80 |
81 |
82 |
83 |
84 | );
85 | }
86 | }
87 |
88 | export default App;
89 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (!isLocalhost) {
36 | // Is not local host. Just register service worker
37 | registerValidSW(swUrl);
38 | } else {
39 | // This is running on localhost. Lets check if a service worker still exists or not.
40 | checkValidServiceWorker(swUrl);
41 | }
42 | });
43 | }
44 | }
45 |
46 | function registerValidSW(swUrl) {
47 | navigator.serviceWorker
48 | .register(swUrl)
49 | .then(registration => {
50 | registration.onupdatefound = () => {
51 | const installingWorker = registration.installing;
52 | installingWorker.onstatechange = () => {
53 | if (installingWorker.state === 'installed') {
54 | if (navigator.serviceWorker.controller) {
55 | // At this point, the old content will have been purged and
56 | // the fresh content will have been added to the cache.
57 | // It's the perfect time to display a "New content is
58 | // available; please refresh." message in your web app.
59 | console.log('New content is available; please refresh.');
60 | } else {
61 | // At this point, everything has been precached.
62 | // It's the perfect time to display a
63 | // "Content is cached for offline use." message.
64 | console.log('Content is cached for offline use.');
65 | }
66 | }
67 | };
68 | };
69 | })
70 | .catch(error => {
71 | console.error('Error during service worker registration:', error);
72 | });
73 | }
74 |
75 | function checkValidServiceWorker(swUrl) {
76 | // Check if the service worker can be found. If it can't reload the page.
77 | fetch(swUrl)
78 | .then(response => {
79 | // Ensure service worker exists, and that we really are getting a JS file.
80 | if (
81 | response.status === 404 ||
82 | response.headers.get('content-type').indexOf('javascript') === -1
83 | ) {
84 | // No service worker found. Probably a different app. Reload the page.
85 | navigator.serviceWorker.ready.then(registration => {
86 | registration.unregister().then(() => {
87 | window.location.reload();
88 | });
89 | });
90 | } else {
91 | // Service worker found. Proceed as normal.
92 | registerValidSW(swUrl);
93 | }
94 | })
95 | .catch(() => {
96 | console.log(
97 | 'No internet connection found. App is running in offline mode.'
98 | );
99 | });
100 | }
101 |
102 | export function unregister() {
103 | if ('serviceWorker' in navigator) {
104 | navigator.serviceWorker.ready.then(registration => {
105 | registration.unregister();
106 | });
107 | }
108 | }
109 |
--------------------------------------------------------------------------------