Search code examples
javascriptreactjsfile-uploadreact-router-domform-data

How can i use the loader and action functions from the react-router-dom package to POST a file to my server?


it's my first question. Please be forgiving if I have done something wrong.

Setup: I have a create-react-app which uses the react-router-dom for routing. The main idea is some kind of a simple file server. So the user can upload/drop a file on the webpage. This will be uploaded to a server and should then be listed on the page. For this the server provides an api endpoint which lists all files that were uploaded.

Because i want to use some kind of dropzone, which provides me the File object directly. I will not have a form in the end. So i think for this case i have to choose the useFetcher() and then run fetcher.submit(...).

Problem: My problem is, that the file is not send to the server. Then I tried to find the source of it. I think it happens between the two lines with the comment (9 and 27). So the file wrapped inside a FormData gets some how stringified to only the name when passed to the other function.

What I've tried As already mentioned, I checked in which step the problem occurs. So in my eyes it happens somewhere inside the library, which is "submitting" the formData to the action function. So i looked inside the library, in the file dist/index.js in the node module (react-router-dom: ^6.10.0) and in line 709 I found a function which can convert the formdata: getFormSubmissionInfo. But i think because my input is of type FormData it will not be touched and returned in the same way as it was given to the function. (See line 161, dist/index.js).

Question Is useFetcher the correct way to submit a file "in the background" to the server, so no form submit needed? And if so, how can i provide the file to my action function?

Code snippet: To seperate the problem from my project I've created a github repo of my problem (https://github.com/fabalexsie/StackOverflow_SendFileWithReactRouterDomAction).

The loader and action functions are referenced in the routing object.

export async function loader() {
    return fetch('http://localhost:8080/fileList').then(r => r.json())
}

export async function action({request}) {
    const formData = await request.formData();
    console.log("Formdata in action", formData) // here the file is only the name
    await fetch('http://localhost:8080/upload', {
        method: 'POST',
        body: formData
    });
    return {success: true};
}

export function Details() {
    const fileList = useLoaderData();
    const fetcher = useFetcher();

    const handleUpload = (ev) => {
        const file =  ev.target.files[0];
        console.log("File from handle upload", file);
        const formData = new FormData();
        formData.append('file', file);
        formData.append('pw', "InRealityComesFromSomeOtherInput");
        console.log("Formdata in handle upload", formData); // here the file is the file object
        fetcher.submit(formData, {method: 'POST', action: '/details'})
    }

    return (
        <>
        <ul>
            {fileList.map(f => 
                <li key={f}>
                    <a href={`http://localhost:8080/files/${f}`}>{f}</a>
                </li>
            )}
        </ul>
        <input type="file" id="file" name="file" onChange={handleUpload} />
        </>
    );
}

Solution

  • After a break, I looked into it again and found the error.

    When submitting, the encoding type 'multipart/form-data' must be specified in order to send files as POST requests:

    fetcher.submit(formData, {method: 'POST', action: '/details', encType: 'multipart/form-data'})
    

    PS: I have also published the solution in the repo.