how can I save image and video file in redux - javascript

I am preparing a form that has fields to allow the user to post his picture and a video about her/him(self). To post a picture I prepared a component based on input type file, but to upload a video I have used React Dropzone. Now I want to save this to redux, however when I try to do that redux is complaining that it is the non-serializable item, and when I put it into JSON.Stringify() redux is getting an empty object. What would be the optimal solution for that, I have to store it somewhere in-state, should it be a local state created in this step of the form (fortunately it is the last one) however if the user would like to go back to any previous step and come back this data will be lost
Please advise, the dropzone component is below, onResult is just a handler that takes the value and dispatches an action to redux
import React, { useCallback, useState } from "react";
import { useDropzone } from "react-dropzone";
export const FileDropzone = ({ onResult, fileTypes, maxFiles }) => {
const [progress, setProgress] = useState(0);
const [error, setError] = useState(false);
const onDrop = useCallback(acceptedFiles => {
acceptedFiles.forEach(file => {
const reader = new FileReader();
reader.onerror = () => setError(true);
reader.onprogress = data => {
if (data.lengthComputable) {
var progress = parseInt(
(data.loaded / data.total) * 100,
10
);
setProgress(progress);
}
};
reader.onloadend = () => onResult(reader.result);
reader.readAsArrayBuffer(file);
});
}, []);
const { getRootProps, getInputProps } = useDropzone({
onDrop,
maxFiles,
accept: fileTypes
});
return (
<>
<div
className={`border border-${
error ? "danger" : "light"
} rounded d-flex justify-content-center align-items-center hpx-100`}
{...getRootProps()}
>
<input {...getInputProps()} />
<p>Proszę kliknąć, lub upuścić wybrany plik.</p>
</div>
<div
className="bg-primary hpx-20 mt-1"
style={{ width: `${progress}%` }}
></div>
</>
);
};
Thank you

I think your best bet would be to convert the file to Base64.
Please check How to convert file to base64 in JavaScript?
and
https://github.com/reduxjs/redux/issues/2276

Related

Why does my component fail to construct Url?

I'm fairly new to React development and hope someone can help me with this problem. I'm coding along with a YouTube video https://www.youtube.com/watch?v=XtMThy8QKqU&t=10138s (2:55:00 shows what it is supposed to do)and for some reason I can't find the mistake I'm making. When I test my app on localhost the window in which the trailer is supposed to play is only displayed when I click certain movie covers but not when I click on others. my other problem is that it will never actually play a trailer. The console displays the error you can hopefully see here [1]: https://i.stack.imgur.com/vC6Sh.jpg
import movieTrailer from "movie-trailer";
import React, { useEffect, useState } from "react";
import YouTube from "react-youtube";
import axios from "./axios";
import "./Row.css"
const base_url = "https://image.tmdb.org/t/p/original/";
function Row({ title, fetchUrl, isLargeRow }) {
const [movies, setMovies] = useState([]);
const [trailerUrl, setTrailerUrl] = useState("");
//A snippet of code which runs based on a specific condition or variable
useEffect(() => {
// if brackets are blank [] it means run once when row loads, and don't run again
async function fetchData() {
const request = await axios.get(fetchUrl);
// console.log(request.data.results);
setMovies(request.data.results)
return request;
// async function fetchData() {
// try{
// const request = await axios.get(fetchUrl);
// console.log(request);
// return request;
// }
// catch (error){
// console.log(error);
// }
}
fetchData();
}, [fetchUrl]);
const opts = {
height: '390',
width: '100%',
playerVars: {
// https://developers.google.com/youtube/player_parameters
autoplay: 1,
},
};
//console.log(movies);
const handleClick = (movie) => {
if (trailerUrl){
setTrailerUrl('');
} else {
movieTrailer(movie?.name || "")
.then ((url) => {
const urlParams = new URLSearchParams(new URL(url).search);
setTrailerUrl(urlParams.get("v"));
}).catch(error => console.log(error));
}
};
return(
<div className="row">
<h2>{title}</h2>
<div className="row__posters">
{movies.map(movie => (
<img
key={movie.id}
onClick={() => handleClick(movie)}
className= {`row__poster ${isLargeRow && "row__posterLarge"}`}
src={`${base_url}${isLargeRow ? movie.poster_path : movie.backdrop_path}`} alt={movie.name}/>
))}
</div>
{trailerUrl && <YouTube videoId="{trailerUrl}" opts={opts} /> }
</div>
)
}
export default Row
Invalid property name in movie
Taking a look at the tmdb docs it will show you what the properties of each object has. In this case, there is no name. Try using something like movie.title
In your handleClick() function you could use movie?.title.
Trying to use movie.name will give back a null value. Which errors out movieTrailer() and you get no YouTube url back.
Create handle function like this and the call it in your return function and use however you want... mainly should be used by using onClick method

Updating react state inside react-query onSuccess event causes a memory leak

I have component called ImageDisplay. And that component uses a custom react hook (useImageUpload) to upload images to cloudinary using react-query.
The custom hook useImageUpload contains a state which stores the urls of uploaded images in an array. The custom hook returns an object which contains images urls array, a function to upload new images, and isLoading boolean.
The image is being uploaded successfully. But looks like, this react-query snippet is causing memory leak.
onSuccess: ({ data }) => {
console.log("Image uploaded successfully");
setImages([...images, data.secure_url]);
},
How can I update the images array on onSuccess event?
ImageDisplay.js
import React, { useEffect } from "react";
import "styles/ImageDisplay.scss";
import { Field } from "formik";
import Skeleton from "react-loading-skeleton";
import "react-loading-skeleton/dist/skeleton.css";
import useImageUpload from "utils/useImageUpload";
export default function ImageDisplay() {
let { images, uploadImage, uploading } = useImageUpload();
const handleFileChange = (e) => {
const inputFile = e.target.files[0];
uploadImage(inputFile);
};
useEffect(() => {
console.log(images);
}, [images]);
return (
<>
<div className="image-display border mb-3 px-3">
<div className="image-preview text-center">
<h6>Upload product images to attract customers</h6>
{/* <Skeleton width={300} height={300} /> */}
</div>
<hr />
<div className="img-choose border p-2 ">
<div className="options">
<Skeleton width={100} height={100} />
<Skeleton width={100} height={100} />
<Skeleton width={100} height={100} />
<Skeleton width={100} height={100} />
<Skeleton width={100} height={100} />
<Skeleton width={100} height={100} />
</div>
</div>
.
</div>
<div className="img-input">
<Field
type="file"
name="productImages"
id="productImages"
onChange={handleFileChange}
accept="image/*"
/>
<h5>Select product images to upload</h5>
</div>
</>
);
}
useImageUpload.js
import axios from "axios";
import { useState } from "react";
import { useMutation } from "react-query";
const useImageUpload = () => {
const [images, setImages] = useState([]);
let { isLoading: uploading, mutateAsync: uploadImage } = useMutation(
async (data) => {
let formData = new FormData();
formData.append("file", data);
formData.append("upload_preset", "uploadPresetHere");
formData.append("cloud_name", "nameHere");
return await axios.post(
"cloudinary_url",
formData,
{
withCredentials: false,
}
);
},
{
onSuccess: ({ data }) => {
console.log("Image uploaded successfully");
setImages([...images, data.secure_url]);
},
onError: (error) => {
console.log(error.response);
},
}
);
return { images, uploadImage, uploading };
};
export default useImageUpload;
Sorry if I have made any mistake in the question.
This is a normal behaviour of React Query : if a component performing a mutation using useMutation unmounts, the onSuccess callback still gets called.
To fix it you have several options, you can :
Keep track of the mounted / unmounted state of the component in a ref :
const mounted = useRef(true)
useEffect(() => {
mounted.current = true
() => mounted.current = false
})
let { ... } = useMutation(
async (data) => {...},
{
onSuccess: ({ data }) => {
if (mounted.current) {
setImages([...images, data.secure_url]);
}
}
}
);
Not sure it works in all cases though, normally React deletes refs when unmounting a component.
Another solution is to store the images in the React Query cache. After all it's data coming from the server, so it's server state and should reside in the cache :
let { ... } = useMutation(
async (data) => {...},
{
onSuccess: ({ data }) => {
queryClient.setQueryData('images', prev => [...prev, data])
}
}
);
As stated in this answer, the callbacks on useMutation are always called, even if your component unmounts. However, you can use the additional callback on .mutate(), as these callbacks will only be called if the component is still mounted when the mutation finishes. This is documented here.
Further, this warning is a red hering, and the warning will be removed in React 18, as documented by the react team in the React 18 working group. It is stated that calling setState in an already unmounted component is not in fact a memory leak and is a totally fine thing to do, while the proposed workaround with a ref is actually worse than the original problem (which was forgetting to unsubscribe in useEffects when subscribing to external stores - something that can happen in libraries, but is very unlikely to happen for app developers).

React-Webcam Recording, Replay, Re-recording, Storing

Just making a video recording component that you can replay afterwards to see if you like it, if not you just re-record, and finally it will store it in the database. I'm using react-webcam for this, with some functionality I've found online.
I have a handleDownload function which sets the video tag source with the blob I just recorded. Originally it downloaded the video file when clicking the button, but I want the video to be replayable as soon as I stop a recording. Ideally, I want to use the same react-webcam component, but not sure I can do that, so for now this will do.
It works when I set the function to onClick listener to the button, however, it doesn't work when I call the function inside of handleStopCaptureClick
so I tried to implement a useEffect which causes handleDownload to run after we stop capturing. This doesn't work either - thoughts? Thanks!
import React, {useEffect} from "react";
import Webcam from "react-webcam";
export const WebcamStreamCapture = () => {
const webcamRef = React.useRef(null);
const mediaRecorderRef = React.useRef(null);
const [capturing, setCapturing] = React.useState(false);
const [recordedChunks, setRecordedChunks] = React.useState([]);
const isInitialMount = React.useRef(true);
useEffect(() => {
if (isInitialMount.current) {
isInitialMount.current = false;
} else {
if (!capturing) {
console.log('running handleDownload')
handleDownload();
}
}
}, [capturing])
const handleStartCaptureClick = React.useCallback(() => {
setCapturing(true);
mediaRecorderRef.current = new MediaRecorder(webcamRef.current.stream, {
mimeType: "video/webm"
});
mediaRecorderRef.current.addEventListener(
"dataavailable",
handleDataAvailable
);
mediaRecorderRef.current.start();
}, [webcamRef, setCapturing, mediaRecorderRef]);
const handleDataAvailable = React.useCallback(
({ data }) => {
if (data.size > 0) {
setRecordedChunks((prev) => prev.concat(data));
}
},
[setRecordedChunks]
);
const handleStopCaptureClick = React.useCallback(() => {
mediaRecorderRef.current.stop();
setCapturing(false);
}, [mediaRecorderRef, webcamRef, setCapturing]);
const handleDownload = React.useCallback(() => {
if (recordedChunks.length) {
const blob = new Blob(recordedChunks, {
type: "video/webm"
});
const url = URL.createObjectURL(blob);
const video = document.getElementById("video-replay");
video.src = url
}
}, [recordedChunks]);
return (
<div className="d-flex flex-column align-items-center">
<Webcam audio={false} ref={webcamRef} height={400} width={500}/>
<video id="video-replay" height="400" width="500" controls></video>
{capturing ? (
<button className="btn btn-danger" onClick={handleStopCaptureClick}>Stop Capture</button>
) : (
<button className="btn btn-danger" onClick={handleStartCaptureClick}>Start Capture</button>
)}
{recordedChunks.length > 0 && (
<div>
<button onClick={handleDownload}>Download</button>
</div>
)}
</div>
);
};
Possible Solution
So I caught myself thinking, if the chunks aren't appearing/working during the useEffect either, it must mean that when capturing stops in handleStopCaptureClick it takes the state a while to update, including chunks I suppose. By changing the dependency from 'capturing' to 'recordedChunks' in useEffect, I was successful in making the video appear right after you stop recording.
Solution: By changing the dependency from 'capturing' to 'recordedChunks' in useEffect, I was successful in making the video appear right after you stop recording.

Is there any way to auto crop the face after doing face detection with face-api.js?

I've implemented face-API in my react project which is detecting a single face with detectSingleFace from the picture.
Now I want to move one step further. I want face-api to auto-crop the face after detection. So, I can store it in some server, state or local storage. Is there any way to do so?
Here you can see a screenshot example I want to achieve One side is a picture another side is the auto cropped face(which I want to implement).
Here is my live code link in codesandbox
Below is my code module for face-api
PhotoFaceDetection.js
import React, { useState, useEffect, useRef } from "react";
import * as faceapi from "face-api.js";
import Img from "./assets/mFace.jpg";
import "./styles.css";
const PhotoFaceDetection = () => {
const [initializing, setInitializing] = useState(false);
const [image, setImage] = useState(Img);
const canvasRef = useRef();
const imageRef = useRef();
// I want to store cropped image in this state
const [pic, setPic] = useState();
useEffect(() => {
const loadModels = async () => {
setInitializing(true);
Promise.all([
// models getting from public/model directory
faceapi.nets.tinyFaceDetector.load("/models"),
faceapi.nets.faceLandmark68Net.load("/models"),
faceapi.nets.faceRecognitionNet.load("/models"),
faceapi.nets.faceExpressionNet.load("/models")
])
.then(console.log("success", "/models"))
.then(handleImageClick)
.catch((e) => console.error(e));
};
loadModels();
}, []);
const handleImageClick = async () => {
if (initializing) {
setInitializing(false);
}
canvasRef.current.innerHTML = faceapi.createCanvasFromMedia(
imageRef.current
);
const displaySize = {
width: 500,
height: 350
};
faceapi.matchDimensions(canvasRef.current, displaySize);
const detections = await faceapi.detectSingleFace(
imageRef.current,
new faceapi.TinyFaceDetectorOptions()
);
const resizeDetections = faceapi.resizeResults(detections, displaySize);
canvasRef.current
.getContext("2d")
.clearRect(0, 0, displaySize.width, displaySize.height);
faceapi.draw.drawDetections(canvasRef.current, resizeDetections);
console.log(
`Width ${detections.box._width} and Height ${detections.box._height}`
);
setPic(detections);
console.log(detections);
};
return (
<div className="App">
<span>{initializing ? "Initializing" : "Ready"}</span>
<div className="display-flex justify-content-center">
<img ref={imageRef} src={image} alt="face" crossorigin="anonymous" />
<canvas ref={canvasRef} className="position-absolute" />
</div>
</div>
);
};
export default PhotoFaceDetection;
After doing a lot of R&D I figured it out. For future readers who may face an issue here is the guide.
I've created another function that will get the original image reference and the bounded box dimension i.e. width and height. After that, I've used faceapi method to extract faces and then with the help of the toDataURL method I actually converted it to base64 file which can be rendered to any image src or can be stored anywhere.
This is the function I was explaining above
async function extractFaceFromBox(imageRef, box) {
const regionsToExtract = [
new faceapi.Rect(box.x, box.y, box.width, box.height)
];
let faceImages = await faceapi.extractFaces(imageRef, regionsToExtract);
if (faceImages.length === 0) {
console.log("No face found");
} else {
const outputImage = "";
faceImages.forEach((cnv) => {
outputImage.src = cnv.toDataURL();
setPic(cnv.toDataURL());
});
// setPic(faceImages.toDataUrl);
console.log("face found ");
console.log(pic);
}
}
Then I call the above function inside my main function where I used faceapi face detection tiny model.
extractFaceFromBox(imageRef.current, detections.box);
You can also visit live code here to check complete implementation

SetState not rendering the component

So I am working on my React app and a one point i need to upload some files. So I simply used the input file to make it work. I set its display none as I wanted my attachment icon to be clicked when wanting to upload the file.
Problem : Using the ref method, everything is working fine except one thing and that is below in my hangleFileChange function, when the setFiles() set the file variable, the component is not rendered and I do not see the file array. but if I do the file saving simply like
setFile(event.target.files[0])
I can see the rendering . But with the below code, the component is not rendering
import React, { useRef, useState } from "react";
const App = () => {
const fileInput = useRef(null);
const [file, setFile] = useState([]);
const handleClick = () => {
fileInput.current.click();
};
const handleFileChange = (event) => {
console.log("Make something");
let newFiles = file;
newFiles.push(event.target.files[0]);
console.log(newFiles);
setFile(newFiles);
};
// This should run on every render
console.log("the files array is ", file);
return (
<div className="patientactions-container">
<input
type="file"
style={{ display: "none" }}
onChange={(e) => handleFileChange(e)}
ref={fileInput}
/>
<div onClick={() => handleClick()}>clck</div>
</div>
);
};
export default App;
Please help.
Sandbox : https://codesandbox.io/s/kind-breeze-czc3w?file=/src/App.js:0-692
Try this version
const handleFileChange = (event) => {
console.log("Make something");
// Set the ne variable to an array, not file
let ne = [];
ne.push(event.target.files[0]);
// then set it equals file.
ne = file;
console.log(ne);
console.log(file);
setFile(file);
};
You can fix the code like this below.
import React, { useRef, useState } from "react";
const App = () => {
const fileInput = useRef(null);
const [file, setFile] = useState(null);
const handleClick = () => {
fileInput.current.click();
};
const handleFileChange = (nfile) => {
console.log("Make something");
if (file == null) setFile([nfile]);
else setFile([...file, nfile]);
};
console.log("the files array", file);
return (
<div className="patientactions-container">
<input
type="file"
style={{ display: "none" }}
onChange={(e) => handleFileChange(e.target.files[0])}
ref={fileInput}
/>
<div onClick={() => handleClick()}>clck</div>
</div>
);
};
export default App;
I'd have put this in the comments but my rep is not high enough.
I had a problem with rendering changes to an array, because arrays use pointers it did not "register" a state change that was enough to cause a render. Using the spread operator in your solution affected the pointer and thus a render occurred.
In my own solution I set my array to null before adding content and that worked fine for my problem.

Categories