I'm using the library JSZip to read a user-uploaded zip file and I want to load the contents of each file into the state.
Here's a toned-down version of reading the zip file:
const [files, setFiles] = useState([]);
const onUploadClick = () => {
const load = (filename) => {
return new Promise(
(resolve) => {
jzip.file(filename).async(`arraybuffer`).then(
(content) => {
return resolve(Array.from(new Uint8Array(content)));
}
)
}
)
}
jzip.loadAsync(file).then(
(zip) => {
zip.forEach(
async (_, entry) => {
const filename = entry[`name`];
const buffer = await load(filename);
const newFiles = [...files];
newFiles.push(buffer);
setFiles([...newFiles]);
)
}
)
}
I then used useEffect to monitor the changes to the files state component:
useEffect(
() => {
console.log(files)
},
[files]
);
But what I get out is just the following when trying to load a zip file with 3 files inside:
Instead, I am expecting something like
[Array(33), Array(33), Array(33)]
So it's reading each file correctly, converting to a Uint8Array, and adding it to the state, but the state is not retaining the previous files.
I also tried pushing just the filename into the files state component, and same error.
I am unsure why this is happening. Any advice is appreciated. Thank you!
Fixed code below:
const [files, setFiles] = useState([]);
const onUploadClick = () => {
const load = (filename) => {
return new Promise(
(resolve) => {
jzip.file(filename).async(`arraybuffer`).then(
(content) => {
return resolve(Array.from(new Uint8Array(content)));
}
)
}
)
}
jzip.loadAsync(file).then(
(zip) => {
const newFiles = [];
zip.forEach(
async (_, entry) => {
const filename = entry[`name`];
const buffer = await load(filename);
newFiles.push(buffer);
}
)
setFiles(newFiles);
)
}
The state update setFiles was not finished yet before trying to add a new entry, therefore files was not updated yet when trying to add a 2nd one. Same for the third file: it pulled the current files array (which was still empty, because the state update before it had not finished) and pushed something new. That's why there was only ever one item in the list.
Related
I would like to parse a pdf file in a React app. The pdf will be provided through a html input.
I used pdf-parse - a wrapper around pdf.js in node - without any problem. But when it comes to React, I only receive this error:
MissingPDFException {message: 'Missing PDF "http://localhost:3000/myfile.pdf".', name: 'MissingPDFException'}
I upload the file like this:
export default function Home() {
const [data, setData] = useState();
const handleFile = (e) => {
const file = e.target.files[0];
const fileReader = new FileReader();
fileReader.onload = (d) => {
setData(new Uint32Array(d.target.result));
};
};
return (
<>
<h1>hello!</h1>
<input
type="file"
accept="application/pdf"
placeholder="insert PDF here"
onChange={(e) => handleFile(e)}
/>
<PDFViewer pdfFile={data} />
</>
);
}
And The file is supposed to be read here:
import * as PDFJS from "pdfjs-dist/build/pdf";
import * as pdfjsWorker from "pdfjs-dist/build/pdf.worker.entry";
window.PDFJS = PDFJS;
export default function PDFViewer({ pdfFile }) {
PDFJS.GlobalWorkerOptions.workerSrc = pdfjsWorker;
const getPDFDoc = useCallback(async () => {
const doc = await PDFJS.getDocument(pdfFile);
doc.promise.then(
(loadedPdf) => {
setPdfRef(loadedPdf);
},
function (reason) {
console.error(reason);
}
);
}, []);
useEffect(() => {
getPDFDoc();
}, [getPDFDoc]);
I doesn't seem to work at all. I have a custom config with webpack, typescript and SWC-loader. I have read all the related stackoverflow threads.
How to properly parse a PDF with PDF.js in React? If there is a better library, I'm open to any suggestions. My goal is not to display the pdf, but to get its content.
Your component only runs getPDFDoc on mount since pdfFile is missing in the usecallback deps, so when the file changes, it probably doesn't even notice as your effect won't re-run since getPDFDoc is referentially stable when it shouldn't be.
Try
import * as PDFJS from "pdfjs-dist/build/pdf";
import * as pdfjsWorker from "pdfjs-dist/build/pdf.worker.entry";
window.PDFJS = PDFJS;
export default function PDFViewer({ pdfFile }) {
PDFJS.GlobalWorkerOptions.workerSrc = pdfjsWorker;
const getPDFDoc = useCallback(async () => {
if (!pdfFile) return
const doc = await PDFJS.getDocument(pdfFile);
doc.promise.then(
(loadedPdf) => {
setPdfRef(loadedPdf);
},
function (reason) {
console.error(reason);
}
);
}, [pdfFile]);
useEffect(() => {
getPDFDoc();
}, [getPDFDoc]);
I think the reason for the weird "myfile.pdf" thing is probably because when it first runs pdfFile is not defined and this might be some internal library default. So I also added a guard to not do anything when it's not set.
Hi Experts, I am new to react and stuck with below issue.
I have a Getfulldata function in which, I am using setFulltoursdata(copy) method to get pure data.
const [Fulltoursdata, setFulltoursdata] = useState([]);
const [Trimmedtoursdata, setTrimmedtoursdata] = useState([]);
useEffect(() => {
// fetchData();
Getfulldata();
}, []);
const Getfulldata = async () => {
await fetch("https://course-api.com/react-tours-project")
.then((response) => {
return response.json();
})
.then((reponsefulldata) => {
let copy = [...reponsefulldata];
console.log(copy);
setFulltoursdata(copy);
settoursdata([...reponsefulldata]);
setIsLoaded(true);
return reponsefulldata;
})
.then((dataobject) => {
const filteredTourinfo = dataobject.map((tour) => {
tour.info = tour.info.slice(0, 120) + "....";
return tour;
});
let Trimmedcopy = [...filteredTourinfo];
setTrimmedtoursdata([...filteredTourinfo]);
});
};
fetching api data.
.then((reponsefulldata) => {
let copy = [...reponsefulldata];
console.log(copy);
setFulltoursdata(copy);
Trying to make copy of reponsefulldata
Setting Fulltoursdata using setFulltoursdata()
return reponsefulldata;
})
After which, using then method I am trying to use slice method to update one column i.e. info in each array object. This needs to be Set into another state object i.e. Trimmedtoursdata.
.then((dataobject) => {
const filteredTourinfo = dataobject.map((tour) => {
tour.info = tour.info.slice(0, 120) + "....";
return tour;
});
let Trimmedcopy = [...filteredTourinfo];
setTrimmedtoursdata([...filteredTourinfo]);
Setting filteredTourinfousing setTrimmedtoursdata(). so setting Trimmedtoursdata should not effect Fulltoursdata.
Thanks!
So this is basically concept of shallow copy and deep copy. I created a deep copy using below.
let Trimmedcopy = JSON.parse(JSON.stringify(filteredTourinfo));
I have an array of country codes and I need to have the name.
I am trying to access the countries data from the state (axios call) and from there filter by country code, and from that new array, extract the common name of the country.
(I am using the restcountries.com api).
-If I create a new state to map from, I get the too many re-renders.
-Right now, Although the border countries info is there, I can't access it, I get the "Cannot read properties of undefined" error, that usually is tied to a lifecycle issue, therefore I am using a condition on when to access the information.
Still I am not able to get it stable and return the name that I need.
Can someone please take a look and tell me what am I doing wrong?
Thanks in advance
import axios from "axios";
const BorderCountries = (props) => {
const [countriesList, setCountriesList] = useState([]);
useEffect(() => {
axios
.get(`https://restcountries.com/v3.1/all`)
.then((countries) => setCountriesList(countries.data))
.catch((error) => console.log(`${error}`));
}, []);
const getCountryName = () => {
const codes = props.data;
const borderCountries = [];
codes.map((code) => {
const borderCountry = countriesList.filter((country) =>
country.cca3.includes(code)
);
borderCountries.push(borderCountry);
});
// console.log(borderCountries);
if (props.data.length === borderCountries.length) {
const borderName = borderCountries.map((border) =>
console.log(border[0].name.common)
);
return borderName
}
};
return (
<div>
<h3>Border Countries:</h3>
{getCountryName()}
</div>
);
};
export default BorderCountries;
const getCountryName = () => {
const codes = props.data;
if(countriesList.length === 0) return <></>;
const borderCountries = [];
codes.map((code) => {
const borderCountry = countriesList.filter((country) =>
country.cca3.includes(code)
);
borderCountries.push(borderCountry);
});
// console.log(borderCountries);
if (props.data.length === borderCountries.length) {
const borderName = borderCountries.map((border) =>
console.log(border[0].name.common)
);
return borderName
}
};
Try this, you forgot to wait for the call to finish.
I am using antd drag and drop component https://ant.design/components/upload/#components-upload-demo-drag. In the example they have given if I add the prop accept it only accepts the restricted formats and do not add other files in the fileList. However when I use this component in my application, it adds all kinds of files. Why is this behavior occuring and how to avoid it?
const Uploader = () => {
const [files, setFiles] = useState([])
const onChangeHandler = (res) => {
setFiles(res.fileList)
};
console.log(files)
return (
<Upload.Dragger
accept=".pdf,.doc,.docx"
onChange={onChangeHandler}
showUploadList={false}
multiple
fileList={files}
>
Upload
</Upload.Dragger>
);
};
If I drag a png image for instance it does not get added in the fileList but if I manually select any file (which is in not in accept prop) it adds in state which I do not want. Any help?
You can combine with the prop beforeUpload (example in the documentation)
Example:
const Uploader = () => {
const types = ["application/pdf","application/vnd.openxmlformats-officedocument.wordprocessingm","application/msword"];
const [files, setFiles] = useState([])
const onChangeHandler = (res) => {
console.log(res);
let addFiles = true;
for(let i = 0; i < res.fileList.length; i++) {
if (!types.includes(res.fileList[i].type)) {
addFiles = false;
}
}
if( addFiles ) {
setFiles(res.fileList);
}
console.log(files);
};
return (
<Upload.Dragger
accept=".pdf,.doc,.docx"
beforeUpload={(file) => {
if (!types.includes(file.type)) {
message.error(`${file.name} is not a pdf, doc or docx file`);
return false;
} else {
return true
}
}}
onChange={onChangeHandler}
showUploadList={false}
multiple
fileList={files}
>
Upload
</Upload.Dragger>
);
};
I'm working on a higher order component that will provide the ability to capture media using the MediaRecorder API. However, when I try to use the captured video (in the form of a Blob passed to createObjectURL) I am getting an error ERR_REQUEST_RANGE_NOT_SATISFIABLE. When I console log the Blob that is passed to the wrapped component, it has a length of 0. I have included my code at the end of this post.
In order to diagnose the problem, I tried the following tests:
Console logging newChunks in handleDataAvailable logs the correct value (i.e. [Blob]).
I added React.useEffect(() => console.log(chunks), [chunks]); in order to see if chunks is actually getting updated. This also results in the correct value being logged (i.e. [Blob]).
I added React.useEffect(() => console.log(captured), [captured]); in order to see if captured is getting updated. This results in a Blob of size 0 being logged.
In handleStop, I console log chunks and the blob created by combining the chunks. That results in an empty array and a blob with size 0 respectively.
This leads me to believe that handleDataAvailable is correctly adding each chunk to the chunks array, but somehow the array is being emptied by the time that handleStop gets run.
Does anyone see what might be causing that to happen?
Code:
import React from 'react';
import { getUserMedia, getConstraints } from '../../utils/general';
const withMediaCapture = (WrappedComponent, recordingType, facingMode, deviceID) => {
const constraints = getConstraints(recordingType, facingMode, deviceID);
const type = recordingType === 'audio'
? 'audio/ogg; codecs=opus'
: 'video/webm; codecs=vp9';
return props => {
const [mediaStream, setMediaStream] = React.useState(undefined);
const [mediaRecorder, setMediaRecorder] = React.useState(undefined);
const [isRecording, setIsRecording] = React.useState(false);
const [captured, setCaptured] = React.useState(undefined);
const [chunks, setChunks] = React.useState([]);
// On mount, get the mediaStream:
const setupStream = () => {
getUserMedia(constraints)
.then(setMediaStream)
.catch(error => {/* TODO: Handle error */});
};
React.useEffect(setupStream, []);
// Once we have gotten the mediaStream, get the mediaRecorder:
const handleDataAvailable = ({ data }) => {
const newChunks = [...chunks, data];
setChunks(newChunks);
};
const handleStop = foo => {
const blob = new Blob(chunks, { type });
setCaptured(blob);
setChunks([]);
}
const getMediaRecorder = () => {
mediaStream && setMediaRecorder(Object.assign(
new MediaRecorder(mediaStream),
{
ondataavailable: handleDataAvailable,
onstop: handleStop,
},
));
}
React.useEffect(getMediaRecorder, [mediaStream]);
const toggleRecording = () => {
isRecording
? mediaRecorder.stop()
: mediaRecorder.start();
setIsRecording(!isRecording);
};
return <WrappedComponent {...{ preview: mediaStream, captured, toggleRecording, isRecording, ...props }} />;
};
};
const VideoCaptureDemo = ({ preview, captured, toggleRecording, isRecording }) => {
const previewRef = React.useRef(null);
const capturedRef = React.useRef(null);
const setupPreview = () => {
previewRef.current.srcObject = preview;
};
React.useEffect(setupPreview, [preview]);
const setupCaptured = () => {
const url = captured && window.URL.createObjectURL(captured);
capturedRef.current.src = url;
};
React.useEffect(setupCaptured, [captured]);
return (
<div>
<video ref={previewRef} autoPlay={true} muted={true} />
<video ref={capturedRef} controls />
<button onClick={toggleRecording}>
{isRecording ? 'Stop Recording' : 'Start Recording'}
</button>
</div>
);
};
export default withMediaCapture(VideoCaptureDemo, 'videoAndAudio');
handleStop and handleDataAvailable are both closing over the initial, empty chunks array. If handleDataAvailable is called more than once, earlier chunks will be lost, and handleStop will always create a Blob from the empty chunks array. Re-renders caused by setChunks will cause new versions of the handle methods to be created, but the MediaRecorder will still be using the versions from when the MediaRecorder was created.
You could fix handleDataAvailable by using functional updates, but in order to fix handleStop I think you would be best off to switch to using useReducer (with the reducer managing both chunks and captured) so that you can just dispatch an action and then the reducer can have access to the current chunks and create the Blob appropriately.