Value of state variable is lost - React - reactjs

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})).

Related

Unexpected behaviour of useState (do not renew a constant after getting server data)

I have a very simple react code, which I use to track containers location on a territory. After a new container get's into the territory I have props.operationsList changed. So I send get response to server API when props.operationsList changes
useEffect(() => {
async function fetchContainerLocation() {
const response = await CoordinatesService.getContainersPosition()
console.log('response = ', response.data.features)
setContainersList(response.data.features)
console.log('containersList = ', containersList)
}
fetchContainerLocation()
}, [props.operationsList])
I need to update containersList const, that I use to rerender a map API where I should locate the containers. I define it like that:
const [containersList, setContainersList] = useState([])
I need to set containersList in accordance with that response fron server (response.data.features) to make my map rerender. What's strange,
console.log('response = ', response.data.features)
shows accurate and correct data from server, but the next
console.log('containersList = ', containersList)
is not equal with this response
Instad of getting the map rendered with the right data from server response, I have wrong data. So, I do now understand why such an straightforward approch do not work and how to fix it
State updates in React are asynchronous; when an update is requested, there is no guarantee that the updates will be made immediately.
So, try to log your containersList outside useEffect and compare both logs. both should be same.
TIP: While using map method with your containerList use it like containerList?.map() so that page does not turn out to be blank.
const fetchContainerLocation = async () => {
const response = await CoordinatesService.getContainersPosition();
console.log("response = ", response.data.features);
setContainersList(response.data.features);
};
useEffect(() => {
fetchContainerLocation();
}, [props.operationsList]);
console.log(containerList);
return (
<>
{containerList?.map((container) => (
<p>something you want to render</p>
))}
</>
);
No idea why, but it worked when I changed response.data.features
to [...response.data.features]
Working code
useEffect(() => {
async function fetchContainerLocation() {
setContainersList([])
const response = await CoordinatesService.getContainersPosition()
setContainersList([...response.data.features])
}
fetchContainerLocation()
}, [props.operationsList])
If anybody could explain why, it would be useful

How to display a badge when a chat thread has unread messages React JS, Firestore

I am developing a chat application in React and firebase firestore. I want to display an unread messages badge every time a new message is added to the database. Currently, I am using a useEffect hook to check if the last message is read, and that only works only the first time the page renders - the unread messages badge appears only after I reload the page. I think what I can't figure out is how to re-render every time the state changes. Kindly look at my code and tell me what I am missing. I hope my issue is clear. Thanks in advance.
const [chatMessages, setChatMessages] = useState([])
const [isRead, setIsRead] = useState(false)
const readsRef = db.collection('rooms').doc(id).collection('messages')
useEffect(() => {
const unsubscribe = readsRef.orderBy('timestamp', 'desc')
.limit(1).onSnapshot((snapshot) => {
snapshot.docs.map((snap) => {
setChatMessages(snap.data().message)
readsRef.doc(snap.id).collection('read').doc(userID.uid).onSnapshot((snapshot1 => {
if (snapshot1.get('readReceipt') === userID.uid) {
setIsRead(true)
}
}))
})
})
return unsubscribe;
}, [isRead])
return (
<SidebarOptionChannel>
<span># </span>{title} - {chatMessages}<span>{isRead ? null : <UnreadBadge>
<span>1</span></UnreadBadge> }</span>
</SidebarOptionChannel>
)
Another solution to your problem could be to update your chat application by listening for changes in your Database of Firebase. This can be done by using the methods of firebase.database.Reference:
on() or once()
Here is an example provided by the documentation of Firebase:
var starCountRef = firebase.database().ref('posts/' + postId + '/starCount');
starCountRef.on('value', (snapshot) => {
const data = snapshot.val();
updateStarCount(postElement, data);
});

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 }));
};
};

How to refresh child component from parent component

I have a page that allows the user to upload and display a photo. The backend API is all built out. My problem is after I successfully upload the photo, I need to refresh the page in order to see the new photo at the bottom of the page. Here's the relevant code:
Parent Component
refreshPage = (value) => {
if(this.state.photoUploaded)
{
//this refreshes the API call to get the new photo
this.componentDidMount();
}
};
async componentDidMount(){
const url = <the url of my API>;
const response = await fetch(url);
const data = await response.json();
this.setState({eventdata:data});
var i = 0;
for(i = 0; i < data.photos.length; i++)
{
this.state.photos.push({"url":data.photos[i].url});
}
this.setState({loading:false});
}
render(){
<div>
<PhotoSelectorComponent eventdata={this.state.eventdata} refreshPage={this.refreshPage}></PhotoSelectorComponent>
<PhotoDisplayComponent photos={this.state.photos} eventdata={this.state.eventdata} refreshPage={this.refreshPage}></PhotoDisplayComponent>
</div>
}
I have the PhotoSelectorComponent notifying the Parent Component when a Photo has been uploaded. It calls the RefreshPage function, which calls componentDidMount(), which makes the api call an additional time, and I see the new photo in my photos array. I'm not even sure if this is the best way to do this, but it's an idea that I tried based on what I've read, and it's working. But I'm not sure how to re-render the PhotoDisplayComponent at this point. I'm updating the state, but it's not refreshing the page. I'm very close to making this work, but I'm not sure if they way that I have it setup currently is best practice or not. I feel like the page should just refresh itself if I have everything wired correctly.
You have some basic React mistakes there, like directly mutating state and calling setState few times in the same function. Try this:
async componentDidMount() {
const url = 'someurl';
const response = await fetch(url);
const data = await response.json();
const newPhotos = data.photos.map((photo) => {
return { "url": photo.url }
})
this.setState(({
loading: false,
photos:[...this.state.photos,...newPhotos],//I dont think you actually get only new photos, but following your code...
eventdata: data
}));
}
Also i think your architecture doesn't make much sense, because i've never seen a direct call to componentDidMount.

React Redux Firebase Upload File Object

Trying to pass the file object to redux action and perform the function inside of an redux action, not sure its the correct way? but basically i want back downloadURL from firebase upload complete so I can show image front end.
createLocation(event) {
event.preventDefault();
const fileObject = this.state.file;
const test = {
fileObject
}
this.props.uploadImage_func(test);
}
and action function:
export function uploadImage_func(fileObject) {
return dispatch => {
const fileName = 'myimage';
const storageRef = firebase.storage().ref('test/' + fileName);
const task = storageRef.put(fileObject);
task.on('state_changed',
function complete(snapshot) {
const downloadURL = task.snapshot.downloadURL;
},
).then(function () {
dispatch(attemptLogin({
...downloadURL
}));
});
}
}
error:
As you can see you have got an error Invalid argument in 'put' at index 0: Expected Blob or File. So first of all you need path exactly File or Blob. If you did right in you createLocation and got file object than you need not to wrap it in const test object more. That action causes unnecessary nesting, so just path fileObject as it is. And more. When you subscribe for firebase UploadTask on event you need to path callback functions and do it in a right order, so try to use next:
uploadTask.on('state_changed',
(snapshot) => {
// here you could log loading information in percents but uploading is not finished yes
console.log((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
},
(error) => console.log(error),
() => {
// And after uploading is complete you could get your download url
console.log('Call save img', uploadTask.snapshot.downloadURL);
}
);
For more information read documentation for Firebase Storage (Upload files)

Resources