Using FileReader on Next.js SSR? - reactjs

I've got a Next.js app, and I am trying to use FileReader to read the contents of an 'uploaded' file.
My component looks like below:
let fileReader;
let props = {
multiple: false,
onRemove: file => {
const index = fileList.indexOf(file);
const newFileList = fileList.slice();
newFileList.splice(index, 1);
setFileList(newFileList);
},
beforeUpload: file => {
setFileList($ => [file]);
fileReader.readAsText(file);
return false;
},
fileList,
};
useEffect(() => {
fileReader = new FileReader();
fileReader.onload = event => {
setFileData(JSON.parse(event.target.result));
};
setReady(true);
}, []);
My upload form calls props.beforeUpload to pass the file to it, however the issue I have is that fileReader is undefined at this point.
I'm suspecting this is because of what's rendered server side and what's rendered client side, however I'm not sure how to handle this?

I don't know what you suspect is correct but if you really think that server side rendering has broken it, then you could use dynamic import of next and use ssr:false to make it render on the client side only, more info

Related

Is there any way to sort out the problem of delay or (lag) in rendering? in reactjs

Actually, I made a drag and drop component in my application. And the problem I faced is when I drop a picture in that component, it doesn't render on the screen. But on next go it render the picture that I uploaded last time and vice versa.
<div className="uf-upload-box" style={{"text-align":"center", "padding-top":"30px"}}
onDrop={onChangeHandler}>
const onChangeHandler = (event) => {
console.log("Hello");
setIsLoading(true);
let reader = new FileReader();
reader.readAsDataURL(event.target.files[0]);
reader.addEventListener("load", () => {
fabric.Image.fromURL(reader.result, function (img) {
setIsLoading(false);
console.log(img);
activeCanvas.add(img);
});
});
}
It would be great if you could share more of your code. For example, how you are managing the component state?
However, this problem indicates the UI is getting updated via setIsLoading(false) before the image is actually added to activeCanvas.
Now what is activeCanvas? If it is an array of images, you can do something like this:
const [activeCanvas , setActiveCanvas] = useState([]);
And as the image is loaded successfully:
reader.addEventListener("load", () => {
fabric.Image.fromURL(reader.result, function (img) {
setIsLoading(false);
setActiveCanvas([...activeCanvas , img]);
});
});
I sorted out the issue that I faced, what I did is; just change the event on dropping the file from ondrop to onChange. And I got the desired result.
replace this one:
onDrop={onChangeHandler}
from this one:
onChange={onChangeHandler}

How to get onUploadProgress value in an await function from axios?

I'm relatively new to react and having trouble getting the progress value "progressEvent" of axios in the onUploadProgress callback,
I have two files, one for the api call and one for my react component:
Here is a sample of my api.js
function uploadImage(file) {
return axios.post('/api/media_objects', file, {
onUploadProgress: progressEvent => {
let percentComplete = progressEvent.loaded / progressEvent.total
percentComplete = parseInt(percentComplete * 100);
console.log(percentComplete);
}
}).then(response => response.data.id);
}
and my try/catch from the component
try {
const upload = await xxxAPI.uploadImage(formData);
} catch (error) {
console.log(error);
}
How can i retrieve in the "try" the "percentComplete" ?
Thanks !
Generally, I'd advise using some kind of state management (redux/mobx) for controlling this flow. Not to handle it directly from a React component. So the component will trigger a kind of action and the upload process will be handled outside.
But, for a very simple solution, you'd need something like this:
function uploadImage(file, updateProgress) {
return axios.post('/api/media_objects', file, {
onUploadProgress: progressEvent => {
let percentComplete = progressEvent.loaded / progressEvent.total
percentComplete = parseInt(percentComplete * 100);
console.log(percentComplete);
updateProgress(percentComplete);
}
}).then(response => response.data.id);
}
const MyComponent = () => {
const [progress, setProgress] = useState(0);
const onUpload = useCallback(() => {
myApi.uploadImage(data, setProgress);
},[]);
return <div>
<span>Uploaded: {progress}</span>
<button onClick={onUpload}>Upload</button>
</div>;
};
the progress value is stored in the component's state so it can be updated and rendered.
I would also put the try/catch in the API method rather than in the component.
On a more general note. I'd advise using a library such as react-uploady to manage the upload for you. There's are a lot of edge cases and functionality you need to handle typically when uploading files and a small 3rd party like Uploady takes care of it for you: Preview with progress for file uploading in React

FileReader - how to update local state after store is updated?

I'm playing around with a food recognition api.
I have a component with a local state called ingredients.
In the component, I have an input tag that accepts a file image upload and calls cameraHandler method onChange. The method uses FileReader to convert the image into Base64
Once the FileReader is finished encoding the image, the method calls a redux action fetchIngredientsFromImage to post the base64 image into a route to trigger to trigger an API call (to analyze the ingredients in the image).
The response is sent back to the front end, and used to update store.
So basically, the API call is successful, I get the data I need, and store is updated successfully. Great.
But what I also need to do, is update my local ingredients state. But I don't know how to wait for store to be updated before calling setState.
I've tried componentDidUpdate with if(this.props !== prevProps) methodToUpdateLocalState(), but this doesn't work because for some reason the component won't re-render after store is updated.. Turns out that everything inside componentDidUpdate runs first, and store is updated afterwards. I feel like also isn't necessary (probably).
I also tried .then the awaited readers inside cameraHandler, but .then is undefined.
I'd appreciate any input I could get. Really at a loss here, because I have the data, and I just need to somehow grab it so I can setState.
Component
class RecipesSearch extends Component {
state = {
ingredients: [], //need to update this after store is updated, but how?
};
cameraHandler = async (event) => {
const { fetchIngredientsFromImage } = this.props;
const file = event.target.files[0];
const reader = new FileReader();
await reader.readAsDataURL(file);
reader.onloadend = async () => {
const imgBase = reader.result.replace(/^data:image\/(.*);base64,/, '');
await fetchIngredientsFromImage(imgBase); //.then here is undefined
};
};
render(){
<input
className="form-check-input"
type="file"
name="camera"
accept="image/*"
onChange={this.cameraHandler}
/>
}
Actions
const fetchIngredientsFromImage = (imgBase) => async (dispatch) => {
const { data } = await axios.post(`/api/camera/`, { imgBase });
return dispatch(setIngredientsFromCamera(data)); //successfully updates store
};
as a workaround I made an axios.post call inside cameraHandler. Not proud of it, because I'd like to utilize store and keep it consistent with my other methods, but for the time being it'll do I guess.
cameraHandler = async (event) => {
// const { loadIngredientsFromImage } = this.props;
const file = event.target.files[0];
const reader = new FileReader();
await reader.readAsDataURL(file);
reader.onloadend = async () => {
const imgBase = reader.result.replace(/^data:image\/(.*);base64,/, '');
await axios
.post(`/api/camera/`, { imgBase })
.then((response) => this.setState({ ingredients: response.data }));
};
};

React Promise - confusion in coding

I am new to react, and trying to follow a tutorial of Promises. I merged the two files into one as below for convenience. However, I am totally lost as to how to display the desired images. I copied the last bit from another react application.
import React from 'react'
function DemoPromise2() {
function loadImage(url) {
return new Promise((resolve, reject) => {
let image = new Image();
image.onload = function () {
resolve(image);
};
image.onerror = function () {
let message = "Could not load image at " + url;
reject(new Error(message));
};
image.src = url;
});
}
// export default loadImage;
let addImg = (src) => {
let imgElement = document.createElement("img");
imgElement.src = src;
document.body.appendChild(imgElement);
};
Promise.all([
loadImage("images/img1.jpg"),
loadImage("images/img2.jpg"),
loadImage("images/img3.jpg"),
loadImage("images/img4.jpg"),
])
.then((images) => {
images.forEach((img) => addImg(img.src));
})
.catch((error) => {
// handle error later
});
return (
<div className="App">
Demo Promise2 -
<br />
???? Question :: how can I display images here??
</div>
);
}
export default DemoPromise2;
#Evert is correct, you need to load this into state to show the images.
TL;DR: Click Run code snippet below to see the code in action.
--
Longer Explanation:
useState will allow the data to be accessed with the component and allow it to be persisted within it. You either get the value with it's name or setValue to update it.
Ex:
const [myvalue, setMyvalue] = useState('defaultValue');
useEffect is use as another React but specifically for when state get modified and given that you give specific things to look for (or watch as an array)
Example Nothing To Watch:
useEffect(() => {
console.log('CODE IS RUN ONLY ONCE, Nothing to watch except when component loads');
}, []);
Example State To Watch:
const [value, setValue] = useState('watch this');
useEffect(() => {
console.log('RUN EACH TIME value is updated including first time set');
}, [value]);
Code:
// main.js
// for your code use: import React, { useState, useEffect } from 'react';
const { useState, useEffect } = React;
const App = () => {
// State / Props
const [images, setImages] = useState([]);
// Load Images Function
const loadImage = (url) => {
return new Promise((resolve, reject) => {
let image = new Image();
image.onload = () => {
resolve(image);
};
image.onerror = () => {
let message = `Could not load ${url}`;
reject(new Error(message));
};
image.src = url;
});
};
// Hook to use when the component is loaded and ready
// Equivalent to something like window.onload
useEffect(() => {
Promise.all([
loadImage('https://picsum.photos/seed/picsum/200/200'),
loadImage('https://picsum.photos/200/200')
])
.then((data) => {
setImages(data);
})
.catch((error) => console.log('ERROR', error));
}, []);
return <div><h1>My Images</h1><p>Images Loaded: {images.length}</p>{images.map((img, index) => <img key={`img-${index}`} src={img.getAttribute('src')} />)}</div>;
};
ReactDOM.render(<App />, document.querySelector('#root'));
<body>
<div id="root"></div>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<script src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<script type="text/babel" src="main.js"></script>
</body>
====== CLARIFICATIONS ======
Question:
const [images, setImages] = useState([]); ???????????? the above defines const 'images'. Where is 'images' value modified? where is it receiving values/images?
Answer:
Think of this part as destructuring from a variable that's an array, but you're getting the getter and setter from useState.
The same way you would destructure an array like:
const myArray = [1, 2];
const [firstValue, secondValue] = myArray;
// firstValue = 1
// secondValue = 2
But instead of just plain values, you're getting the equivalent of functions returned to get the value and set a new value.
Note that useState is defined by React.
With that in mind, images is updated (set) here:
.then((data) => {
setImages(data);
})
Question:
2. let image = new Image(); ???????? whats the purpose of this sttmt? 'let image = new image()' ??? such was used to set an instance of a class but why is it used for a function? ????????
Answer:
new Image() is a base function baked into the browser, and would be typically be used for creating a new <img> with some event handling, or potentially other custom functionality.
Technically you do not need to use new Image() here if you're just going to rely on the browser to load the images naturally.
In the case of your initial code, the functionality exists to do something when the image is done loading, or handle errors in a specific way, but there isn't really any code except for resolving a promise.
What you would do is something like:
image.onload = () => {
// Images is loaded send message to custom logging software
// OR Image is loaded which works like a tracking pixel sent a message
// OR Image is loaded, append it to DOM and perform a fade in animation
resolve(image);
};
Sometimes you would just use this function to handle ordering images in a sequential way, or control the sequence of loading and animations.
On in the case of your code, wait until all the images are loaded, then show the page vs. loading them one by one natively in the browser.
Question:
3. image.onload = () => {resolve(image); 'image.onload' is just a const , will it hold the 'image'?
Answer:
It does not hold the image, this is just an event function that is defined by Image to handle when the image is done loading. So it's a predefined function that is called by Image, but defined by you.
Question:
?4. image.src = url .. ??
Answer:
This is the part of Image that starts the whole process, that's why it's defined at the end.
The code is saying.
// Before you begin you're going to be getting an image
let image = new Image();
// Before we load the image, I want to tell you how to handle it once
// it loads successfully
image.onload = function {
// custom instructions
};
// Before we load the image, I want to tell you how to handle it
// in case things go wrong
image.onerror = function {
// custom instructions
}
// Ok, we're good to go, start the process, here's the url
image.src = url; // https://picsum.photos/seed/picsum/200/200

Value of state variable is lost - React

I want to build a CRUD in React with Laravel and Firebase. Everything is perfect when I'm working with text, but I got trouble when I try to upload an image to Firebase Storage. I can save it but I can't get its URL.
I wrote 2 "console.log". In the first one the URL is there, but the second one (when I try to get the URL from the state variable) doesn't return anything.
handleSubmit = event =>{
event.preventDefault();
const {imagen} = this.state;
if(imagen!=null){
const uploadTask = storage.ref(`imagenes/${imagen.name}`).put(imagen);
uploadTask.on('state_changed',
(snapshot) => {
const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
this.setState({progress});
},
(error) => {
console.log(error);
},
() => {
storage.ref('imagenes').child(imagen.name).getDownloadURL().then(url => {
this.setState({url});
console.log(this.state.url); //<<<<<<<<<<<<<SHOW URL (IT'S OK!)
})
});
}
var direccion = null;
const form = event.target;
let data = new FormData(form);
data.append('url', this.state.url);
console.log(this.state.url); //<<<<<<<DOESN'T SHOW URL !! (HERE'S THE TROUBLE)
If you want to check the entire file:
https://github.com/AndresVasquezPUCE/project/blob/master/pelicula
I'm not a professional, so please don't be rude :D
this.setState is asynchronous
If you want to get the updated state value, add a callback and access the new state there like
this.setState({ url: 'some url'}, () => {
conosle.log(this.state.url);
});
Data is loaded from Firebase asynchronously. By the time your console.log(this.state.url); //<<<<<<<DOESN'T SHOW URL !! (HERE'S THE TROUBLE) the data hasn't been loaded from Firebase yet, and the then hasn't been called yet.
Any code that needs the data from Firebase needs to either be inside the then() callback (such as console.log(this.state.url); //<<<<<<<<<<<<<SHOW URL (IT'S OK!)) or be called from there (such as this.setState({url})).

Resources