1

So I am trying to send data that is inputted from a form in React to an e-mail address.

Everything is working perfectly, except where I try and upload a file and it only sends the text of the path file to the e-mail.


eg, the email will look like this:

From: test

Email: testing@gmail.com

Message: testing

File: C:\fakepath\2020-06-10 18-49-37.mp4


I obviously don't want the text to show up on the e-mail, but a file to be uploaded instead.

Any ideas on how to make this work.

I will post heaps of code below for everyone to check out.

Thanks in advance!

Form.jsx

import React from 'react'
import Axios from 'axios'


class Form extends React.Component {
  constructor(props) {
    super(props);
    super(props);
    this.state = {
      name: '',
      email: '',
      message: '',
      file: null,
    };
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState(
      {
        [event.target.name]: event.target.value,
        [event.target.email]: event.target.value,
        [event.target.message]: event.target.value,
        [event.target.file]: event.target.file,
      }
    );
  }

    this.setState({
      [name]: value
    })

  }

  handleSubmit(event) {
    console.log(this.state)
    event.preventDefault();
    const data = {
      name: this.state.name,
      email: this.state.email,
      message: this.state.message,
      file: this.state.file,
    };

    Axios.post("api/v1/sendMail", data)
      {
      alert("Thank you! We will be in touch shortly!")
      }
  }

  render() {
    return (
      <React.Fragment>
        <div className="formContainer centerImg" id="formScale">
          <form onSubmit={this.handleSubmit} method="post">
            <div className='contact'>
              <h2 className="formTitles">YOUR FULL NAME</h2>
              <input
                name='name'
                value={this.state.name}
                onChange={this.handleChange}
                required />
              <h2 className="formTitles">EMAIL ADDRESS</h2>
              <input
                name='email'
                value={this.state.email}
                onChange={this.handleChange}
                required />
              <h2 className="formTitles">UPLOAD FILE</h2>
              <input
                type='file'
                name='file'
                value={this.state.file}
                onChange={this.handleChange} />
              <div id='messageForm'>
                <h2 className="formTitles">MESSAGE</h2>
                <textarea
                  name='message'
                  value={this.state.message}
                  onChange={this.handleChange}
                  required />
              </div>                      
              <div id='submit-btn'>
                <input type='submit' value='SUBMIT' />
              </div>
            </div>
          </form>
        </div>
      </React.Fragment>
    )
  }
}

export default Form

(server) index.js

const server = require('./server')

const bodyParser = require("body-parser");
const cookieParser = require("cookie-parser");

server.use(bodyParser.urlencoded({ extended: true }));
server.use(bodyParser.json());
server.use(cookieParser());

const { sendEmail } = require("../server/routes/mail");

server.post("/api/v1/sendMail", (req, res) => {
  sendEmail(req.body.name, req.body.email, req.body.message, req.body.file);
});

const port = 3000

server.listen(port, () => {
  // eslint-disable-next-line no-console
  console.log('Server listening on port', port)
})

mail.js

const mailer = require("nodemailer");

const getEmailData = (name, email, message, file) => {
    let data = null;


            data = {
                from: "Contact Form",
                to: "(*correct e-mail here*)",
                subject: `Message from the contact form!`,
                html: `<b>From:</b>&nbsp;${name}
                      <br><br><b>Email:</b>&nbsp;${email}
                      <br><br><b>Message:</b>&nbsp;${message}
                      <br><br><b>File:</b>&nbsp;${file}`
            }
    return data;
}


    const sendEmail = (name, email, message, file) => {

        const smtpTransport = mailer.createTransport({
            service: "Gmail",
            auth: {
                user: "(correct e-mail here)",
                pass: "(correct password here)"
            }
        })

        const mail = getEmailData(name, email, message, file)

        smtpTransport.sendMail(mail, function(error, response) {
            if(error) {
                console.log(error)
            } else {
                alert( "Thank you! We will be in touch shortly!")
            }
            smtpTransport.close();
        })


    }

    module.exports = { sendEmail }

3 Answers3

2

I suggest you send a FormData and parse the FormData on the server.

For express servers you can use multer to parse the request.

send the form data on the client side:

const formData = new FormData();
formData.append("name", this.state.name);
formData.append("email", this.state.email);
formData.append("message", this.state.message);
formData.append("file", this.state.file);
Axios.post("api/v1/sendMail", formData);

on server side, use the directUpload middleware and console.log(req.files) in your request handler:

const upload = multer({
  storage: multer.memoryStorage()
});
const directUpload = upload.fields([{
  name: "name"
}, {
  name: "email"
}, {
  name: "message"
}, {
  name: "file"
}]);

server.post("/api/v1/sendMail", directUpload, (req, res) => {
  console.log(req.files);
  sendEmail(req.body.name, req.body.email, req.body.message, req.body.file);
});
ardean
  • 152
  • 7
2

You can either send FormData or convert file to dataUri and handle it on the backend

Form Data

handleSubmit(event) {
  event.preventDefault();

  // or you can set ref to form and use new FormData(formRef.current)
  // but then keeping state doesnt make sense at all
  const formData = new FormData();
  for(let [key, value] of Object.entries(this.state)) {
    formData.append(key, value);
  }


  Axios.post("api/v1/sendMail", formData)
      {
      alert("Thank you! We will be in touch shortly!")
      }
  }
handleFileChange({target: {name, files}}) {
   this.setState(state => ({...state, [name]: files[0]}))
}
 <input
  type='file'
  name='file'
  onChange={this.handleFileChange} />

Data Uri

const toBase64 = file => new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () => resolve(reader.result);
    reader.onerror = error => reject(error);
});

 handleSubmit(event) {
    event.preventDefault();
    const data = {...this.state};

    Axios.post("api/v1/sendMail", data)
      {
      alert("Thank you! We will be in touch shortly!")
      }
  }

handleFileChange({target: {name, files}}) {
   toBase64(files[0]).then(dataUri => {
     this.setState(state => ({...state, [name]: dataUri}))
   })
}
<input
  type='file'
  name='file'
  onChange={this.handleFileChange} />

Also this can be simplified

handleChange({target: {name, value}}) {
    this.setState(state => ({...state, [name]: value}))
}

Example

const { Component, Fragment, createRef } = React;

const toBase64 = file => new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () => resolve(reader.result);
    reader.onerror = error => reject(error);
});

class Form extends Component {
  constructor(props) {
    super(props);
    
    this.state = {
      variant1: {
        name: '',
        email: '',
        message: '',
        file: null,
      },
      variant2: {
        name: '',
        email: '',
        message: '',
        file: null
      }        
    };
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit1 = this.handleSubmit1.bind(this);
    this.handleSubmit2 = this.handleSubmit2.bind(this);
    this.handleFileChange1 = this.handleFileChange1.bind(this);
    this.handleFileChange2 = this.handleFileChange2.bind(this);
    this.formRef = createRef(null);
  }
  
  handleFileChange1({target: {name, files}}) {
    toBase64(files[0]).then(dataUri => {
      this.setState(state => ({
        ...state,
        variant1: {
          ...state.variant1,
          [name]: dataUri
        }
      }))
    })
  }
  
  handleFileChange2({target: {name, files}}) {
    this.setState(state => ({
      ...state,
      variant2: {
        ...state.variant2,
        [name]: files[0]
      }
    }))
  }

  handleChange({target: {name, value}}) {
    this.setState(state => ({
      ...state,
      variant1: {
        ...state.variant1,
        [name]: value
      },
      variant2: {
        ...state.variant2,
        [name]: value
      }
    }))
  }

  handleSubmit1(event) {
    const data = {...this.state.variant1}
    console.log('json', data);
  }
  
  handleSubmit2(event) {
    const formData = new FormData();
    for(let [key, value] of Object.entries(this.state.variant2)) {
      formData.append(key, value);
    }
    console.log('form data', [...formData.entries()]);
    
    const formData1 = new FormData(this.formRef.current);
    console.log('formdata ref', [...formData1.entries()]);
  }

  render() {
    const { name, email, message } = this.state.variant1;
    
    return (
      <Fragment>
        <div className="formContainer centerImg" id="formScale">
          <form ref={this.formRef} onSubmit={(event) => {event.preventDefault();this.handleSubmit1(event);this.handleSubmit2(event)}} method="post">
            <div className='contact'>
              <h2 className="formTitles">YOUR FULL NAME</h2>
              <input
                name='name'
                value={name}
                onChange={this.handleChange}
                required />
              <h2 className="formTitles">EMAIL ADDRESS</h2>
              <input
                name='email'
                value={email}
                onChange={this.handleChange}
                required />
              <h2 className="formTitles">UPLOAD FILE</h2>
              <input
                type='file'
                name='file'
                onChange={(event) => {this.handleFileChange1(event);this.handleFileChange2(event)}} />
              <div id='messageForm'>
                <h2 className="formTitles">MESSAGE</h2>
                <textarea
                  name='message'
                  value={message}
                  onChange={this.handleChange}
                  required />
              </div>                      
              <div id='submit-btn'>
                <input type='submit' value='SUBMIT' />
              </div>
            </div>
          </form>
        </div>
      </Fragment>
    )
  }
}

ReactDOM.render(
    <Form />,
    document.getElementById('root')
  );
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<div id="root"></div>
Józef Podlecki
  • 10,453
  • 5
  • 24
  • 50
  • Updated answer. Here are two solutions – Józef Podlecki Jun 11 '20 at 11:30
  • copied the code, it didn't work. it all comes up with undefined. – Matthew-Devonport Jun 11 '20 at 11:39
  • Which solution did you choose? Did you bind `handleFileChange`? What does the `console log(this.state)` or `console log([...formData.entries()])` show? – Józef Podlecki Jun 11 '20 at 12:10
  • I tried both, neither worked at all. It either just came up with the pathfile again as text or came up as undefined. I did bind handleFileChange as well. Console log either shows undefined or just text of what's being inputted from the form. – Matthew-Devonport Jun 11 '20 at 22:24
  • Added snippet with three mentioned cases. – Józef Podlecki Jun 11 '20 at 23:25
  • Alright so, using the first one (handlesubmit1, handlefilechange1, etc), I get an e-mail with just json data basically as text. – Matthew-Devonport Jun 11 '20 at 23:38
  • Using the second variant, everything comes up as undefined. – Matthew-Devonport Jun 11 '20 at 23:47
  • Check in browser networking tab if axios is sending requests with specific content type and formatted payload for each type. This is backend issue. axios probably required additional 'content-type': 'application/x-www-form-urlencoded' header – Józef Podlecki Jun 12 '20 at 00:08
  • I've been troubleshooting this over the last couple of days to no avail. All I can get is just a whole lot of json data in the body at best. I can console log the file up until the point before I set the state and everything is fine, and then it all just doesn't work. I put the site live to see if it was a development issue and then I got 503 errors on Heroku. – Matthew-Devonport Jun 15 '20 at 09:58
  • Did you check in networking tab if request has required content type and form-data? – Józef Podlecki Jun 15 '20 at 10:03
  • Can you set `app.use(express.json({limit: '50mb'}));` ? Or if you are using form data `app.use(express.urlencoded({limit: '50mb'}));` – Józef Podlecki Jun 15 '20 at 10:11
  • in the network tab it shows: file: data:image/jpeg;base64,/9j/4gv4SUNDX1BST0ZJTEUAAQE... after the ... is just a whole lot of json data I assume. – Matthew-Devonport Jun 15 '20 at 10:12
  • I am using the base64 way as well – Matthew-Devonport Jun 15 '20 at 10:12
  • do I set that on my index.js? If so it doesn't do anything. – Matthew-Devonport Jun 15 '20 at 10:16
  • 1
    So I managed to get it working. I used all the Data Uri examples, had to put the limit as 50mb, although I had to put the file as an attachment with nodemailer syntax in my mail.js file which looked like: attachments: [ {path: `${file}` }, Thanks for your help :) – Matthew-Devonport Jun 15 '20 at 23:42
0

Józef Podlecki provided guidance to get the right solution, this is the final code to anyone who is trying to get the final solution to work.

Form.jsx

import React from 'react'
import Axios from 'axios'

const toBase64 = file => new Promise((resolve, reject) => {
  const reader = new FileReader();
  reader.readAsDataURL(file);
  reader.onload = () => resolve(reader.result);
  reader.onerror = error => reject(error);
});

class Form extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: '',
      email: '',
      message: '',
      file: null,

    };
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleFileChange = this.handleFileChange.bind(this);
  }

  handleFileChange({target: {name, files}}) {
    toBase64(files[0]).then(dataUri => {
      this.setState(state => ({...state, [name]: dataUri}))
    })
 }

  handleChange(event) {
    this.setState(
      {
        [event.target.name]: event.target.value,
        [event.target.email]: event.target.value,
        [event.target.message]: event.target.value,
      }
    );
  }

  handleSubmit(event) {
    event.preventDefault();
    const data = {...this.state};

    Axios.post("api/v1/sendMail", data)
      {
      alert("Thank you! We will be in touch shortly!")
      }
  }

  render() {
    return (
      <React.Fragment>
        <div className="formContainer centerImg" id="formScale">
          <form onSubmit={this.handleSubmit} method="post">
            <div className='contact'>
              <h2 className="formTitles">YOUR FULL NAME</h2>
              <input
                name='name'
                value={this.state.name}
                onChange={this.handleChange}
                required />
              <h2 className="formTitles">EMAIL ADDRESS</h2>
              <input
                name='email'
                value={this.state.email}
                onChange={this.handleChange}
                required />
              <h2 className="formTitles">UPLOAD FILE</h2>
              <input
                type='file'
                name='file'
                // value={this.state.file}
                onChange={this.handleFileChange} />
              <div id='messageForm'>
                <h2 className="formTitles">MESSAGE</h2>
                <textarea
                  name='message'
                  value={this.state.message}
                  onChange={this.handleChange}
                  required />
              </div>
              <div id='submit-btn'>
                <input type='submit' value='SUBMIT' />
              </div>
            </div>
          </form>
        </div>
      </React.Fragment>
    )
  }
}

export default Form

(server) index.js

const server = require('./server')
const express = require('express')

const bodyParser = require("body-parser");
const cookieParser = require("cookie-parser");

server.use(express.json({limit: '50mb'}))
server.use(bodyParser.urlencoded({ extended: true }));
server.use(bodyParser.json());
server.use(cookieParser());

const { sendEmail } = require("../server/routes/mail");



server.post("/api/v1/sendMail", (req, res) => {
  sendEmail(req.body.name, req.body.email, req.body.message, req.body.file);
});

const port = process.env.PORT || 3000;

server.listen(port, () => {
  // eslint-disable-next-line no-console
  console.log('Server listening on port', port)
})

mail.js

const mailer = require("nodemailer");

const getEmailData = (name, email, message, file) => {
    let data = null;


        data = {
            from: "Contact Form",
            to: "(correct e-mail here)",
            subject: `Message from the contact form!`,
            attachments: [
                {
                  path: `${file}`
                }
              ],
            html: `<b>From:</b>&nbsp;${name}
                  <br><br><b>Email:</b>&nbsp;${email}
                  <br><br><b>Message:</b>&nbsp;${message}`
            }
    return data;
}


    const sendEmail = (name, email, message, file) => {

        const smtpTransport = mailer.createTransport({
            service: "Gmail",
            auth: {
                user: "(correct e-mail here)",
                pass: "(correct password here)"
            }
        })

        const mail = getEmailData(name, email, message, file)

        smtpTransport.sendMail(mail, function(error, response) {
            if(error) {
                console.log(error)
            } else {
                alert( "Thank you! We will be in touch shortly!")
            }
            smtpTransport.close();
        })


    }

    module.exports = { sendEmail }