is requestAnimationFrame belong to microtask or macrotask in main thread task management? if not, how can we categorize this kind of render side task - reactjs

how react schedule effects? I made some test, it seems hooks is called after requestAnimationFrame, but before setTimeout. So, I was wondering, how is the real implementation of scheduler. I checked react source code, it seems built upon MessageChannel api.
Also, how event-loop runs the macrotask sequence, for instance setTimeout/script etc.?
const addMessageChannel = (performWorkUntilDeadline: any) => {
const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
port.postMessage(null);
}
const Component1 = () => {
const [value,] = useState('---NOT INITIALISED')
requestIdleCallback(() => {
console.log('requestIdleCallback---')
})
useEffect(() => {
console.log('useEffect---')
}, [])
Promise.resolve().then(() => {
console.log('promise---')
})
setTimeout(() => {
console.log('setTimeout---')
});
addMessageChannel(()=> {
console.log('addMessageChannel---')
})
requestAnimationFrame(() => {
console.log('requestAnimationFrame---')
})
return <div>{value}</div>;
}
export default Component1
browser console result:
promise---
requestAnimationFrame---
addMessageChannel---
useEffect---
setTimeout---
requestIdleCallback---

I'm not sure about the useEffect so I'll take your word they use a MessageChannel and consider both addMessageChannel and useEffect a tie.
First the title (part of it at least):
[Does] requestAnimationFrame belong to microtask or macrotask[...]?
Technically... neither. requestAnimationFrame (rAF)'s callbacks are ... callbacks.
Friendly reminder that there is no such thing as a "macrotask": there are "tasks" and "microtasks", the latter being a subset of the former.
Now while microtasks are tasks they do have a peculiar processing model since they do have their own microtask-queue (which is not a task queue) and which will get visited several times during each event-loop iterations. There are multiple "microtask-checkpoints" defined in the event-loop processing model, and every time the JS callstack is empty this microtask-queue will get visited too.
Then there are tasks, colloquially called "macro-tasks" here and there to differentiate from the micro-tasks. Only one of these tasks will get executed per event-loop iteration, selected at the first step.
Finally there are callbacks. These may be called from a task (e.g when the task is to fire an event), or in some particular event-loop iterations, called "painting frames".
Indeed the step labelled update the rendering is to be called once in a while (generally when the monitor sent its V-Sync update), and will run a series of operations, calling callbacks, among which our dear rAF's callbacks.
Why is this important? Because this means that rAF (and the other callbacks in the "painting frame"), have a special place in the event-loop where they may seem to be called with the highest priority. Actually they don't participate in the task prioritization system per se (which happens in the first step of the event loop), they may indeed be called from the same event-loop iteration as even the task that did queue them.
setTimeout(() => {
console.log("timeout 1");
requestAnimationFrame(() => console.log("rAF callback"));
const now = performance.now();
while(performance.now() - now < 1000) {} // lock the event loop
});
setTimeout(() => console.log("timeout 2"));
Which we can compare with this other snippet where we start the whole thing from inside a rAF callback:
requestAnimationFrame(() => {
setTimeout(() => {
console.log("timeout 1");
requestAnimationFrame(() => console.log("rAF callback"));
});
setTimeout(() => console.log("timeout 2"));
});
While this may seem like an exceptional case to have our task called in a painting-frame, it's actually quite common, because browsers have recently decided to break rAF make the first call to rAF trigger a painting frame instantly when the document is not animated.
So any test with rAF should start long after the document has started, with an rAF loop already running in the background...
Ok, so rAF result may be fluck. What about your other results.
Promise first, yes. Not part of the task prioritization either, as said above the microtask-queue will get visited as soon as the JS callstack is empty, as part of the clean after running a script step.
rAF, fluck.
addMessageChannel, see this answer of mine. Basically, in Chrome it's due to both setTimeout having a minimum timeout of 1ms, and a higher priority of the message-tasksource over the timeout-tasksource.
setTimeout currently has a 1ms minimum delay in Chrome and a lower priority than MessageEvents, still it would not be against the specs to have it called before the message.
requestIdleCallback, that one is a bit complex but given it will wait for the event-loop has not done anything in some time, it will be the last.

Related

After sending each message there becomes 2 more messages using socket io

I have a React website.
I receive messages like this:
useEffect(() => {
socket.on('message', message => {
console.log(message)
})
}, [socket])
I send messages like this:
socket.emit('chatMessage', { message, id })
Server side:
socket.on('chatMessage', ({ message }) => {
socket.broadcast.emit('message', message)
})
First time there is 2 message (1 for the user who sent it), the next time there is 4, 6, 8 and so on.
Cleaning up the connections from the previous renders
useEffect(() => {
let isValidScope = true;
socket.on('message', message => {
console.log(message)
// if message received when component unmounts
// stop executing the code
if (!isValidScope) { return; };
// if you need to access latest state, props or variables
// without including them in the depedency array
// i.e you want to refer the variables without reseting the connection
// use useRef or some custom solution (link below)
})
return () => {
// cleanup code, disconnect
// socket.disconnect()
isValidScope = false;
}
}, [socket])
more about useEffect life cycle, to get an idea why
A new effect is created after every render
How the cleanup for previous effect occurs before executing of current useEffect
You can read about why isValid is set synchronizing with effects
Why it was running 3 times in dev mode
If you are intererested in taking a deep dive, consider reading a blog post by Dan on useEffect, its old but helps to build a good mental model about useEffects and functional components.
useEvent can solve the problem but it is in RFC
you can check my question about a implementation to build a custom useEvent till it becomes stable
Hope it helps, cheers
The problem with your code is you assume your component will never be recreated. But React does not provide such guarantees. And if you will add logging at the place when you open a socket, you will notice that for the first render it will be called 2 times. And because you do not have cleanup code the socket remains open even after the component is destroyed. Thus duplicated messages.
Furthermore it would seem that your component is recreated on every message, which multiplies the existing effect of duplication.
The solution in your case would be to close the connection in cleanup part of the effect.

Why does my react tests fail in CI-pipeline due to "not wrapped in act()", while working fine locally?

I have a test-suite containing 37 tests that are testing one of my views. Locally, all tests pass without any issues, but when I push my code, the test-suite fails in our pipeline (we are using GitLab).
The error-output from the logs in CI are extremely long (thousands of lines, it even exceeds the limit set by GitLab). The error consists of many "not wrapped in act()"-, and "nested calls to act() are not supported"-warnings (Moslty triggered by useTranslation() from I18Next and componens like Tooltip from Material-UI).
My guess is that async-data from the API (mocked using msw) triggers a state-update after a call to act() has completed, but I'm not sure how to prove this, or even figure out what tests are actually failing.
Has anyone experienced something similar, or knows what's up?
Example of a failing test:
it.each([
[Status.DRAFT, [PAGE_1, PAGE_11, PAGE_2, PAGE_22, PAGE_3]],
[Status.PUBLISHED, [PAGE_1, PAGE_12, PAGE_2, PAGE_21, PAGE_22, PAGE_221]],
])('should be possible to filter nodes by status %s', async (status, expectedVisiblePages) => {
renderComponent();
await waitFor(() => {
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
});
userEvent.click(screen.getByLabelText('components.FilterMenu.MenuLabel'));
const overlay = await screen.findByRole('presentation');
await waitFor(() => expect(within(overlay).queryByRole('progressbar')).not.toBeInTheDocument());
userEvent.click(within(overlay).getByText(`SiteStatus.${status}`));
userEvent.keyboard('{Esc}');
const items = await screen.findAllByRole('link');
expect(items).toHaveLength(expectedVisiblePages.length);
expectedVisiblePages.forEach((page) => expect(screen.getByText(page.title)).toBeInTheDocument());
});
Update 1
Okay. So I've narrowed it down to this line:
const items = await screen.findAllByRole('link');
There seems to be a lot of stuff happening while waiting for things to appear. I believed that the call to findAllByRole was already wrapped in act() and that this would make sure all updates has been applied.
Update 2
It seems to be a problem partly caused by tests timing out.
I believe multiple calls to waitFor(...) and find[All]By(...) in the same test, in addition to a slow runner, collectively exceeds the timout for the test (5000ms by default). I've tried to adjust this limit by running the tests with --testTimeout 60000. And now, some of the tests are passing. I'm still struggling with the "act()"-warnings. Theese might be caused by a different problem entirely...
The bughunt continues...
After many attempts, I finally found the answer. The CI-server only has 2 CPUs available, and by running the tests with --maxWorkers=2 --maxConcurrent=2, instead of the default --maxWorkers=100% --maxConcurrent=5, proved to solve the problem.
This is a common issue ;)
I guess, you see this problem on CI Server because of the environment (less cpu/mem/etc).
This warning is because you do some async action but did not finish for complete it (because it's async).
You can read more about this issue in this article: https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning
The best solution is waiting for the operation to finish. For example by adding loading indicator and waiting for element remove.
For example:
it('should show empty table', async () => {
const [render] = createRenderAndStore()
mockResponse([])
const { container } = render(<CrmClientsView />) // - this view do async request in first render
await waitForElementToBeRemoved(screen.queryByRole('test-loading'))
await waitFor(() => expect(container).toHaveTextContent('There is no data'))
})

React log final state value after delay

I have a MERN app. On the react side, I have a state. This state may or may not change many times a second. When the state is updated, I want to send the state to my back end server API so I can save the value in my mongodb. This state can possibly change hundreds of times a second which I wish to allow. However, I only want to send this value to my server once every 5 seconds at most. This is to avoid spam and clogging my mongodb Atlas requests.
Currently, I have tried setInterval, setTimeout and even locking cpu with a while(time<endTime).
These have all posed an issue:
The setInterval is nice since I could check the currentValue with the lastSentValue and if they do not equal (!==) then I would send the currentValue to my server. Unfortunately, when I set interval, it returns the initial value that was present when the setInterval was called.
If you know how I can let a user spam a boolean button while only sending updates at most once every 5 seconds from the front end (React) to the back end (Node) and that it sends the current and up to date value then please share your thoughts and I will test them as soon as possible.
My state value is stored as::
const [aValue, anUpdate] = useState(false);
The state is changed with an onClick method returned in my React app.
function Clicked(){
anUpdate(!aValue);
}
My set interval test looked like this::
//This is so that the button may be pressed multiple times but the value is only sent once.
const [sent, sentUpdate] = useState(false);
//inside of the clicked method
if(!sent){
sentUpdate(true);
setInterval(()=>{
console.log(aValue);
},1000);
}
My setTimeout is very similar except I add one more sentUpdate and reset it to false after aValue has been logged, that way I can log the timeout again.
//setInterval and setTimeout expected results in psudocode
aValue=true
first click->set aValue to !aValue (now aValue=false), start timeout/interval, stop setting timeouts/interval until completed
second click->set aValue to !aValue (now aValue=true), do not set timeout/interval as it is still currently waiting.
Completed timeout/interval
Log->false
//expected true as that is the current value of aValue. If logged outside of this timeout then I would receive a true value logged
In quite the opposite direction, another popular stackOverflow answer that I stumbled upon was to define a function that used a while loop to occupy computer time in order to fake the setTimeout/setInterval.
it looked like this::
function wait(ms){
let start = new Date().getTime();
let end = start;
while(end < start + ms) {
end = new Date().getTime();
}
}
Unfortunately, when used inside of the aforementioned if statement (to avoid spam presses) my results were::
aValue=true
first click->set aValue to !aValue (now aValue=false), start wait(5000), turn off if statement so we don't call many while loops
second click->nothing happens yet - waiting for first click to end.
first click timeout->logged "false"->if statement turned back on
second click that was waiting in que is called now->set aValue to !aValue (now aValue=true), start wait(5000), turn off if statement so we don't call many while loops
second click timeout->logged "true"->if statement turned back on
So the while loop method is also not an option as it will still send every button press. It will just bog down the client when they spam click.
One more method that I saw was to use a Promise to wrap my setTimeout and setInterval however that in no way changed the original output of setTimeout/setInterval.
It looked like this::
const promise = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(true);
},5000);
});
promise.then(console.log(aValue));
//I also tried resolve(aValue)->promise.then(val=>console.log(val));
For what you are trying to do with looping and starting intervals in the callback will only ever close over a specific state value fo reach iteration, i.e. the initial state value.
The solution is to use an useEffect hook to handle or "listen" for changes to a dependency value. Each time the state updates a component rerender is triggered and the useEffect hook is called, and since the dependency updated, the hook's callback is called.
useEffect(() => {
sendStateToBackend(state);
}, [state]);
If you want to limit how often sendStateToBackend is actually invoked then you want to throttle the call. Here's an example using lodash's throttle Higher Order Function.
import { throttle } from 'lodash';
const sendStateToBackend = throttle((value) => {
// logic to send to backend.
}, 5000);
const MyComponent = () => {
...
useEffect(() => {
sendStateToBackend(state);
}, [state]);
...
Update
If you want to wait until the button is clicked to start sending updates to the backend then you can use a React ref to track when the button is initially clicked in order to trigger sending data to backend.
const clickedRef = React.useRef(false);
const [aValue, anUpdate] = React.useState(false);
React.useEffect(() => {
if (clickedRef.current) {
sendToBackend(aValue);
}
}, [aValue]);
const clicked = () => {
clickedRef.current = true
anUpdate(t => !t);
};
...
See also Lodash per method packages since package/bundle size seems a concern.
So I had a brain blast last night and I solved it by creating a series of timeouts that cancel the previous timeout on button click and set a new timeout with the remaining value from the last timeout.
const [buttonValue, buttonUpdate] = useState(props.buttonValue);
const [lastButtonValue, lastButtonUpdate] = useState(props.buttonValue);
const [newDateNeededValue, newDateNeededUpdate] = useState(true);
const [dateValue, dateUpdate] = useState();
const [timeoutValue, timeoutUpdate] = useState();
function ButtonClicked(){
let oldDate = new Date().getTime();
if(newDateNeededValue){
newDateNeededUpdate(false);
dateUpdate(oldDate);
}else{
oldDate = dateValue;
}
//clear old timeout
clearTimeout(timeoutValue);
//check if value has changed -- value has not changed, do not set timeout
if(lastButtonValue === !buttonValue){
console.log("same value do not set new timout");
buttonUpdate(!buttonValue);
return;
}
//set timeout
timeoutUpdate(setTimeout(()=>{
console.log("timed out");
lastButtonUpdate(!buttonValue);
newDateNeededUpdate(true);
//This is where I send to my previous file and send to my server with axios
props.onButtonUpdate({newVal:!buttonValue});
clearTimeout(timeoutValue);
}, (5000-(new Date().getTime() - oldDate))));
}

React - use Dependencies in useEffect without trigger the effect itself when the Dependency change

I have a react state
const [list, setList] = useState([])
and a react effect that is triggered when the list is modified, and do some work with the first element in the list:
useEffect( () => {
if(list.length <= 0) return
//Do something with the first element of the list
//Remove first element of the list
}, [list])
In this way, the effect trigger itself n times where n is the number of the element in the list.
Then i have another method in my component that insert elements in the list with the setList() method, let's call it
insertElemInList = () => {
//insert one or more elements in list
}
when insertElemInList is called, the useEffect trigger and start working for n times.
I don't know how many times the insertElemInList() is called, and how many elements is inserted every time, since this method is called after some actions of the user on the page.
So if an user call the insertElemInList() two or more times, before the last iteration of the effect is finished, then the effect trigger in the wrong way, in fact it will activate due to the change of state given by insertElemInList, but also by itself, resulting in more iterations and wrong behaviour.
So i'm trying to figure out how to use something inside the effect that doesn't trigger the effect itself, but can be used correctly.
for example I was thinking of modifying the effect and the state adding
const [semWait, setSem] = useState(1)
and then, continue to update the list state with the insertElemInList() method, but now:
useEffect( () => {
let doSomething = () => {
if(list.length < 0) return
//Do Something with the first element of the list
//Remove first element from the list
if(list.length > 0) doSomething()
}
doSomething()
setSem(1)
}, [semWait])
insertElemInList = () => {
//insert one or more elements in list
if(semWait == 1) setSem(0)
}
the above code is just an example of how I can solve the problem, I don't think it is the best solution and I gave you this example just to make you understand what I would like to do.
however, as you can see in this way I could add as many value as i want to my state whith insertElemInList() ​​and trigger the effect only if it is not already active (in other word, only if the semaphore is reset by the effect itself). However, I know it's not a good thing to use a state in the effect, without including it in dependencies, and if i add the state list as dependency of the useEffect the problem return.
the problem is that I can't figure out how to use a value inside useEffect without including it in the dependency
EDIT:
sorry for the late reply, i tried to implement this code on my own but there are workflow problems in my work, i'll try to explain the problems:
the code is a snippet to download some file from an API, the user on the site have a list of files to download, he can click on the files to download them as many times as he wants. my intent is to create a request queue, so as not to send too many requests to the server.
the code below show my work, i've inserted some comments to let you figure out how my code should work:
const [queue, setQueue] = useState({
"op_name": "NO_OP"
})
//file download function
let requestFileDownload = (fileId) => {
/*
This function construct the object to put in queue state and call the method 'insertInQueue'
*/
let workState = appState
insertInQueue({
"op_name": "DOWNLOAD_FILE",
"file_id": fileId,
"username": workState.user.username,
"token": workState.user.token
})
}
//Function insertInQueue to insert an element in the queue
let insertInQueue = (objQueue) => {
//Some control to check if the object passed exist, and have valid fields
if (!objQueue || !objQueue.op_name || objQueue.op_name === "NO_OP") return //nothing to insert in queue
//calling method to insert in timeline div, this work only whith front-end dom elements (full synchronous)
insertElemInTimeline(objQueue.op_name)
//setting timeout in which try to insert the object passed in queue
setTimeout(function run() {
let workQueue = queue //gettind queue object
if (workQueue && workQueue.op_name === "NO_OP") {
/*
if queue exist and the op_name is "NO_OP", this mean that the previus operations on the queue
is finished, so we can start this operation
*/
setQueue(objQueue) //set the queue with the object passed as paramether to trigger the effect
return;
}
// if the queue op_name was != "NO_OP" call the function again for retry to insert in 1 second
setTimeout(run, 1000)
}, 0)
}
//Effect triggered when queue object change
useEffect(() => {
if (queue.op_name === "NO_OP") return //no operation to do
//Effective file download
let downloadFileEffect = async () => {
let objQueue = queue //getting queue state
//Two functions to download the element by calling backend api
let downloadFileResponse = await downloadFile(objQueue.file_id, objQueue.username, objQueue.token)
download(downloadFileResponse.data, downloadFileResponse.headers['x-suggested-filename'])
//after the method have completed, i can set a new state for the queue with "op_name": "NO_OP"
let appoStateQueue = {
"op_name": "NO_OP"
}
setQueue(appoStateQueue)
//method for remove the element from the dom
removeElemFromTimeline()
}
//calling function to trigger the donwload.
downloadFileEffect()
}, [queue])
now the problem is, that when i try to reset the queue state in the effect, when i call:
let appoStateQueue = {
"op_name": "NO_OP"
}
setQueue(appoStateQueue)
the queue is not resetted in the case the user have clicked two download one after the first is running.
In fact the queue stops with the first object inserted in it, and is not reset by the effect, so the second download never starts, because it sees forever the queue occupied by the first download.
In case user click one download, then wait for the download, and only then click the second, then there's no problem, and the queue is resetted correctly by the effect
First, useEffect doesn't run for "trigger itself n times where n is the number of the element in the list". useEffect will run every time list changes in length or resides in a different memory space than it did in a previous render. This is how "shallow" comparison works with javascript objects in react. Your main issue is that you are changing your dependency from within the effect. This means that while the effect runs, it updates the dependency and forces it to run again and again and again...memory leak!
Your solution might work, but as you stated is not best practice. A better solution (imo) would be to allow for a "parsedList" state that can be the end result of parsing the list. Let the source of truth with the list only be impacted by the client interaction. You monitor these changes and change your parsedList based on these changes.

Akka.net - Streams with parallelism, backpressure and ActorRef

Tying to learn how use Akka.net Streams to process items in parallel from a Source.Queue, with the processing done in an Actor.
I've been able to get it to work with calling a function with Sink.ForEachParallel, and it works as expected.
Is it possible to process items in parallel with Sink.ActorRefWithAck (as I would prefer it utilize back-pressure)?
About to press Post, when tried to combine previous attempts and viola!
Previous attempts with ForEachParallel failed when I tried to create the actor within, but couldn't do so in an async function. If I use an single actor previous declared, then the Tell would work, but I couldn't get the parallelism I desired.
I got it to work with a router with roundrobin configuration.
var props = new RoundRobinPool(5).Props(Props.Create<MyActor>());
var actor = Context.ActorOf(props);
flow = Source.Queue<Element>(2000,OverflowStrategy.Backpressure)
.Select(x => {
return new Wrapper() { Element = x, Request = ++cnt };
})
.To(Sink.ForEachParallel<Wrapper>(5, (s) => { actor.Tell(s); }))
.Run(materializer);
The Request ++cnt is for console output to verify the requests are being processed as desired.
MyActor has a long delay on every 10th request to verify the backpressure was working.

Resources