reactjsaxiospromise

How to pass variables other than the Promises between multiple .then() chains?


After looking up answers about how to download files with Axios in React I implemented the following code:

const [fileName, setFileName] = useState("")
const [extension, setExtension] = useState("")

const downloadFile = useCallback(async (path) => {

    Axios.get(
        `http://localhost:8080/api${path}`,
        { responseType: 'blob' })
        .then(res => {

            let headers = res.headers['content-disposition']

            let fileName = headers.split('filename=')[1].split('.')[0]
            let extension = headers.split('.')[1].split(';')[0]

            setFileName(fileName)
            setExtension(extension)

            return res.data
        })
        .then(blob => {

            const url = window.URL.createObjectURL(blob)
            const link = document.createElement('a')

            const fullFileName = fileName + "." + extension

            link.href = url
            link.download = fullFileName;
            document.body.appendChild(link)

            link.click()

            link.remove()
            URL.revokeObjectURL(url)
        })
        .catch(err => console.log(err))
},
    []
)

I was looking to set the filename and extension in a useState in the first .then() and then use it to name the downloaded file its original name. However, I came to see that it simply doesn't executes the setters before the next .then() call, so the fileName and extension variables are still empty strings by then, and it gives it a default name because of it. How could I make it pass on the correct values to the next function in the chain?


Solution

  • Callbacks are called with its context from when they are created. So:

    1. Widget is rendered and downloadFile with fileName is empty
    2. You call downloadFile in this context
    3. In the first then you change widget state - rerender
    4. New downloadFile callback is created with fileName populated BUT your old callback is running
    5. Your old callback runs second then with the old context.

    Easy solution:

    Don't store fileName and extension in widget state if you do not have to. Use simple variables.

    const downloadFile = useCallback(async (path) => {
        let fileName;
        let extension;
    
        Axios.get(
            `http://localhost:8080/api${path}`,
            { responseType: 'blob' })
            .then(res => {
    
                let headers = res.headers['content-disposition']
    
                fileName = headers.split('filename=')[1].split('.')[0]
                extension = headers.split('.')[1].split(';')[0]
    
                return res.data
            })
            .then(blob => {
    
                const url = window.URL.createObjectURL(blob)
                const link = document.createElement('a')
    
                const fullFileName = fileName + "." + extension
    
                link.href = url
                link.download = fullFileName;
                document.body.appendChild(link)
    
                link.click()
    
                link.remove()
                URL.revokeObjectURL(url)
            })
            .catch(err => console.log(err))
    },
        []
    )