{"id":23514751,"url":"https://github.com/niweera/fsuptutorial","last_synced_at":"2025-04-19T14:41:46.360Z","repository":{"id":99711401,"uuid":"209220310","full_name":"Niweera/fsuptutorial","owner":"Niweera","description":"Upload images to the Google Cloud Storage with ReactJS, Firebase SDK and Firestore","archived":false,"fork":false,"pushed_at":"2019-10-02T04:23:08.000Z","size":1622,"stargazers_count":18,"open_issues_count":1,"forks_count":4,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-29T08:43:55.207Z","etag":null,"topics":["firebase","firestore","reactjs","redux","redux-thunk"],"latest_commit_sha":null,"homepage":"https://fsuptutorial.firebaseapp.com/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Niweera.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-09-18T04:52:52.000Z","updated_at":"2022-06-25T09:27:11.000Z","dependencies_parsed_at":null,"dependency_job_id":"f1c7b53a-9ce6-43f0-9b7c-cdc0f5f588f5","html_url":"https://github.com/Niweera/fsuptutorial","commit_stats":null,"previous_names":[],"tags_count":0,"template":true,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Niweera%2Ffsuptutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Niweera%2Ffsuptutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Niweera%2Ffsuptutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Niweera%2Ffsuptutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Niweera","download_url":"https://codeload.github.com/Niweera/fsuptutorial/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249716496,"owners_count":21315066,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["firebase","firestore","reactjs","redux","redux-thunk"],"created_at":"2024-12-25T14:10:21.337Z","updated_at":"2025-04-19T14:41:46.353Z","avatar_url":"https://github.com/Niweera.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Upload images =\u003e Firestore + Google Cloud Storage + ReactJS\n\n\u003e Upload images to the Google Cloud Storage with ReactJS, Firebase SDK and Firestore\n\nTo view the application we are going to build beforehand, if you are familiar with `Heroku` just click the `Heroku Button`.\n\n[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)\n\n### Prerequisites\n\n1. Create a Firebase project [Firebase Console](https://console.firebase.google.com) =\u003e Click + Add Project =\u003e Follow along...\n\n2. Create a Storage Bucket and allow appropriate rules. [docs](https://firebase.google.com/docs/storage/web/start)\n\n3. Create a ReactJS application and register the app in Firebase Console. [docs](https://firebase.google.com/docs/web/setup)\n\nLets move on to the real deal...\n\n## Create Firebase Config object.\n\nCreate a folder inside /src/ as config =\u003e `/src/config`\n\nAdd the following lines to config.js inside config folder.\n\n```javascript\n    import * as firebase from \"firebase\";\n\n    const config = {\n        apiKey: \"\u003cyour-api-key\u003e\",\n        authDomain: \"\u003cyour-auth-domain\u003e\",\n        databaseURL: \"\u003cyour-database-url\u003e\",\n        projectId: \"\u003cyour-project-id\u003e\",\n        storageBucket: \"\u003cyour-storage-bucket-url\u003e\",\n        messagingSenderId: \"\u003cyour-messaging-sender-id\u003e\",\n        appId: \"\u003cyour-app-id\u003e\"\n    };\n\n    if (!firebase.apps.length) {\n    firebase.initializeApp(config);\n    }\n\n    export const auth = firebase.auth();\n    export const f = firebase;\n    // Get a reference to the storage service, which is used to create references in your storage bucket\n    export const storage = firebase.storage();\n    // Create a storage reference from our storage service\n    export const storageRef = storage.ref();\n    export const database = firebase.firestore();\n``` \n\nTo get the `config` object =\u003e Go to Settings of Firebase project and look for `Your apps` section.\n\nNow that you have created the Firebase config object let's move on to the ReactJS development.\n\nFor 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.\n\nFor my ease, I'll be using [Bootstrap](https://getbootstrap.com/)\n\nNow let's move onto creating the basic components for our App.\n\nCreate HOC folder on /src/\n\nNow create `/src/HOC/Header.js` and `/src/HOC/Footer.js`\n\nNow set up the Redux `store`\n\n`/src/store.js`\n\n```javascript\n    import { createStore, applyMiddleware, compose } from \"redux\";\n    import thunk from \"redux-thunk\";\n    import rootReducer from \"./reducers\";\n    import { f } from \"./config/config\";\n    import { reactReduxFirebase, getFirebase } from \"react-redux-firebase\";\n    import { reduxFirestore, getFirestore } from \"redux-firestore\";\n\n    // react-redux-firebase config\n    const rrfConfig = {\n    userProfile: \"users\",\n    useFirestoreForProfile: true, // Firestore for Profile instead of Realtime DB\n    attachAuthIsReady: true // attaches auth is ready promise to store\n    };\n\n    //this setting is used to avoid the error in non redux extension installed browsers\n\n    const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;\n\n    const store = createStore(\n    rootReducer,\n    composeEnhancers(\n        reactReduxFirebase(f, rrfConfig),\n        reduxFirestore(f),\n        applyMiddleware(thunk.withExtraArgument({ getFirebase, getFirestore }))\n    )\n    );\n\n    //this setting is used to avoid the error in non redux extension installed browsers\n\n    export default store;\n```\n\nNow create `rootReducer` file\n\n`/src/reducers/index.js`\n\n```javascript\n    import { combineReducers } from \"redux\";\n    import itemReducer from \"./itemReducer\";\n    import { firebaseReducer } from \"react-redux-firebase\";\n    import { firestoreReducer } from \"redux-firestore\";\n\n    export default combineReducers({\n    item: itemReducer,\n    firebase: firebaseReducer,\n    firestore: firestoreReducer\n    });\n\n    Now create the `reducer` file\n\n    For my application I have used the `itemReducer.js`\n\n    `/src/reducers/itemReducer.js`\n\n    import {\n    UPLOADING_START,\n    UPLOADING_SUCCESS,\n    UPLOADING_FAIL,\n    UPLOADING,\n    GET_DATA\n    } from \"../actions/types\";\n\n    const initialState = {\n    error: null,\n    percent: null,\n    showProgress: false,\n    image: null\n    };\n\n    export default function(state = initialState, action) {\n    switch (action.type) {\n        case UPLOADING_START:\n        return {\n            ...state,\n            percent: 0,\n            showProgress: true\n        };\n        case UPLOADING_SUCCESS:\n        return {\n            ...state,\n            error: false,\n            percent: null,\n            showProgress: false\n        };\n        case UPLOADING_FAIL:\n        return {\n            ...state,\n            error: action.payload,\n            showProgress: false\n        };\n        case UPLOADING:\n        return {\n            ...state,\n            percent: action.payload,\n            showProgress: true\n        };\n\n        case GET_DATA:\n        return {\n            ...state,\n            image: action.payload\n        };\n        default:\n        return state;\n    }\n    }\n```\n\nNow we are done with the reducer stuff;\n\nNow create the actions for reducer actions.\n\nFirst we have to declare the action types.\n\n`/src/actions/types.js`\n\n```javascript\n    export const UPLOADING_START = \"UPLOADING_START\";\n    export const UPLOADING_SUCCESS = \"UPLOADING_SUCCESS\";\n    export const UPLOADING_FAIL = \"UPLOADING_FAIL\";\n    export const UPLOADING = \"UPLOADING\";\n\n    export const GET_DATA = \"GET_DATA\";\n```\n\nNow the `itemActions.js`\n\n`/src/actions/itemActions.js`\n\n```javascript\n    //import action types\n    import {\n    UPLOADING_START,\n    UPLOADING_SUCCESS,\n    UPLOADING_FAIL,\n    UPLOADING,\n    GET_DATA\n    } from \"./types\";\n\n    //import the Google Cloud Storage reference object\n    import { storageRef } from \"../config/config\";\n\n    // Update the image\n    export const uploadImage = data =\u003e async (\n    dispatch,\n    getState,\n    { getFirestore }\n    ) =\u003e {\n    const firestore = getFirestore();\n    try {\n        // Create the file metadata\n        const metadata = {\n        contentType: \"image/jpeg\"\n        };\n\n        // Upload file and metadata to the object 'images/mountains.jpg'\n        const uploadTask = storageRef\n        .child(\"images/\" + data.name)\n        .put(data, metadata);\n\n        dispatch({ type: UPLOADING_START });\n\n        uploadTask.on(\n        \"state_changed\",\n        function(snapshot) {\n            // Observe state change events such as progress, pause, and resume\n            // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded\n            let progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;\n            dispatch({ type: UPLOADING, payload: Math.floor(progress) });\n        },\n        function(error) {\n            // Handle unsuccessful uploads\n            dispatch({ type: UPLOADING_FAIL, payload: error });\n        },\n        function() {\n            // Handle successful uploads on complete\n            // For instance, get the download URL: https://firebasestorage.googleapis.com/...\n            uploadTask.snapshot.ref.getDownloadURL().then(function(downloadURL) {\n            dispatch({ type: UPLOADING_SUCCESS });\n            firestore\n                .collection(\"data\")\n                .doc(\"user\")\n                .update({\n                image_url: downloadURL\n                })\n                .then(() =\u003e {\n                //get the latest data\n                //once the data is sent to the firestore the latest version is stored in the redux store\n                get_Data(dispatch, getState, { getFirestore });\n                })\n                .catch(e =\u003e {\n                console.log(e);\n                });\n            });\n        }\n        );\n    } catch (err) {\n        console.log(err);\n    }\n    };\n\n    export const getData = () =\u003e async (dispatch, getState, { getFirestore }) =\u003e {\n    const firestore = getFirestore();\n    try {\n        const res = await firestore\n        .collection(\"data\")\n        .doc(\"user\")\n        .get();\n\n        const data = res.data().image_url;\n\n        if (data) {\n        dispatch({ type: GET_DATA, payload: data });\n        } else {\n        dispatch({ type: GET_DATA, payload: null });\n        }\n    } catch (e) {\n        console.log(e);\n    }\n    };\n\n    const get_Data = async (dispatch, getState, { getFirestore }) =\u003e {\n    const firestore = getFirestore();\n    try {\n        const res = await firestore\n        .collection(\"data\")\n        .doc(\"user\")\n        .get();\n\n        const data = res.data().image_url;\n\n        if (data) {\n        dispatch({ type: GET_DATA, payload: data });\n        } else {\n        dispatch({ type: GET_DATA, payload: null });\n        }\n    } catch (e) {\n        console.log(e);\n    }\n    };\n```\n\nNow we are done with the reducer actions stuff.\n\nAt this point you can see the Redux Store if you have installed the Redux Dev Tools.\n\n![image](/img/1.jpg)\n\nNow let's set up `App.js` and without this you won't be able to render anything.\n\n`/src/App.js`\n\n```javascript\n    import React from \"react\";\n    import \"./App.css\";\n    import MainComponent from \"./HOC/MainComponent\";\n    import { BrowserRouter as Router, Route, Switch } from \"react-router-dom\";\n    import { Provider } from \"react-redux\";\n    import Header from \"./HOC/Header\";\n    import Footer from \"./HOC/Footer\";\n\n    import store from \"./store\";\n\n    function App() {\n    return (\n        \u003cProvider store={store}\u003e\n        \u003cRouter\u003e\n            \u003cdiv className=\"App\"\u003e\n            \u003cHeader /\u003e\n            \u003cdiv className=\"container\"\u003e\n                \u003cSwitch\u003e\n                \u003cRoute exact path=\"/\" component={MainComponent} /\u003e\n                \u003c/Switch\u003e\n            \u003c/div\u003e\n            \u003cFooter /\u003e\n            \u003c/div\u003e\n        \u003c/Router\u003e\n        \u003c/Provider\u003e\n    );\n    }\n\n    export default App;\n```\n\nNow let's move on to the most important part.\n\nNow create `/src/HOC/MainComponent.js` this MainComponent.js will contain the image upload button and other related components.\n\n```javascript\n    import React, { Component } from \"react\";\n    import { uploadImage, getData } from \"../actions/itemActions\";\n    import Spinner from \"../helpers/Spinner\";\n    import { connect } from \"react-redux\";\n    import { withRouter } from \"react-router-dom\";\n\n    class MainComponent extends Component {\n    constructor() {\n        super();\n        this.state = {\n        error: null,\n        percent: 0,\n        showProgress: null,\n        image: null\n        };\n    }\n\n    componentDidMount() {\n        this.props.getData();\n    }\n\n    //create ref\n    fileInputRef = React.createRef();\n\n    onFormSubmit = e =\u003e {\n        e.preventDefault(); // Stop form submit\n\n        //validating the file\n        //check if the file is exists\n        if (this.state.file === null) {\n        alert(\"No image is selected!\");\n        return;\n        }\n\n        //check if the image size is larger than 1MB\n        if (this.state.file.size \u003e 1048576) {\n        alert(\"Image size must be less than 1MB!\");\n        return;\n        }\n\n        //check if the dimension of the image is 2048 x 2048 px\n        if (this.state.file.width \u003e 2048 || this.state.file.height \u003e 2048) {\n        alert(\"Image dimensions must be 2048 x 2048 px\");\n        return;\n        }\n\n        //check if the file is an image\n        if (\n        this.state.file.type === \"image/jpeg\" ||\n        this.state.file.type === \"image/png\" ||\n        this.state.file.type === \"image/jpg\"\n        ) {\n        this.props.uploadImage(this.state.file);\n        } else {\n        alert(\"Please provide a valid image. (JPG, JPEG or PNG)\");\n        }\n    };\n\n    //handle file change\n    fileChange = event =\u003e {\n        event.preventDefault();\n\n        this.setState({ file: event.target.files[0] });\n\n        let imageFile = event.target.files[0];\n\n        if (imageFile) {\n        const localImageUrl = URL.createObjectURL(imageFile);\n        const imageObject = new window.Image();\n        imageObject.onload = () =\u003e {\n            imageFile.width = imageObject.naturalWidth;\n            imageFile.height = imageObject.naturalHeight;\n            URL.revokeObjectURL(imageFile);\n        };\n        imageObject.src = localImageUrl;\n        }\n    };\n\n    static getDerivedStateFromProps(nextProps, prevState) {\n        if (nextProps.showProgress !== prevState.showProgress) {\n        return { showProgress: nextProps.showProgress };\n        }\n        if (nextProps.image !== prevState.image) {\n        return { image: nextProps.image };\n        }\n        if (nextProps.percent !== prevState.percent) {\n        return { percent: nextProps.percent };\n        }\n\n        if (nextProps.error !== prevState.error) {\n        return { error: nextProps.error };\n        } else {\n        return null;\n        }\n    }\n\n    render() {\n        const { image, percent, showProgress } = this.state;\n        if (image) {\n        return (\n            \u003cdiv\n            className=\"jumbotron jumbotron-fluid mt-5 pt-4\"\n            style={{\n                backgroundColor: \"#3b3a30\",\n                textShadow: \"0 1px 3px rgba(0,0,0,.5)\",\n                color: \"white\"\n            }}\n            \u003e\n            \u003cdiv className=\"container\"\u003e\n                \u003cdiv className=\"container\"\u003e\n                \u003cdiv className=\"row\"\u003e\n                    \u003cdiv className=\"col-md-4\"\u003e\u003c/div\u003e\n                    \u003cdiv className=\"col-md-4\"\u003e\n                    \u003cdiv className=\"container\"\u003e\n                        \u003cdiv className=\"row\"\u003e\n                        \u003cdiv className=\"col-md-2\"\u003e\u003c/div\u003e\n                        \u003cdiv className=\"col-md-8 mb-2\"\u003e\n                            {image === \"empty\" ? (\n                            \u003cimg\n                                className=\"card-img-top\"\n                                src=\"https://react.semantic-ui.com/images/wireframe/image.png\"\n                                alt=\"\"\n                                style={{ width: 250, height: 250 }}\n                            /\u003e\n                            ) : (\n                            \u003cimg\n                                className=\"card-img-top\"\n                                src={image}\n                                style={{ width: 250, height: 250 }}\n                                alt=\"\"\n                            /\u003e\n                            )}\n                        \u003c/div\u003e\n                        \u003cdiv className=\"col-md-2\"\u003e\u003c/div\u003e\n                        \u003c/div\u003e\n                    \u003c/div\u003e\n                    \u003cdiv\n                        className=\"card\"\n                        style={{\n                        width: \"25rem\",\n                        backgroundColor: \"#e9ecef\",\n                        color: \"black\"\n                        }}\n                    \u003e\n                        \u003cdiv className=\"card-body\"\u003e\n                        \u003ch5 className=\"card-title\"\u003eUpload image\u003c/h5\u003e\n                        \u003cp className=\"card-text\"\u003e\n                            Select the image and click upload.\n                        \u003c/p\u003e\n                        \u003cform\u003e\n                            \u003cdiv className=\"container\"\u003e\n                            \u003cdiv className=\"row\"\u003e\n                                \u003cdiv className=\"col-md-1\"\u003e\u003c/div\u003e\n                                \u003cdiv className=\"col-md-5\"\u003e\n                                \u003cbutton\n                                    className=\"btn btn-outline-secondary btn-block\"\n                                    type=\"button\"\n                                    onClick={() =\u003e\n                                    this.fileInputRef.current.click()\n                                    }\n                                \u003e\n                                    Select Image\n                                \u003c/button\u003e\n                                \u003c/div\u003e\n                                \u003cdiv className=\"col-md-5\"\u003e\n                                \u003cbutton\n                                    className=\"btn btn-outline-secondary btn-block\"\n                                    type=\"button\"\n                                    onClick={this.onFormSubmit}\n                                \u003e\n                                    Upload\n                                \u003c/button\u003e\n                                \u003c/div\u003e\n                                \u003cdiv className=\"col-md-1\"\u003e\n                                \u003cinput\n                                    type=\"file\"\n                                    ref={this.fileInputRef}\n                                    onChange={event =\u003e this.fileChange(event)}\n                                    hidden\n                                /\u003e\n                                \u003c/div\u003e\n                            \u003c/div\u003e\n                            {showProgress ? (\n                                \u003cdiv className=\"row mt-3\"\u003e\n                                \u003cdiv className=\"col-md-12\"\u003e\n                                    \u003cdiv className=\"progress\"\u003e\n                                    \u003cdiv\n                                        className=\"progress-bar bg-success\"\n                                        style={{ width: `${percent}%` }}\n                                        role=\"progressbar\"\n                                        aria-valuenow={percent}\n                                        aria-valuemin=\"0\"\n                                        aria-valuemax=\"100\"\n                                    \u003e\u003c/div\u003e\n                                    \u003c/div\u003e\n                                \u003c/div\u003e\n                                \u003c/div\u003e\n                            ) : null}\n                            \u003c/div\u003e\n                        \u003c/form\u003e\n                        \u003c/div\u003e\n                    \u003c/div\u003e\n                    \u003c/div\u003e\n                    \u003cdiv className=\"col-md-4\"\u003e\u003c/div\u003e\n                \u003c/div\u003e\n                \u003c/div\u003e\n            \u003c/div\u003e\n            \u003c/div\u003e\n        );\n        } else {\n        return \u003cSpinner /\u003e;\n        }\n    }\n    }\n\n    const mapStateToProps = ({ item }) =\u003e ({\n    error: item.error,\n    percent: item.percent,\n    showProgress: item.showProgress,\n    image: item.image\n    });\n\n    const mapDispatchToProps = {\n    uploadImage,\n    getData\n    };\n\n    export default connect(\n    mapStateToProps,\n    mapDispatchToProps\n    )(withRouter(MainComponent));\n```\n\nNow we are done. Let's try out uploading...\n\n![image](/img/2.gif)\n\nTo view this application to try it out,\n\nGo to [FSUPTUTORIAL](https://fsuptutorial.firebaseapp.com) hosted on `Firebase`\n\nTo view the application repo in [GitHub](https://github.com/Niweera/fsuptutorial)\n\n### Acknowledgement\n\nThis tutorial is just a dream, if it weren't for these resources;\n\nhttps://medium.com/strands-tech-corner/how-to-validate-an-image-in-redux-form-1cef01e4ff6c\n\nhttps://codesandbox.io/s/04lz4580pl\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fniweera%2Ffsuptutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fniweera%2Ffsuptutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fniweera%2Ffsuptutorial/lists"}