Браузер не обновляет изображение, когда URL-адрес источника изображения изменен на URL-адрес изображения в корзине AWS S3

Мое приложение реагирования позволяет пользователю загружать фотографии из своей системы и Facebook (еще не реализовано). После загрузки из системы URL отображаемых изображений являются URL-адресами объектов. При нажатии кнопки «Сохранить» с сервера запрашиваются заданные URL-адреса для каждого изображения. Заранее назначенные URL-адреса разделяются, чтобы получить только часть URL-адреса (до .png или .jpg). Затем URL-адреса источника изображения изменяются на заранее заданные URL-адреса. Ожидается, что изображение обновится путем загрузки изображения из нового источника изображения. Но это не так. Однако, если я щелкну правой кнопкой мыши по выбору загрузки изображения, изображение загрузится. Я пробовал такие вещи, как очистка кеша, размещение кода рендеринга изображений в компоненте реагирования, использование резервного варианта onError, уникальные ключи для изображений. Не работает. Может ли кто-нибудь сообщить мне обходной путь. Код приведен ниже. Вот краткая анатомия моего кода:

useEffect — отображает кнопку сохранения только тогда, когда в массив изображений добавлено изображение. В настоящее время добавлены локальные изображения. В будущем изображения будут добавляться из различных источников, таких как AWS S3, Instagram, Facebook и т. д.

inputFilePickerFunc — в настоящее время эта функция проверяет размеры изображений, выбранных из локальной системы. В будущем эта функция будет проверять размеры изображений, загружаемых из внешних API. Эта функция после проверки размеров, чтобы убедиться, что изображения имеют правильный размер, добавляет изображения в массив изображений. Локальные URL-адреса преобразуются в URL-адреса объектов для источников изображений.

setImgProfilePic — установить изображение в качестве изображения профиля.

deleteItem — удаляет изображение из DOM.

savePics — вычисляет количество изображений для сохранения в S3. Получает множество заранее назначенных URL-адресов. Сохраняет изображения в S3. Когда S3 отвечает, что загрузка прошла успешно, разделяет заранее назначенные URL-адреса, чтобы получить только ту часть, которая загружает изображение, и назначает эту разделенную часть в качестве нового источника изображений.

Итак, проблема в том, что в savePics изображения не обновляются, хотя их источники изменились. Мне приходится вручную попросить браузер обновить страницу, щелкнув правой кнопкой мыши и выбрав «Загрузить изображение».

фотопикер.js

import Box from '@mui/material/Box'
import * as React from 'react';
import { useState, useEffect } from "react";
import ReactDOM from "react-dom/client";
import '../../css/styles.css'
import Typography from '@mui/material/Typography';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faRectangleXmark } from "@fortawesome/free-solid-svg-icons";
import { faTrashCan } from "@fortawesome/free-solid-svg-icons";
import { faComputer } from "@fortawesome/free-solid-svg-icons";
import FacebookIcon from '@mui/icons-material/Facebook';
import Icon from '@mui/material/Icon';
import { useRef } from 'react';
import Button from '@mui/material/Button';
// import { Provider } from 'react-redux';
import { store } from '../appstate/store'
// import { useSelector, useDispatch } from 'react-redux'
// import { picArrayAdd, picArrayDel } from '../appstate/photopickerslice';
import axios from 'axios';
import Tooltip from '@mui/material/Tooltip';

export default function PhotoPicker() {
    const inputRef = useRef();
    const openFileDialog = (e) => {
        inputRef.current.value = null;
        inputRef.current.click()
    }
    // const initialImageArrayState = [];
    const [presignedURL, setPresignedURL] = useState('');
    const [imageArray, setImageArray] = useState([]);
    const [displaySaveCancelButton, setDisplaySaveCancelButton] = useState("none");
    const [profilePic, setProfilePic] = useState('');

    useEffect(() => {
        imageArray.length !== 0 ? setDisplaySaveCancelButton("") : setDisplaySaveCancelButton("none")
    })
  
    const inputFilePickerFunc = (e) => {
        // console.info(e.target.files);
        var file = e.target.files;
        var k = Object.keys(file);
        var objStore = [];
        k.forEach(
            (key) => {
                objStore.push(file[k[key]]);
            }
        );

        //Check if file is image
        let pattern = /image/
        objStore.forEach((index) => {
            let text = index.type;
            /* console.info(text); */
            let result = text.match(pattern);
            if (result == null) {
                console.info('invalid file');
                objStore.splice(index, 1)
            }
        })
        //Check image size
        objStore.forEach((index) => {
            let imageSize = index.size;
            /* console.info(text); */
            if (imageSize > 15728640) {
                console.info('file ' + index + ' image is bigger than 15 MB size limit.');
                objStore.splice(index, 1)
            }
        })

        //check image dimensions

        objStore.forEach(
            (item) => {
                var im = new Image();
                im.src = URL.createObjectURL(item);
                im.onload = function () {
                    if (im.width < 400 || im.height < 500) {
                        objStore.splice(item, 1);
                        console.info('dimensions should be minimum 400 x 500 of image ' + item.name)

                    }
                    else {
                        console.info(imageArray);
                        setImageArray(prevState => {
                            return [...prevState, { "imagesource": im.src, "profilepic": "no", "file": item, "inS3": "no" }]
                        }
                        )

                    }
                }
                console.info(imageArray)
            }
        )

    }
    const setImgProfilePic = (e) => {
               setProfilePic(e.currentTarget.id);
        let imageArrayDump = [...imageArray];
        for (let i = 0; i < imageArrayDump.length; i++) {
            if (imageArrayDump[i].profilepic == "yes") {
                imageArrayDump[i].profilepic = "no";
                break;
            }
        }
        for (let k = 0; k < imageArrayDump.length; k++) {
            if (imageArrayDump[k].imagesource == e.currentTarget.id) {
                imageArrayDump[k].profilepic = "yes";
                setImageArray([...imageArrayDump]);
                break;
            }
        }

    }
    function deleteItem(e, item) {
        e.stopPropagation();
        let indexofItemtoBeDeleted;
        let dupArr = imageArray.slice();
        for (let i = 0; i < dupArr.length; i++) {
            if (dupArr[i].imagesource == item) {
                indexofItemtoBeDeleted = i;
                break;
            }
        }
        dupArr.splice(indexofItemtoBeDeleted, 1);
        setImageArray([...dupArr]);
    }
      
   
    function savePics() {  
        //check in the image array the number of items with inS3 = no, and send this number to API
        let presignedURLsRequired = [];
        for (let i = 0; i < imageArray.length; i++) {
            if (imageArray[i].inS3 == "no") {
                presignedURLsRequired.push(imageArray[i].file.type);
            }
        }
        axios.post('/presignedUploadURL', {
            "numberOfPresignedURLsRequired": presignedURLsRequired
        }).then(
            (res) => {
                let imageArrayDump = [...imageArray];
                for (let i = 0; i < imageArrayDump.length; i++) {
                    for (let k = 0; k < res.data.length; k++) {
                        if (imageArrayDump[i].file.type == res.data[k].imageType) {
                            imageArrayDump[i].presignedURL = res.data[k].presignedURL;
                            res.data.splice(k, 1);
                        }
                    }
                }
                console.info(imageArrayDump);
                for (let j = 0; j < imageArrayDump.length; j++) {
                    axios.put(imageArrayDump[j].presignedURL, imageArrayDump[j].file)
                        .then(
                            (res) => {
                                if (res.status == 200) {
                                    imageArrayDump[j].inS3 = "yes";
                                }
                            }

                        )
                        .catch(res => console.info(res));
                }
                for(let l = 0; l < imageArrayDump.length; l++){
                     let newImageSource = imageArrayDump[l].presignedURL.split('?')[0];
                     imageArrayDump[l].imagesource = newImageSource;
                     delete imageArrayDump[l].presignedURL;
                 }
                            
                setImageArray([...imageArrayDump]);
            }
        ).catch(err => console.info(err));

      
    }
    
    return (

        <Box sx = {{ border: 1, display: 'flex', flexWrap: 'wrap', width: '50%', margin: 'auto', minWidth: '570px' }}>
            <Box sx = {{ display: 'flex', border: 1, borderColor: 'red', width: '100%', flexWrap: 'nowrap' }}>

                <Typography sx = {{ border: 1, width: '100%', minHeight: '30px', textAlign: "center" }}>
                    Upload pictures
                </Typography>

                <Icon onClick = {() => alert("hi")} sx = {{ border: 1, cursor: "pointer" }}>
                    <FontAwesomeIcon icon = {faRectangleXmark} />
                </Icon>
            </Box>
            <Box sx = {{ border: 1, width: '49.5%', minHeight: '50px', textAlign: "center", cursor: "pointer" }} onClick = {openFileDialog}><input ref = {inputRef} type = "file" multiple onChange = {inputFilePickerFunc} style = {{ display: 'none' }}></input><Typography>Computer</Typography><Icon><FontAwesomeIcon icon = {faComputer} /></Icon></Box>
            <Box sx = {{ border: 1, width: '49.5%', minHeight: '50px', textAlign: "center" }}><Typography>Facebook</Typography><Icon sx = {{ color: 'blue' }}>FacebookIcon</Icon></Box>

            {

                imageArray.length !== 0 &&
                (
                    imageArray.map(
                        (item, index) => {
                            let itemImgSrc = item.imagesource
                         
                            return (

                                <Tooltip key = {`${itemImgSrc}1`} title = {profilePic == item.imagesource ? "" : "Set as profile pic"}>
                                    <Box key = {`${itemImgSrc}2`} id = {item.imagesource} style = {{ border: profilePic == item.imagesource ? "2px solid red" : "0px" }} onClick = {setImgProfilePic} sx = {{ width: '49.5%', height: "150px", position: 'relative', cursor: "pointer" }}><Icon id = "icon" key = {`${itemImgSrc}3`} sx = {{ position: 'absolute', right: '0px', cursor: "pointer", color: 'red' }} onClick = {(e) => { deleteItem(e, item.imagesource) }}><FontAwesomeIcon icon = {faTrashCan} /></Icon><img src = {item.imagesource} id = {itemImgSrc} index = {index} key = {`${itemImgSrc}4`} style = {{ width: '100%', height: '100%', objectFit: 'contain' }}></img></Box>
                                </Tooltip>

                            )

                        }
                    )
                )

            }

            <Box sx = {{ width: '100%', textAlign: "center", display: displaySaveCancelButton }}><Button variant = "contained" sx = {{ m: 2 }} onClick = {savePics}>Save</Button></Box>
        </Box>
    )
}

🤔 А знаете ли вы, что...
С помощью JavaScript можно валидировать данные на стороне клиента, что улучшает пользовательский опыт.


64
2

Ответы:

Чтобы браузер перезагрузил изображение из нового источника, вы можете использовать подход Очистка кэша. Для этого вам необходимо обновить функцию savePics. Вы можете добавить временную метку к параметру запроса. Проверьте код ниже 👇

const savePics = () => {
    const presignedURLsRequired = imageArray
        .filter((img) => img.inS3 === 'no')
        .map((img) => img.file.type);

    axios.post('/presignedUploadURL', {
        numberOfPresignedURLsRequired: presignedURLsRequired,
    }).then((res) => {
        const imageArrayDump = imageArray.map((img) => {
            const presignedURLObj = res.data.find((urlObj) => urlObj.imageType === img.file.type);
            if (presignedURLObj) {
                axios.put(presignedURLObj.presignedURL, img.file).then((uploadRes) => {
                    if (uploadRes.status === 200) {
                        img.inS3 = 'yes';
                        img.imagesource = `${presignedURLObj.presignedURL.split('?')[0]}?${new Date().getTime()}`;
                    }
                }).catch((error) => console.error(error));
            }
            return img;
        });
        setImageArray([...imageArrayDump]);
    }).catch((err) => console.error(err));
};

Еще одно изменение заключается в том, чтобы каждый элемент изображения имел уникальный ключевой реквизит. Проверьте код ниже: 👇

{imageArray.length !== 0 && (
    imageArray.map((item, index) => (
        <Tooltip key = {`${item.imagesource}_tooltip`} title = {profilePic === item.imagesource ? '' : 'Set as profile pic'}>
            <Box
                key = {`${item.imagesource}_box`}
                id = {item.imagesource}
                style = {{ border: profilePic === item.imagesource ? '2px solid red' : '0px' }}
                onClick = {setImgProfilePic}
                sx = {{ width: '49.5%', height: '150px', position: 'relative', cursor: 'pointer' }}
            >
                <Icon id = "icon" key = {`${item.imagesource}_icon`} sx = {{ position: 'absolute', right: '0px', cursor: 'pointer', color: 'red' }} onClick = {(e) => deleteItem(e, item.imagesource)}>
                    <FontAwesomeIcon icon = {faTrashCan} />
                </Icon>
                <img src = {item.imagesource} id = {item.imagesource} index = {index} key = {`${item.imagesource}_img`} style = {{ width: '100%', height: '100%', objectFit: 'contain' }} />
            </Box>
        </Tooltip>
    ))
)}

Проблема может быть в useEffect без зависимостей. Также обновите useEffect, как показано ниже: 👇

useEffect(() => {
    setDisplaySaveCancelButton(imageArray.length !== 0 ? '' : 'none');
}, [imageArray]);

Решено

Я использовал document.location.reload(), и он работал без обновления страницы. Ссылки были установлены.