https://github.com/niweera/fsuptutorial
Upload images to the Google Cloud Storage with ReactJS, Firebase SDK and Firestore
https://github.com/niweera/fsuptutorial
firebase firestore reactjs redux redux-thunk
Last synced: about 1 year ago
JSON representation
Upload images to the Google Cloud Storage with ReactJS, Firebase SDK and Firestore
- Host: GitHub
- URL: https://github.com/niweera/fsuptutorial
- Owner: Niweera
- License: mit
- Created: 2019-09-18T04:52:52.000Z (almost 7 years ago)
- Default Branch: master
- Last Pushed: 2019-10-02T04:23:08.000Z (over 6 years ago)
- Last Synced: 2025-03-29T08:43:55.207Z (about 1 year ago)
- Topics: firebase, firestore, reactjs, redux, redux-thunk
- Language: JavaScript
- Homepage: https://fsuptutorial.firebaseapp.com/
- Size: 1.55 MB
- Stars: 18
- Watchers: 1
- Forks: 4
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Upload images => Firestore + Google Cloud Storage + ReactJS
> Upload images to the Google Cloud Storage with ReactJS, Firebase SDK and Firestore
To view the application we are going to build beforehand, if you are familiar with `Heroku` just click the `Heroku Button`.
[](https://heroku.com/deploy)
### Prerequisites
1. Create a Firebase project [Firebase Console](https://console.firebase.google.com) => Click + Add Project => Follow along...
2. Create a Storage Bucket and allow appropriate rules. [docs](https://firebase.google.com/docs/storage/web/start)
3. Create a ReactJS application and register the app in Firebase Console. [docs](https://firebase.google.com/docs/web/setup)
Lets move on to the real deal...
## Create Firebase Config object.
Create a folder inside /src/ as config => `/src/config`
Add the following lines to config.js inside config folder.
```javascript
import * as firebase from "firebase";
const config = {
apiKey: "",
authDomain: "",
databaseURL: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: ""
};
if (!firebase.apps.length) {
firebase.initializeApp(config);
}
export const auth = firebase.auth();
export const f = firebase;
// Get a reference to the storage service, which is used to create references in your storage bucket
export const storage = firebase.storage();
// Create a storage reference from our storage service
export const storageRef = storage.ref();
export const database = firebase.firestore();
```
To get the `config` object => Go to Settings of Firebase project and look for `Your apps` section.
Now that you have created the Firebase config object let's move on to the ReactJS development.
For demonstration purposes, I'll use a single page with a header and a fixed footer and inside the page an image placeholder and file upload button.
For my ease, I'll be using [Bootstrap](https://getbootstrap.com/)
Now let's move onto creating the basic components for our App.
Create HOC folder on /src/
Now create `/src/HOC/Header.js` and `/src/HOC/Footer.js`
Now set up the Redux `store`
`/src/store.js`
```javascript
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducers";
import { f } from "./config/config";
import { reactReduxFirebase, getFirebase } from "react-redux-firebase";
import { reduxFirestore, getFirestore } from "redux-firestore";
// react-redux-firebase config
const rrfConfig = {
userProfile: "users",
useFirestoreForProfile: true, // Firestore for Profile instead of Realtime DB
attachAuthIsReady: true // attaches auth is ready promise to store
};
//this setting is used to avoid the error in non redux extension installed browsers
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
rootReducer,
composeEnhancers(
reactReduxFirebase(f, rrfConfig),
reduxFirestore(f),
applyMiddleware(thunk.withExtraArgument({ getFirebase, getFirestore }))
)
);
//this setting is used to avoid the error in non redux extension installed browsers
export default store;
```
Now create `rootReducer` file
`/src/reducers/index.js`
```javascript
import { combineReducers } from "redux";
import itemReducer from "./itemReducer";
import { firebaseReducer } from "react-redux-firebase";
import { firestoreReducer } from "redux-firestore";
export default combineReducers({
item: itemReducer,
firebase: firebaseReducer,
firestore: firestoreReducer
});
Now create the `reducer` file
For my application I have used the `itemReducer.js`
`/src/reducers/itemReducer.js`
import {
UPLOADING_START,
UPLOADING_SUCCESS,
UPLOADING_FAIL,
UPLOADING,
GET_DATA
} from "../actions/types";
const initialState = {
error: null,
percent: null,
showProgress: false,
image: null
};
export default function(state = initialState, action) {
switch (action.type) {
case UPLOADING_START:
return {
...state,
percent: 0,
showProgress: true
};
case UPLOADING_SUCCESS:
return {
...state,
error: false,
percent: null,
showProgress: false
};
case UPLOADING_FAIL:
return {
...state,
error: action.payload,
showProgress: false
};
case UPLOADING:
return {
...state,
percent: action.payload,
showProgress: true
};
case GET_DATA:
return {
...state,
image: action.payload
};
default:
return state;
}
}
```
Now we are done with the reducer stuff;
Now create the actions for reducer actions.
First we have to declare the action types.
`/src/actions/types.js`
```javascript
export const UPLOADING_START = "UPLOADING_START";
export const UPLOADING_SUCCESS = "UPLOADING_SUCCESS";
export const UPLOADING_FAIL = "UPLOADING_FAIL";
export const UPLOADING = "UPLOADING";
export const GET_DATA = "GET_DATA";
```
Now the `itemActions.js`
`/src/actions/itemActions.js`
```javascript
//import action types
import {
UPLOADING_START,
UPLOADING_SUCCESS,
UPLOADING_FAIL,
UPLOADING,
GET_DATA
} from "./types";
//import the Google Cloud Storage reference object
import { storageRef } from "../config/config";
// Update the image
export const uploadImage = data => async (
dispatch,
getState,
{ getFirestore }
) => {
const firestore = getFirestore();
try {
// Create the file metadata
const metadata = {
contentType: "image/jpeg"
};
// Upload file and metadata to the object 'images/mountains.jpg'
const uploadTask = storageRef
.child("images/" + data.name)
.put(data, metadata);
dispatch({ type: UPLOADING_START });
uploadTask.on(
"state_changed",
function(snapshot) {
// Observe state change events such as progress, pause, and resume
// Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
let progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
dispatch({ type: UPLOADING, payload: Math.floor(progress) });
},
function(error) {
// Handle unsuccessful uploads
dispatch({ type: UPLOADING_FAIL, payload: error });
},
function() {
// Handle successful uploads on complete
// For instance, get the download URL: https://firebasestorage.googleapis.com/...
uploadTask.snapshot.ref.getDownloadURL().then(function(downloadURL) {
dispatch({ type: UPLOADING_SUCCESS });
firestore
.collection("data")
.doc("user")
.update({
image_url: downloadURL
})
.then(() => {
//get the latest data
//once the data is sent to the firestore the latest version is stored in the redux store
get_Data(dispatch, getState, { getFirestore });
})
.catch(e => {
console.log(e);
});
});
}
);
} catch (err) {
console.log(err);
}
};
export const getData = () => async (dispatch, getState, { getFirestore }) => {
const firestore = getFirestore();
try {
const res = await firestore
.collection("data")
.doc("user")
.get();
const data = res.data().image_url;
if (data) {
dispatch({ type: GET_DATA, payload: data });
} else {
dispatch({ type: GET_DATA, payload: null });
}
} catch (e) {
console.log(e);
}
};
const get_Data = async (dispatch, getState, { getFirestore }) => {
const firestore = getFirestore();
try {
const res = await firestore
.collection("data")
.doc("user")
.get();
const data = res.data().image_url;
if (data) {
dispatch({ type: GET_DATA, payload: data });
} else {
dispatch({ type: GET_DATA, payload: null });
}
} catch (e) {
console.log(e);
}
};
```
Now we are done with the reducer actions stuff.
At this point you can see the Redux Store if you have installed the Redux Dev Tools.

Now let's set up `App.js` and without this you won't be able to render anything.
`/src/App.js`
```javascript
import React from "react";
import "./App.css";
import MainComponent from "./HOC/MainComponent";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import { Provider } from "react-redux";
import Header from "./HOC/Header";
import Footer from "./HOC/Footer";
import store from "./store";
function App() {
return (
);
}
export default App;
```
Now let's move on to the most important part.
Now create `/src/HOC/MainComponent.js` this MainComponent.js will contain the image upload button and other related components.
```javascript
import React, { Component } from "react";
import { uploadImage, getData } from "../actions/itemActions";
import Spinner from "../helpers/Spinner";
import { connect } from "react-redux";
import { withRouter } from "react-router-dom";
class MainComponent extends Component {
constructor() {
super();
this.state = {
error: null,
percent: 0,
showProgress: null,
image: null
};
}
componentDidMount() {
this.props.getData();
}
//create ref
fileInputRef = React.createRef();
onFormSubmit = e => {
e.preventDefault(); // Stop form submit
//validating the file
//check if the file is exists
if (this.state.file === null) {
alert("No image is selected!");
return;
}
//check if the image size is larger than 1MB
if (this.state.file.size > 1048576) {
alert("Image size must be less than 1MB!");
return;
}
//check if the dimension of the image is 2048 x 2048 px
if (this.state.file.width > 2048 || this.state.file.height > 2048) {
alert("Image dimensions must be 2048 x 2048 px");
return;
}
//check if the file is an image
if (
this.state.file.type === "image/jpeg" ||
this.state.file.type === "image/png" ||
this.state.file.type === "image/jpg"
) {
this.props.uploadImage(this.state.file);
} else {
alert("Please provide a valid image. (JPG, JPEG or PNG)");
}
};
//handle file change
fileChange = event => {
event.preventDefault();
this.setState({ file: event.target.files[0] });
let imageFile = event.target.files[0];
if (imageFile) {
const localImageUrl = URL.createObjectURL(imageFile);
const imageObject = new window.Image();
imageObject.onload = () => {
imageFile.width = imageObject.naturalWidth;
imageFile.height = imageObject.naturalHeight;
URL.revokeObjectURL(imageFile);
};
imageObject.src = localImageUrl;
}
};
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.showProgress !== prevState.showProgress) {
return { showProgress: nextProps.showProgress };
}
if (nextProps.image !== prevState.image) {
return { image: nextProps.image };
}
if (nextProps.percent !== prevState.percent) {
return { percent: nextProps.percent };
}
if (nextProps.error !== prevState.error) {
return { error: nextProps.error };
} else {
return null;
}
}
render() {
const { image, percent, showProgress } = this.state;
if (image) {
return (
{image === "empty" ? (
) : (
)}
Upload image
Select the image and click upload.
this.fileInputRef.current.click()
}
>
Select Image
Upload
this.fileChange(event)}
hidden
/>
{showProgress ? (
) : null}
);
} else {
return ;
}
}
}
const mapStateToProps = ({ item }) => ({
error: item.error,
percent: item.percent,
showProgress: item.showProgress,
image: item.image
});
const mapDispatchToProps = {
uploadImage,
getData
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(withRouter(MainComponent));
```
Now we are done. Let's try out uploading...

To view this application to try it out,
Go to [FSUPTUTORIAL](https://fsuptutorial.firebaseapp.com) hosted on `Firebase`
To view the application repo in [GitHub](https://github.com/Niweera/fsuptutorial)
### Acknowledgement
This tutorial is just a dream, if it weren't for these resources;
https://medium.com/strands-tech-corner/how-to-validate-an-image-in-redux-form-1cef01e4ff6c
https://codesandbox.io/s/04lz4580pl