0
votes

I've trying to:

  • Upload image to firebase storage
  • create field 'fileUrl' in Firestore under the 'articles' document
  • reference the doc in Firestore so that each image knows to which article it is assigned.

I managed to do all of that in a way but the issue is that when I submit the form with a title, content and image, it creates a new document in Firestore with just a the image ('fileUrl'), instead of creating a document that includes the rest of the form's data.

I know that this is probably because in my UploadFile.js I'm creating a new document but how do I join the Field created in UploadFile.js with the fields in AddArticle.js?

I'm also getting ''Unhandled Rejection (TypeError): Cannot read property 'state' of undefined' for this line:

firebase.firestore().collection('articles').doc(this.state.documentId).update({
            fileUrl: fileUrl
        })

UploadFile.js

import React from "react";
import firebase from '../Firebase';

function UploadFile() {

  const [fileUrl, setFileUrl] = React.useState(null);

  const onFileChange = async (e) => {
    const file = e.target.files[0]
    const storageRef = firebase.storage().ref()
    const fileRef = storageRef.child(file.name)
    await fileRef.put(file);
    setFileUrl(await fileRef.getDownloadURL().then(fileUrl => {
        firebase.firestore().collection('articles').doc(this.state.documentId).update({
            fileUrl: fileUrl
        })
        .then(() => {
            setFileUrl('')
        })
    } ));
  };

  return (
    <>
        <input type="file" onChange={onFileChange} />
      <div>
          <img width="100" height="100" src={fileUrl} alt=''/>
          
      </div>
    </>
  );
}

export default UploadFile;

AddArticle.js

import React, { Component } from 'react';
import firebase from '../Firebase';
import UploadFile from '../components/UploadFile';

class AddArticle extends Component {

  constructor() {
    super();
    this.ref = firebase.firestore().collection('articles');
    this.state = {
      title: '',
      content: '',
      fileUrl: ''
    };
  }
  onChange = (e) => {
    const state = this.state
    state[e.target.name] = e.target.value;
    this.setState(state);
  }
 
  onSubmit = (e) => {
    e.preventDefault();

    const { title, content, fileUrl } = this.state;

    this.ref.add({
      title,
      content,
      fileUrl

    }).then((docRef) => {
      this.setState({
        title: '',
        content: '',
        fileUrl: '',
        documentId: docRef.id
      });
      
      this.props.history.push("/")
    })
    .catch((error) => {
      console.error("Error adding document: ", error);
    });
  }

  render() {
    const { title, content, fileUrl } = this.state;

    return (
      <div className="container">
      <br></br><br></br><br></br>
        <div className="panel panel-default">
          <div className="panel-heading">
            <h3 className="panel-title text-center">
              Create a new article
            </h3>
          </div>
          <br></br><br></br>
          <div className="panel-body">
            <form onSubmit={this.onSubmit}>
              <div className="form-group">
                <label for="title">Title:</label>
                <input type="text" className="form-control" name="title" value={title} onChange={this.onChange} placeholder="Title" />
              </div>
              <div className="form-group">
                <label for="content">Content:</label>
                <textArea className="form-control" name="content" onChange={this.onChange} placeholder="Content" cols="80" rows="20">{content}</textArea>
              </div>
              
              {/* <input type="file" onChange={this.onFileChange} /> */}
              <UploadFile onChange={this.onChange} value={fileUrl}/>
              <button type="submit" className="btn btn-success">Submit</button>
            </form>
          </div>
        </div>
      </div>
    );
  }
}

export default AddArticle;

1
How is the flow of your code? which is the order, is first created the article or uploaded image? or both actions are fired at same time?Jan Hernandez
Hi, the idea was to have the text fields and the upload image in the same form and have everything created at the same time but I couldn't have the async in AddArticle.js because AddArticle.js is a class which is why I put it in a different file and called the component in my form underneath the text fields. I'm not sure if this was a good idea. Is there an example of a better way to do this?cldev

1 Answers

0
votes

Each time you call firebase.firestore().collection('articles').add(...), you add a new document to the database. Since you call that in both AddArticle and in UploadFile, your image URL and the other fields indeed will end up in separate documents.

The way you're handling the file upload is a bit unusual to me, as I'd normally expect that upload to happen when the other fields are also submitted. Right now, it's not clear to me what the image belong to, as J.A.Hernández also commented.

Either way: you'll need to remove one of the firebase.firestore().collection('articles').add(...) calls and instead pass the document ID into that call. Say that you first add the text fields and then upload the image, the UploadFile can then update the document with:

firebase.firestore().collection('articles').doc(documentId).update({
    fileUrl: fileUrl
})

One way to pass the document ID from the to is by keeping it in the state:

this.ref.add({
  title,
  content,
  fileUrl

}).then((docRef) => {
  this.setState({
    title: '',
    content: '',
    fileUrl: '',
    documentId: docRef.id
  });
  
  this.props.history.push("/")
})

And then:

firebase.firestore().collection('articles').doc(this.state.documentId).update({
    fileUrl: fileUrl
})