setState isn't updating immediately - reactjs

I am working with a react aplication and i'm tryna to update a state and it isnt updating immediately. I looked for it inm internet and found, but the problem is, all this people are using classes componets and i'm using functional componets. they are talking about callbacks in setState function, but i tried it in my code, and didn't work!!
Heres my code:
async function handleSelectImage(event: ChangeEvent <HTMLInputElement>) {
if (!event.target.files) {
return;
}
const selectedImages = Array.from(event.target.files);
selectedImages.map((image) => {
if (!(image.type === 'image/png' || image.type === 'image/jpg' || image.type === 'image/jpeg')) {
const imageIndex = selectedImages.indexOf(image);
selectedImages.splice(imageIndex, 1);
alert('Só são aceitos arquivos jpeg, jpg e png.');
}
});
try {
setImages(images.concat(selectedImages));
} catch (err) {
console.error(err);
}
console.log(images);
Hope you can help me!!!!
THX!!! :)

State can't be read in functional components in React because it's an async operation.
So, it's not that your state ISN'T updating, but that your console.log(images) function is called and read before your async function that updates state returns.
Okay... so what to do about that?
Two options:
1. Pass state into another component and read it there.
This one is preferred, imo, because you can separate your stateful components from your "dumb" components.
So in your component above, you can pass images as a prop to a child component:
Inside the ImageDisplay, get the images state from props.
2. Wait for the async function to update inside your component.
If you really want to read state inside your functional component, you have to wait for the async function to return. To do this, I think the easiest way is to set a "waiting" state, like so.
const [isLoading, setLoading] = useState(true);
/// rest of your call and your async function
///try block:
try {
setImages(images.concat(selectedImages));
setLoading(false);
} catch (err) {
console.error(err);
}
if(setLoading) {
console.log("The images haven't loaded yet");
} else {
console.log(images)
}
Basically, you're giving the component a conditional to wait for the image state to change. When that conditional is no longer true, the images will be ready to be displayed. (You can do this with rendering data on the frontend, too, not just the console!)
Good luck.

Related

Update state in setInterval via dispatch outside component

I currently have a functional component Form that triggers a task to occur. Once the submission is complete, I create a setInterval poll to poll for the status of the task. The code roughly looks like
export function Form(props: FormProps) {
const dispatch = useDispatch()
const pollTaskStatus = () => {
const intervalId = setInterval(async() => {
const response = await fetchTaskStatus() // Function in different file
if (response.status === 'COMPLETE') {
dispatch(Actions.displayTaskComplete())
clearInterval(intervalId)
}
})
}
const submitForm = async() => {
await onSubmitForm() // Function in different file
pollTaskStatus()
}
return (
...
<button onClick={submitForm}>Submit</button>
)
}
When the action is dispatched, the redux store is supposed to be updated and a component is supposed to update alongside it showing a message that the task is complete. However, I see the action logged with an updated store state but nothing occurs. If I just try to dispatch the same action with useEffect() wrapped around it outside the submitForm functions, the message appears. I've searched online and people say that you need to wrap useEffect around setInterval but I can't do that because the function that calls setInterval is not a custom hook or component. Is there a way to do this?
It's a bit difficult to answer your question without seeing all the code.
But my guts feeling is that this might no have anything to do with React.
const pollTaskStatus = () => {
const intervalId = setInterval(async() => {
console.log('fire to fetch')
const response = await fetchTaskStatus() // Function in different file
if (response.status === 'COMPLETE') {
console.log('success from fetch')
dispatch(Actions.displayTaskComplete())
}
})
}
Let's add two console lines to your code. What we want to see is to answer the following questions.
is the setInterval called in every 500ms?
is any of the call finished as success?
how many dispatch has been fired after submission
If you can answer all these questions, most likely you can figure out what went wrong.

Polling the `useQuery` causes un-necessary re-renders in React component

I have a hook that looks like this
export const useThing = id => {
const { stopPolling, startPolling, ...result } = useQuery(GET_THING, {
fetchPolicy: 'cache-and-network',
variables: { id },
skip: !id
});
return {
startPolling,
stopPolling,
thing: result.data.thing,
loading: result.loading,
error: result.error
};
}
Then in my component I'm doing something like this;
const {loading, error, thing, startPolling, stopPolling} = useThing(id);
// this is passed as a prop to a childComponent
const onUpdateThing = ()=> {
setShowInfoMessage(true);
startPolling(5000);
}
const verficationStatus = thing?.things[0]?.verificationStatus || 'PENDING';
useEffect(()=> {
if(verficationStatus === 'FAILED' || verificationStatus === 'SUCCESS') {
stopPolling();
}
}, [verificationStatus])
I'm using the startPolling and startPolling function to dynamically poll the query. The problem with this approach is that whenever I start polling, let's say with an interval of 3 seconds, the component will re-render every 3 seconds. I want the component to render only when the server has sent the updated response.
Here are a few things I've tried to debug;
I've tried profiling the component using React Dev Tools and it displays hooks changed as the cause of re-render.
I'm using multiple useEffects inside my component so my next thought
was to check to see if any of the useEffect is causing the re-renders. I've tried adding and removing every single dependency in my useEffect calls and it doesn't seem to help.
I'm not storing any value returned in the state either.
Child component calls the mutation.
I've also tried using React.memo but it causes more problems than it solves.
Any ideas as to what might be causing the problem would be greatly appreciated.

Why can't useEffect access my state variable in a return statement?

I don't understand why my useEffect() React function can't access my Component's state variable. I'm trying to create a log when a user abandons creating a listing in our app and navigates to another page. I'm using the useEffect() return method of replicating the componentWillUnmount() lifecycle method. Can you help?
Code Sample
let[progress, setProgress] = React.useState(0)
... user starts building their listing, causing progress to increment ...
console.log(`progress outside useEffect: ${progress}`)
useEffect(() => {
return () => logAbandonListing()
}, [])
const logAbandonListing = () => {
console.log(`progress inside: ${progress}`)
if (progress > 0) {
addToLog(userId)
}
}
Expected Behavior
The code would reach addToLog(), causing this behavior to be logged.
Observed Behavior
This is what happens when a user types something into their listing, causing progress to increment, and then leaves the page.
The useEffect() method works perfectly, and fires the logAbandonListing() function
The first console.log() (above useEffect) logs something greater than 0 for the progress state
The second console.log() logs 0 for the progress state, disabling the code to return true for the if statement and reach the addToLog() function.
Environment
Local dev environment of an app built with Next.js running in Firefox 76.0.1
nextjs v 8.1.0
react v 16.8.6
I'd really appreciate some help understanding what's going on here. Thanks.
I think it is a typical stale closure problem. And it is hard to understand at first.
With the empty dependency array the useEffect will be run only once. And it will access the state from that one run. So it will have a reference from the logAbandonListing function from this moment. This function will access the state from this moment also. You can resolve the problem more than one way.
One of them is to add the state variable to your dependency.
useEffect(() => {
return () => logAbandonListing()
}, [progress])
Another solution is that you set the state value to a ref. And the reference of the ref is not changing, so you will always see the freshest value.
let[progress, setProgress] = React.useState(0);
const progressRef = React.createRef();
progressRef.current = progress;
...
const logAbandonListing = () => {
console.log(`progress inside: ${progressRef.current}`)
if (progressRef.current > 0) {
addToLog(userId)
}
}
If userId is changing too, then you should add it to the dependency or a reference.
To do something in the state's current value in the useEffect's return function where the useEffects dependencies are am empty array [], you could use useReducer. This way you can avoid the stale closure issue and update the state from the useReducer's dispatch function.
Example would be:
import React, { useEffect, useReducer } from "react";
function reducer(state, action) {
switch (action.type) {
case "set":
return action.payload;
case "unMount":
console.log("This note has been closed: " + state); // This note has been closed: 201
break;
default:
throw new Error();
}
}
function NoteEditor({ initialNoteId }) {
const [noteId, dispatch] = useReducer(reducer, initialNoteId);
useEffect(function logBeforeUnMount() {
return () => dispatch({ type: "unMount" });
}, []);
return <div>{noteId}</div>;
}
export default NoteEditor;
More info on this answer
When you return a function from useEffect, it behaves like componentWillUnmount so I think it only runs while cleaning up. You'd need to actually call logAbandonListing like:
useEffect(() => {
logAbandonListing();
}, []);
So it runs everytime a component re-renders. You can read more about useEffect on https://reactjs.org/docs/hooks-effect.html
It's written excellently.
I tried using this sandbox to explain my answer.
Basically you are returning a function from your useEffect Callback. But that returned function is never really invoked so it does no actually execute and thus log the abandon action. If you look at the Code in the sandbox I have added a wrapper Parens and () afterwards to actually cause the method to be invoked leading to console.log executing.

Graphql subscriptions inside a useEffect hook doesn't access latest state

I'm building a basic Slack clone. So I have a "Room", which has multiple "Channels". A user subscribes to all messages in a Room, but we only add them to the current message list if the new message is part of the user's current Channel
const [currentChannel, setCurrentChannel] = useState(null);
const doSomething = (thing) => {
console.log(thing, currentChannel)
}
useEffect(() => {
// ... Here I have a call which will grab some data and set the currentChannel
Service.joinRoom(roomId).subscribe({
next: (x) => {
doSomething(x)
},
error: (err: any) => { console.log("error: ", err) }
})
}, [])
I'm only showing some of the code here to illustrate my issue. The subscription gets created before currentChannel gets updated, which is fine, because we want to listen to everything, but then conditionally render based on currentChannel.
The issue I'm having, is that even though currentChannel gets set correctly, because it was null when the next: function was defined in the useEffect hook, doSomething will always log that currentChannel is null. I know it's getting set correctly because I'm displaying it on my screen in the render. So why does doSomething get scoped in a way that currentChannel is null? How can I get it to call a new function each time that accesses the freshest state of currentChannel each time the next function is called? I tried it with both useState, as well as storing/retrieving it from redux, nothing is working.
Actually it is related to all async actions involving javascript closures: your subscribe refers to initial doSomething(it's recreated on each render) that refers to initial currentChannel value. Article with good examples for reference: https://dmitripavlutin.com/react-hooks-stale-closures/
What can we do? I see at least 2 moves here: quick-n-dirty and fundamental.
We can utilize that useState returns exact the same(referentially same) setter function each time and it allows us to use functional version:
const doSomething = (thing) => {
setCurrentChannel(currentChannelFromFunctionalSetter => {
console.log(thing, currentChannelFromFunctionalSetter);
return currentChannelFromFunctionalSetter;
}
}
Fundamental approach is to utilize useRef and put most recent doSomething there:
const latestDoSomething = useRef(null);
...
const doSomething = (thing) => { // nothing changed here
console.log(thing, currentChannel)
}
latestDoSomething.current = doSomething; // happens on each render
useEffect(() => {
Service.joinRoom(roomId).subscribe({
next: (x) => {
// we are using latest version with closure on most recent data
latestDoSomething.current(x)
},

reactjs Redux, how to load data from redux onComponentDidmount while a first action is already sent

Working with redux for a few time now I am facing a configuration that's blocking me.
Here is my ComponentDidMount:
componentDidMount() {
const { dispatch } = this.props;
dispatch(fetchPosts('trendings'));
(this.props.message) ? dispatch(removeMessage('')) : '' ;
}
the dispatch(fetchPosts('trendings')); fill a props.posts with specifics arrays inside.
The thing is, once this props.posts is filled, I need to use it to feed an other dispatch, but all of those actions in the ComponentDidMount.
And here is my problem, the second dispatch is call while the first one is in action.
I need to do it this way:
var outputList = this.props.posts.items['output'];
const { dispatch } = this.props;
if (typeof outputList !== "undefined") {
outputList.map((item, i) => {
dispatch(newOutput(item.MarkerName));
});
I tried to put this code into componentDidUpdate but the problem is that this function will automatically trigger each time an action occurs, which I don't want.
Is there a way?
Many thanks.
You can do it in componentWillReceiveProps, with a tweak:
componentWillReceiveProps(nextProps) {
if (this.props.posts.length === 0 && nextProps.posts.length > 0) {
/* Do stuff here */
}
}
This way, it'll run the code only once, when the posts became from empty array to array with items. If re-render is necessary, you should do it in shouldComponentUpdate instead.

Resources