I'm using dropzone in my Next JS app. and i'm facing an issues. one of them is i can't prevent image duplication correctly.
what is did is i created a useState() to check if element exists.
const [found, setFound] = useState(false);
and on onDrop() method i checked if the dropped file exists in the stored files. (i haven't figured out to check duplication on multiple image drag). it works but not correctly.
const [files, setFiles] = useState([]);
const [found, setFound] = useState(false);
const { getRootProps, getInputProps } = useDropzone({
accept: {
"image/*": [],
},
onDrop: (acceptedFiles) => {
acceptedFiles.filter(
(el, idx) =>
idx ===
files.findIndex((elm) => elm.name === el.name && setFound(true))
);
if (!found) {
setFiles((prev) => [
...prev,
...acceptedFiles.map((file) =>
Object.assign(file, {
preview: URL.createObjectURL(file),
})
),
]);
}
},
});
the issue with my code is that if i insert images and drag another duplicated image it stores that. but on the next drag it doesn't add the image, whether it is duplicated or not. if it's duplicated again then the next drag won't work either and the first drag after the duplicated drag doesn't work too. but the next non duplicate drag will work.
i think this is happening b/c the condition is checked after the drag is completed and file is added.
how can i drag and drop images correctly by not passing duplicated images in single or multiple drags?
if it's not much to ask can anyone show me to make a limitations to the images that are being dragged or inserted? limit is between 4 and 9.
Thank You in Advance
Related
I'm currently building a React component in which user can upload images. In this simplified example, I want to upload 3 Images. When the upload of each file finishes, I want to add this file to my list of uploaded files.
Problem: Because the setter method of useState is not synchronous, we only add one of three uploaded files to the list. The other two additions become overwritten by the last uploading file.
Here the example:
const Example = () => {
const defaultValues = ["aaa.jpg", "bbb.jpg", "ccc.jpg"];
const [uploadedFiles, setUploadedFiles] = React.useState([...defaultValues]);
const uploadFunction = () => {
//images I want to upload
const selectedImagesToUpdate = ["ddd.jpg", "eee.jpg", "fff.jpg"];
//iterate over list of images, which I want to upload
selectedImagesToUpdate.forEach(item => {
//upload image
uploadImage(item).then(response => {
//on response, add uploaded item to myList
console.log("add item ", response);
setUploadedFiles([...uploadedFiles, response]); //<-- here is my problem
})
});
}
const uploadImage = (image) => {
//blackbox for uploading an image async with axios like:
//return axios.post(url, image);
return Promise.resolve(image);
}
return (
<div>
<p>UploadedFiles:{JSON.stringify(uploadedFiles)}</p>
<button onClick={uploadFunction}>Upload</button>
<button onClick={() => setUploadedFiles([...defaultValues])}>Reset</button>
</div>
);
};
// Render it
ReactDOM.createRoot(document.getElementById("root"))
.render(<Example title = "Example"/>);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Here the result:
["aaa.jpg","bbb.jpg","ccc.jpg","fff.jpg"]
What I want as a result:
["aaa.jpg","bbb.jpg","ccc.jpg","ddd.jpg","eee.jpg","fff.jpg"]
That sounds like a common use case.
How can I fix this? How should you handle multiple setStates?
Ideas for a much better way to handle an upload of multiple files? In my use case I have to delete some selected files and upload some selected files on AWS S3 server, if a user clicks on a submit button.
Thanks
Use the callback version of the setter.
Currently this adds an element to whatever the state array was when the component rendered:
setUploadedFiles([...uploadedFiles, response]);
But if multiple state updates can be batched/queued, then each of them would use that same initial value. Instead, the callback version provides whatever the current state value is in that batch/queue:
setUploadedFiles(prev => [...prev, response]);
I'd like to listen to the load event of my images. And here's how I'm trying to.
export const Picture: FC<PictureProps> = ({ src, imgCls, picCls, lazy, alt: initialAlt, onLoad, onClick, style }) => {
const alt = useMemo(() => initialAlt || generator.next().value, [src, initialAlt]);
const { publicRuntimeConfig } = getConfig();
return (
<picture style={style} className={picCls}>
{!publicRuntimeConfig.dev && <source srcSet={`${src}.webp`} type="image/webp"/>}
<img onLoad={onLoad} className={imgCls} alt={alt} src={src} loading={lazy ? 'lazy' : 'eager'}
onClick={onClick} />
</picture>
);
};
As I understand, this code should work fine. But when images are cached, they don't trigger load event from time to time.
I need to know, when all my images are loaded. To do this I checked the amount of loaded images. But it doesn't work when at least 1 of images didn't trigger the event. error event doesn't fire as well, and I can see that all of images are always loaded in the debugger tools.
What should I do?
I know I can add something like src?_dc=timestamp, but I do need caching, it really speeds up re-upload.
UPD: All of the loaded images returns 304 status. But the amount of images, that triggers load differs from 0 to total amount
Okay, this is not exactly what I asked for, but I found one thing that can help me - the completed field
export const Picture: FC<PictureProps> = ({ src, imgCls, picCls, lazy, alt: initialAlt, onLoad, onClick, style }) => {
const alt = useMemo(() => initialAlt || generator.next().value, [src, initialAlt]);
const ref = useRef<HTMLImageElement>();
const onLoadTriggered = useRef(false);
const { publicRuntimeConfig } = getConfig();
const onLoadFunc = useCallback(() => {
!onLoadTriggered.current && onLoad?.();
onLoadTriggered.current = true;
}, [onLoad]);
useEffect(() => {
ref.current.complete && onLoadFunc();
}, []);
return (
<picture style={style} className={picCls}>
{!publicRuntimeConfig.dev && <source srcSet={`${src}.webp`} type="image/webp"/>}
<img onLoad={onLoadFunc} className={imgCls} alt={alt} src={src} loading={lazy ? 'lazy' : 'eager'}
ref={ref} onClick={onClick} />
</picture>
);
};
This code did solve my problems, but it seems like it's a crutch. So, I've decided to leave this solution here, but I'm still waiting for the correct answer
this issue occurs because always it fetch images from cache
please add timestamp after image URL
you can do it from both side
From Backend (DB side)
for example :
#ImagePath+ REPLACE(ImagePath, '~', '') + '?time='+FORMAT(getdate(),'yyyyMMddHHmmssffff')
in IONIC code (Typescript)
for example :
npm i moment --save
import * as moment from moment
const dateStringThatWorksForIonicDatepicker = moment.unix(1536883200000).format(‘YYYY-MM-DD’)
With draft-js and a styled component, I made an inline input intended for a calculator. When I type into it with a keyboard, it works as expected:
When I press the plus button, a "+" is added to the text, but the view doesn't scroll:
Here's the behavior and the code in a codesandbox:
https://codesandbox.io/s/charming-brattain-mvkj3?from-embed=&file=/src/index.js
How can I get the view to scroll when text is added programmatically like that?
At long last, I figured out a solution. I added this to the calculator input component:
const shell = React.useRef(null);
const [scrollToggle, setScrollToggle] = React.useState(
{ value: false }
);
React.useEffect(() => {
const scrollMax = shell.current.scrollWidth - shell.current.clientWidth;
shell.current.scrollLeft += scrollMax;
}, [scrollToggle]);
const scrollToEnd = () => {
setScrollToggle({ value: !scrollToggle.value });
};
Then at the end of the insertChars function I added scrollToEnd();.
And I set ref={shell} on <InputShell>
I am trying to render an array of elements in react so that when you hover over an element it then filters the array of elements and re-renders with just the hovered element. Keeping position is buggy but not the worry for now.
It works but when the hover element is rendered it has no content
const [show, setShow] = useState(false);
const cards = [{key:"1",title:"elem1",content:"..."},{key:"2",title:"elem2",content:"..."},{key:"3",title:"elem3",content:"..."}]
const tempCards = []
My hover function returns
tempCards = [{key:"1",title:"elem1"}];
/// temp function allowing to work from button click
const changeShow = () => {
tempCards = cards.filter(item => item === cards[0]);
console.log(tempCards[0])
setShow(!show)
}
This is all working as it should but then when the element loads on the page no content shows
{show ? cards.map((item)=> {
return <CardSpinLeft
onClick={toggle}
key={item.key}
id={item.key}
image={item.img}
name={item.title}
title={item.title}
paragraph={item.content}
/>
}):
<CardSpinLeft
onClick={toggle}
key={tempCards.key}
id={tempCards.key}
image={tempCards.img}
name={tempCards.title}
title={tempCards.title}
paragraph={tempCards.content}
/>
}
not receiving any error's I even tried making the filter function async/await thinking its a loading issue. Only thing I can think is that react is pre-loading the content before it is there?
i'm trying to develop an App with React using the Open trivia Api. I have mapped a button component (using material ui) to show the different answers for each question. I'm struggling now to target only the clicked one to apply a css property: if the answer is correct should become green, else red. The problem is the fact that once i click, all button become red or green. I tried to store the index in a state and compare the real index, but it doesn't work. here is my code:
in the main APP.js
const [clickedOne, setClickedOne] = useState({
clickedIndex: null,
});
useEffect(() => {
grabData();
}, []);
const handleClick = (choice, ke) => {
setChoice(choice);
if (choice === data.correct_answer) {
setIsCorrect(true);
} else {
setIsCorrect(false);
}
setClickedOne({ clickedIndex: ke });
grabData();
};
The mapped button inside the Render:
{answers.map((answer, index) => {
return (
<ContainedButtons
choice={handleClick}
answer={answer}
correct={data.correct_answer}
isCorrect={isCorrect}
key={index}
id={index}
clicked={clickedOne}
/>
);
})}
Inside the Button component:
const backStyle = () => {
if (clicked === id) {
if (isCorrect) {
return "green";
} else if (isCorrect === false) {
return "red";
} else {
return null;
}
}
};
return (
<div className={classes.root}>
<Button
style={{ backgroundColor: backStyle() }}
value={answer}
onClick={() => choice(answer, id)}
variant="contained"
>
{decodeURIComponent(answer)}
</Button>
When i check now inside the backstyle function if the clicked===id, now nothing happens anymore. Without that if check, i would have all buttons red or green.
Thank you guys for the help!
I have looked at your codesandbox demo, there are alot of other problems apart from the one your question is about.
First of all, each time you make a request to the API to fetch next question, you are making a request to get 10 questions instead of 1. API request URL contains a query parameter named amount which determines how many questions will be fetched on each request. Change its value to 1.
"https://opentdb.com/api.php?amount=1&encode=url3986"
Secondly, there is a lot of unnecessary code and unnecessary use of useState hook. You only need 2 things to be stored in the state, data and answers
const [data, setData] = useState({});
const [answers, setAnswers] = useState([]);
Now, coming to the original problem of detecting which button is clicked and correctly updating its background color.
To achieve the desired functionality, take following steps:
create couple of CSS classes as shown below
button.bgGreen {
background-color: green !important;
}
button.bgRed {
background-color: red !important;
}
pass a handleClick function from App component to ContainedButtons component. When a button is clicked, this click handler will be invoked. Inside the handleClick function, get the text and the button that was clicked using Event.target and depending on whether user answered correctly or not, add appropriate CSS class, created in step 1, on the button that was clicked.
Instead of using index as key for ContainedButtons in map function, use something that will be unique each time. This is needed because we want React to not re-use the ContainedButtons because if React re-uses the ContainedButtons component, then CSS classes added in step 2 will not be removed from the button.
Here's a working codesanbox demo of your app with the above mentioned steps.
In this demo, i have removed the unnecessary code and also changed the key of ContainedButtons inside map function to key={answer.length * Math.random() * 100}. You can change it to anything that will ensure that this key will be unique each time.