Search code examples
javascriptreactjsbase64react-router-dom

How can I modify formData before sending it?


How to modify the code so that after submitting the image changes to base64 code and so that I can use the formData from the form in the subpage component.

At the moment the AddForm look like this:

import { Form, useNavigation } from 'react-router-dom';
import classes from './AuthForm.module.css';

function AddForm() {
  const navigation = useNavigation() 
  const isSubmitting =navigation.state === 'submitting'

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

  function handleFileInputChange(event) {
    const file = event.target.files[0];
  
    fileToBase64(file)
      .then((base64) => {
        console.log(base64);
      })
      .catch((error) => {
        console.error(error);
      });
  }
  
  return (
    <>
      <Form method="post" className={classes.form}>
      <div>
        <label htmlFor="title">Tytuł:</label>
        <input
          type="text"
          id="title"
          name='title'
        />
      </div>
      <div>
        <label htmlFor="file">Wybierz plik:</label>
        <input
          type="file"
          id="file"
          name='title'
          onChange={handleFileInputChange}
        />
      </div>
      <button disabled={isSubmitting}>{isSubmitting ? 'Adding...' : 'Add'}</button>
      </Form>
      </>
  );
}

export default AddForm;

And component where I use it look like this:

import AddForm from '../components/AddForm';

function AddPage() {
  return <AddForm/>; 
}

export default AddPage;

export async function action({ request }) { 
  const data = await request.formData();
  console.log(data)
  const postData = { 
    titlt: data.get('title'),
    file: data.get('file'),
  };
  console.log(postData)
}

I already tried to modify data in the next component but the file field in formData was null.


Solution

  • It seems the problem is that you have an onChange handler in the component that takes the input's value and converts it to a base64 value but then doesn't do anything with it, meanwhile, the route's action function only reads/accesses the form fields' current values, which will only be the file name.

    Add some state to store the base64 converted file string and add a hidden field to the form to hold this value. The action function can access this field value.

    You also had a typo in the file input where the input had the same name attribute as the title input.

    Example:

    function AddForm() {
      const navigation = useNavigation();
      const isSubmitting = navigation.state === "submitting";
    
      const [base64File, setBase64File] = useState(""); // <-- local state
    
      function fileToBase64(file) {
        return new Promise((resolve, reject) => {
          const reader = new FileReader();
          reader.readAsDataURL(file);
    
          reader.onload = () => {
            const base64 = reader.result;
            resolve(base64);
          };
    
          reader.onerror = (error) => {
            reject(error);
          };
        });
      }
    
      function handleFileInputChange(event) {
        const file = event.target.files[0];
    
        fileToBase64(file)
          .then((base64) => {
            console.log(base64);
            setBase64File(base64); // <-- update local state
          })
          .catch((error) => {
            console.error(error);
          });
      }
    
      return (
        <Form method="post" className={classes.form}>
          <div>
            <label htmlFor="title">Tytuł:</label>
            <input type="text" id="title" name="title" />
          </div>
          <div>
            <label htmlFor="file">Wybierz plik:</label>
            <input
              type="file"
              id="file"
              name="file" // <-- fix typo
              onChange={handleFileInputChange}
            />
          </div>
          <input
            name="base64File"
            value={base64File} // <-- set input's value
            hidden
            readyOnly
          />
          <button disabled={isSubmitting}>
            {isSubmitting ? "Adding..." : "Add"}
          </button>
        </Form>
      );
    }
    
    export async function action({ request }) {
      const data = await request.formData();
      console.log(data);
    
      const title = data.get("title");
      const file = data.get("base64File");
    
      const postData = {
        title,
        file
      };
      console.log(postData);
    
      ... business logic
    
      ... return response?
    }
    

    Demo

    Edit how-can-i-modify-formdata-before-sending-it