React couldn't upload same image twice - reactjs

I have a code that lets me upload images to react-easy-crop package. I also have an "x" button that removes the image so the user can reupload another image. The problem I'm facing now is that when the user removes an uploaded image, they are unable to re-upload the same image.
The code for the upload component:
const onSelectFile = (event: any) => {
if (event.target.files && event.target.files.length > 0) {
if (!allowedFileTypes.some(x => x === event.target.files[0].type || event.target.files[0].size > 10000000) {
setImage('')
setError('Failed to upload. Inccorect size or file type');
}
else {
setError('')
reader.readDataAsDataURL(event.target.files[0]);
reader.addEventListener("load", () => {
setImage(reader.result as string);
});
}
and for the remove button, i did this:
const onRemoveImg = () => {
setImage('')
}

With these few info I have to guess...
Try to use .createObjectURL instead of fileReader, it is synchronous, but it ight help you to debug if that particular issue depends from fileReader being stuck if you try to read the same file twice...
const onSelectFile = (event: any) => {
if (event.target.files && event.target.files.length > 0) {
if (!allowedFileTypes.some(x => x === event.target.files[0].type || event.target.files[0].size > 10000000) {
setImage('')
setError('Failed to upload. Inccorect size or file type');
}
else {
setError('')
const img : string = window.createObjectURL(e.target.files[0])
setImage(img);
});
}

Related

Changing image based on index, using ImageId

I'm using street views from Mapillary.js and depending on an image key, passed as a prop, I want to show different images/street views. I've tried to do it with a conditional (ternary) operator like this:
<Mapillary width="auto" height="94vh" imageId={currentClue === 0 ? '2978574139073965' : currentClue === 1 ? '461631028397375' : currentClue === 2 ? '2978574139073965' : currentClue === 3 ? '312627450377787' : currentClue === 4 ? '695710578427767' : ''} />
Right now though, I only see the first image (when currentClue === 0). When currentClue === 1, the first image is still showing, although I can see in the console that the currentClue index is ascending.
I've also tried to change image by saving imageId in state and using it like this:
const handleClick = () => {
setCurrentClue(currentClue + 1);
if (currentClue < 4) { //* Show alert if clue index > 4
setLevel(level - 1);
if (currentClue === 1) setImageId('461631028397375')
if (currentClue === 2) setImageId('2978574139073965')
} else {
swal('Time to make a guess!', {
button: 'OK'
});
}
dispatch(game.actions.setScore(currentScore - 1));
};
But get the same result: First image is showing, but not the second.
Does anyone have any suggestions on how to get it to work?
This is the whole component where I'm trying to do this:
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { game } from 'reducers/game';
import swal from 'sweetalert';
import { Mapillary } from 'components/Mapillary/Mapillary';
import { Paragraph } from 'GlobalStyles';
import { MapillaryContainer, ClueContainer, SpecialSpan, ClueParagraph, AnotherClueButton } from './Clues.Styles'
export const Clues = () => {
const [games, setGames] = useState([])
const [loading, setLoading] = useState(false);
const [currentClue, setCurrentClue] = useState(0);
const [level, setLevel] = useState(5);
// const [imageId, setImageId] = useState('2978574139073965')
//* Fetching clues
const fetchClues = () => {
setLoading(true);
fetch('https://final-project-api-veooltntuq-lz.a.run.app/games')
.then((response) => {
return response.json()
})
.then((response) => {
setGames(response.games)
})
.catch((error) => console.error(error))
.finally(() => setLoading(false));
}
//* Setting current score
const currentScore = useSelector((store) => store.game.score);
const dispatch = useDispatch();
const handleClick = () => {
setCurrentClue(currentClue + 1);
if (currentClue < 4) { //* Show alert if clue index > 4
setLevel(level - 1);
/* if (currentClue === 1) setImageId('461631028397375')
if (currentClue === 2) setImageId('2978574139073965') */
} else {
swal('Time to make a guess!', {
button: 'OK'
});
}
dispatch(game.actions.setScore(currentScore - 1));
};
useEffect(() => {
fetchClues()
}, [])
const activeClue = games[currentClue];
if (loading) {
return <Paragraph>Loading clues...</Paragraph>
}
if (currentClue < 5) { //* Stop showing clues after clue 5
return (
<div>
<MapillaryContainer>
{console.log(currentClue)}
<Mapillary width="auto" height="94vh" imageId={currentClue === 0 ? '2978574139073965' : currentClue === 1 ? '461631028397375' : currentClue === 2 ? '2978574139073965' : currentClue === 3 ? '312627450377787' : currentClue === 4 ? '695710578427767' : ''} />
</MapillaryContainer>
<ClueContainer>
<SpecialSpan>Level: {level}</SpecialSpan>
<ClueParagraph>{activeClue && activeClue.gameOne}</ClueParagraph>
<AnotherClueButton type="button" onClick={() => handleClick()}>I need another clue</AnotherClueButton>
</ClueContainer>
</div>
)
}
}
EDIT: This is the Mapillary component:
import React, { Component } from 'react';
import { Viewer } from 'mapillary-js';
class ViewerComponent extends Component {
constructor(props) {
super(props);
this.containerRef = React.createRef();
}
componentDidMount() {
this.viewer = new Viewer({
accessToken: this.props.accessToken,
container: this.containerRef.current,
imageId: this.props.imageId
});
}
componentWillUnmount() {
if (this.viewer) {
this.viewer.remove();
}
}
render() {
return <div ref={this.containerRef} style={this.props.style} />;
}
}
export const Mapillary = (props) => {
return (
<ViewerComponent
accessToken={process.env.REACT_APP_MAPILLARY_CLIENT_TOKEN}
imageId={props.imageId}
style={{ width: props.width, height: props.height }} />
);
}
Well, in ViewerComponent you only create the viewer once, on component mount, so subsequent changes to it are not used anywhere.
Now if you read the docs for the imageId option, it states
Optional image-id to start from. The id can be any Mapillary image. If a id is provided the viewer is bound to that id until it has been fully loaded. If null is provided no image is loaded at viewer initialization and the viewer is not bound to any particular id. Any image can then be navigated to with e.g. viewer.moveTo("<my-image-id>").
So, you will have to add a componentDidUpdate(prevProps) to the ViewerComponent and check if the update has a new imageId and act accordingly.
componentDidUpdate(prevProps){
if (prevProps.imageId !== this.props.imageId) {
if (this.viewer) {
this.viewer.moveTo(this.props.imageId);
}
}
}
I think your second method will be work, the error is that you are adding and checking the currentClue value in the same function, your state is not updated, you have to use useEffect it will detect the change of currentClue state and change the image according to your state value, I hope it works.
here is one more thing you just need to handle the dispatch according to your requirements because useEffect will be called once your component load.
const handleClick = () => {
const val = currentClue + 1;
setCurrentClue(val);
};
useEffect(() => {
if (currentClue < 4) { //* Show alert if clue index > 4
setLevel(level - 1);
if (currentClue === 1) setImageId('461631028397375')
if (currentClue === 2) setImageId('2978574139073965')
} else {
swal('Time to make a guess!', {
button: 'OK'
});
}
dispatch(game.actions.setScore(currentScore - 1));
}, [currentClue])

How can I properly debounce a wheel event in ReactJS?

Made a codesandbox of the issue : https://codesandbox.io/s/serene-rosalind-fcitpd?file=/src/Photo.tsx
The real problem comes when you change scroll direction, the index photo will glitch a bit ...
I have a photo gallery:https://virgile-hasselmann.vercel.app/photos. I want to switch the photos when the user scrolls up or down. To do so I thought I'd use wheelEvent and the e.deltaY. It kinda works but I had to add a useDebounce function because if not it would glitch the gallery. Here you can see the custom hook :
function useDebounce<T>(value: T, delay?: number) {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
const setDebounce = (newValue: T) => {
setTimeout(() => setDebouncedValue(newValue), delay || 500);
};
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay || 500);
return () => {
clearTimeout(timer);
};
}, [value, delay]);
return { debouncedValue, setDebounce };
}
In the Gallery component, here is how I've implemented the wheel event and the debouncing :
// Init the data to display with the photos of the first category
const [photoIdx, setPhotoIdx] = useState(0);
const { debouncedValue, setDebounce } = useDebounce(photoIdx, 1000);
const setDebouncedIdx = (value: number) => {
setDebounce(value);
setPhotoIdx(debouncedValue);
};
const handleWheel = (e: WheelEvent<HTMLDivElement>) => {
if (e.deltaY > 0) {
if (photoIdx < dataSelected!.length - 1) {
setDebouncedIdx(photoIdx + 1);
} else {
setDebouncedIdx(0);
}
} else {
if (photoIdx > 0) {
setDebouncedIdx(photoIdx - 1);
} else {
setDebouncedIdx(dataSelected!.length - 1);
}
}
};
But the result does not satisfy me the least: it's glitchy and not really responding the user's input + if you try to go back by scrolling back, it will first show you the next photo before going back to it. Hard to explain word but it's quite clear if you look at the index at the bottom left corner.
Perhaps there is a better way of implementing it. The problem is unclear to me if anyone could enlighten me that would much appreciated :)
I think you can use an eventListener to detect if the user is scrolling. You can detect the direction of the scrolling too. So if the user scrolls down then you replace the image with the next one. Or scroll up then replace it with the previous one.
The threshold is used to determine how much you want to scroll before taking an action.
useEffect(() => {
//Scroll listener to determine the scroll direction
const threshold = 0;
let lastScrollY = window.scrollY;
let scrollDir = "";
const updateScrollDir = () => {
const scrollY = window.scrollY;
if (Math.abs(scrollY - lastScrollY) < threshold) {
return;
}
scrollDir = (scrollY > lastScrollY ? "scrolling down" : "scrolling up");
lastScrollY = scrollY > 0 ? scrollY : 0;
if (scrollDir == "scrolling up") {
//Do something to display the previous image
}
if (scrollDir == "scrolling down") {
//Do something to display the next image
}
};
const onScroll = () => {
window.requestAnimationFrame(updateScrollDir);
};
window.addEventListener("scroll", onScroll);
return () => {
window.removeEventListener("scroll", onScroll);
}
},

React Dropzone Uploader S3

we are using react dropzone uploader to upload video to s3 uisng presigned URL.
Below is the code:-
const MyUploader = () => {
const [snackBarOpen, setSnackBarOpen] = React.useState(false);
const [snackbarTitle, setSnackbarTitle] = React.useState("");
const handleClose = (event, reason) => {
if (reason === "onclick") {
return;
}
setSnackBarOpen(false);
}
const getUploadParams = async ({ meta: { name } }) => {
const { fields, uploadUrl, fileUrl } = await getPresignedUploadParams(name)
console.log(fields, uploadUrl, fileUrl)
return { fields, meta: { fileUrl }, url: uploadUrl }
}
// called every time a file's `status` changes
const handleChangeStatus = ({ meta, file }, status) =>
{
console.log(status)
if(status == "done") {
setSnackbarTitle("File uploaded Successfuly")
setSnackBarOpen(true)
window.location.reload(false);
}
else if (status == "error_upload") {
setSnackbarTitle("error!!Please check")
setSnackBarOpen(true)
}
else if (status == "uploading") {
setSnackbarTitle("Uploading..Please wait don't close or refresh")
setSnackBarOpen(true)
}
else {
setSnackbarTitle(status)
setSnackBarOpen(true)
}
}
return (
<div>
<Dropzone
getUploadParams={getUploadParams}
onChangeStatus={handleChangeStatus}
accept="video/*"
/>
<SnackBar
open={snackBarOpen}
close={handleClose}
snackbarTitle={snackbarTitle}
/>
</div>
)
}
we are getting below error
we try to find to find similar question in SO and found The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256
As per above post have set signature version and region_name in backend.
<Error
>
<Code
>
InvalidRequest
</Code
>
<Message
>
The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256.
</Message
>
<RequestId
>
5AZHFFW9Y080TTV5
</RequestId
>
<HostId
>
DzzSjYtIP73RoKN/UrNX5hgCOxJIMfwfE9HAuRlZrLKmK82n+ssBdf6jMUfbNXp4znfOX3LfrMU=
</HostId
>
</Error>
we have set signature version in backend.
client = boto3.client(
's3',
aws_access_key_id=Settings().AWSAccessKeyId,
aws_secret_access_key=Settings().AWSSecretKey,
config=Config(signature_version='s3v4'),
region_name="ap-south-1"
)
It could be great if we can get help to resolve this.

React Map function iterates through an array only once

I have an image input tag which is multiple. I want each image to be scaled down. I use the map function for this. I call this within a useEffect function. But now it is the case that the map function is only run through once, no matter how many images are in an array. How can I change this ?
const articelImg = (e) => {
if (e.target.files && e.target.files.length > 0) {
setFiles([...files, e.target.files]);
}
};
useEffect(() => {
files.length &&
files.map(async (file, idx) => { //Two objects, but only interred once
const thump = await thumpnail(file[idx]);
setThumpnails([...thumpnails, thump]);
});
}, [files]);
when you are working with async/await code in a loop best approach is to use for of loop, below is the code you can give it a try
const articelImg = (e) => {
if (e.target.files && e.target.files.length > 0) {
setFiles([...files, e.target.files]);
}
};
useEffect(() => {
(async () => if (files.length) {
for await (let file of files){
const thump = await thumpnail(file[idx]);
setThumpnails([...thumpnails, thump]);
}
})()
}, [files]);
You have probably got a stale state in this line setThumpnails([...thumpnails, thump]); because of async settings. Try this one setThumpnails(thumpnails => [...thumpnails, thump]); this will provide you a latest snapshot of state
or use refs as described in docs.

use Effect not being called as expected

I am trying to implement a simple file upload drop zone in React:
import { useState, useEffect } from 'react';
import './App.css';
const App = () => {
const [isDropzoneActive, setIsDropzoneActive] = useState(false);
const [files, setFiles] = useState([]);
const [currentChunkIndex, setCurrentChunkIndex] = useState(null);
const handleDragOver = e => {
e.preventDefault();
setIsDropzoneActive(true);
};
const handleDragLeave = e => {
e.preventDefault();
setIsDropzoneActive(false);
};
// Update the files array
const handleDrop = e => {
e.preventDefault();
setIsDropzoneActive(false);
// Just overwrite for this simple example
setFiles(e.dataTransfer.files);
};
// Monitor the files array
useEffect(() => {
if (files.length > 0) {
console.log('got a file');
setCurrentChunkIndex(0);
}
}, [files]);
// Monitor the chunk index
useEffect(() => {
if (currentChunkIndex !== null) {
readAndUploadCurrentChunk();
}
}, [currentChunkIndex]);
const readAndUploadCurrentChunk = () => {
// Implement later
};
return (
<div
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
className={"dropzone" + (isDropzoneActive ? " active" : "")}
>
{files.length > 0 ? 'Uploading' : 'Drop your files here'}
</div>
);
}
export default App;
However it seems that the effect that monitors [currentChunkIndex] is not being called correctly. I have attempted to drag files into the drop zone, one by one. [files] effect it called correctly each time but the effect on [currentChunkIndex] doesn't get called. What am I doing wrong here?
currentChunkIndex changes from null to 0, you set it only to 0.
useEffect(() => {
if (files.length > 0) {
console.log('got a file');
setCurrentChunkIndex(files.length);
}
}, [files]);

Resources