Мое приложение реагирования позволяет пользователю загружать фотографии из своей системы и 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 можно валидировать данные на стороне клиента, что улучшает пользовательский опыт.
Чтобы браузер перезагрузил изображение из нового источника, вы можете использовать подход Очистка кэша. Для этого вам необходимо обновить функцию 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]);