1

Beginner Alert! :) I am setting FormData in child component and then passing it to the parent component using formReducer and dispatch, but in parent formData.entries() is always empty!

ChildComponent.js

function ChildComponent({signed, fileAttached}){
    const { dispatch } = useContext(ContactFormContext);

     const changeHandler = (event) => {

const formData = new FormData();

formData.append('File', event.target.files[0]);


dispatch({ type: "FILE_ATTACHED", payload: formData })
};

return (
<>
            <div>
        <input type="file" name="file" onChange={changeHandler} />
    </div>
</>);
}

ParentComponent.js

function useFormProgress(fileAttached) {
     
     
    function goForward() {
        const currentStep = 1;

        let appvariables = [
                {
                  "key": "PUID",
                  "value": "a2sd"
                },
                {
                  "key": "ApplicationNames",
                  "value": "Trello, abc"
                }
              ];
        switch(currentStep) {
          case 0:
            break;
          case 1:
            console.log(fileAttached);
          if(fileAttached != null)
              sendEmail("Resignation Letter", appvariables, fileAttached);
          break;
        }
    }
    return [goForward];
}

function sendEmail(templateName, variables, attachment){
  console.log("sending email...");
    const requestBody = {
                    "templateName": templateName,
                    "recipients": [    
                    "abc@xyz.com"
                    ],
                    "variables":  variables,
                    "files": attachment
                };

fetch('https://localhost:5001/api/Email',{
  method:'Post',
  body: JSON.stringify(requestBody),
  headers:{'Content-Type': 'application/json'},
 });

}

const initialState = {
      signed: "",
      fileAttached: null
};

function formReducer(state, action) {
   switch (action.type) {
    case "SIGNED":
      return { ...state, signed: action.payload };
    case "FILE_ATTACHED":
      return {...state, fileAttached: action.payload};
    default:
      throw new Error();
  }
}


function ParentComponent() {

   const [state, dispatch] = useReducer(formReducer, initialState);
     const { signed, fileAttached } = state;

     const steps = [<ChildComponent {...{signed, fileAttached}}/>];

   const [goForward] = useFormProgress(fileAttached);


    return (
        <ContactFormContext.Provider value={{ dispatch }}>
          <div>{steps[0]}
        <div><button onClick={e => {
           e.preventDefault();
              goForward();
        }}
             
        >  Parent Button
        </button>
        </div>
    </div>
        </ContactFormContext.Provider>
       );
}

ContactFormContext.js

const ContactFormContext = React.createContext();

In the switch statement above (ParentComponent), the console.log(FileAttached) shows FormData with 0 entries(see image attached), also the API request is not successful.!

enter image description here

you can try it out in https://jscomplete.com/playground

  1. add context on top

  2. add child component code

  3. add parentcomponent code

  4. add the following line

      ReactDOM.render(<ParentComponent/>, mountNode);
    

MyAPI Method

[HttpPost]
    public JsonResult Get(EmailRequest email)
    {
         //the request never comes here
     }

EmailRequest.cs

public class EmailRequest
{
    public string TemplateName { get; set; }
    public string[] Recipients { get; set; }
    public List<Variables> Variables { get; set; }
    public FormFileCollection files { get; set; }
}
Samra
  • 1,815
  • 4
  • 35
  • 71

3 Answers3

1

In order to get values from entries method of FormData by using console.log you should do it like this:

  for (var [key, value] of attachment.entries()) {
    console.log("log from for loop", key, value);
  }

also, if you want to send file to server using POST request, you cannot stringify file that you want to send. What you are sending currently in your json payload looks like this "files": {}. You need to serialize it in a different manner. This means that you need to change the way you are sending this file. Check the answers of this question: How do I upload a file with the JS fetch API?

For serializing FormData, you can can check this post: https://gomakethings.com/serializing-form-data-with-the-vanilla-js-formdata-object/

Lazar Nikolic
  • 4,261
  • 1
  • 22
  • 46
0

I added your codes in codesandbox and everything seems to be ok.

Codesandbox demo

enter image description here

Saeed Hemmati
  • 453
  • 2
  • 11
0

So, starting from Lazar Nikolic's answer above to stop trying json.stringify!.. after stumbling onto a few blockers here and there I am finally able to send a File successfully to the API!!

Here are some important steps to note:

In the backend - I changed controller method:

  1. Added [FromForm] tag

    [HttpPost]
     public JsonResult Get([FromForm] EmailRequest email)
    

This post helped me.

In the Front-end side

  1. Changed ChildComponent.js as follows

    function ChildComponent({signed, fileAttached}){
    const { dispatch } = useContext(ContactFormContext);
    
    const changeHandler = (event) => {
    
    dispatch({ type: "FILE_ATTACHED", payload: event.target.files[0] })
    };
    
    return (
    <>
         <div>
     <input type="file" name="file" onChange={changeHandler} />
    </div>
    </>);
    }
    
  2. Changed sendEmail function in ParentComponent.js as follows

    function sendEmail(templateName, variables, attachment){
      console.log("sending email...");
    
      const formData = new FormData();
    
     formData.append('files', attachment);
     formData.append('templateName', templateName);
     formData.append('recipients', [    
      "abc@xyz.com"
     ]);
     formData.append('variables', variables);
    
    
    fetch('https://localhost:5001/api/Email',{
    method:'Post',
    body: formData,
    //headers:{'Content-Type': 'application/json'},
    });
    
    }
    
  3. Notice the email object being received as a result had all properties set as null until I removed the Content-type header and then the browser will add multipart/form-data header itself.. I got this help from @madhu131313's comment here

  4. we cannot pass an object array directly under form-data so variables array was empty..i did the following

    for(let i = 0; i < variables.length; i++){
     formData.append(`variables[` + i + `].key`, variables[i].key);
     formData.append(`variables[` + i + `].value`, variables[i].value);
    }
    

instead of

   formData.append('variables', variables);

and changed recipients as follows:

   let recipients = [    
  "abc@xyz.com",
  "nop@xyz.com",
  "stu@xyz.com"
  ];

   for(let i = 0; i < recipients.length; i++){
    formData.append(`recipients[` + i + `]`, recipients[i]);
  }
Samra
  • 1,815
  • 4
  • 35
  • 71