Race condition being set up by React.StrictMode double firing functions - javascript

I think I understand why React.StrictMode causes functions to be called twice. However, I have a useEffect that loads data from my api:
useEffect(() => {
async function fetchData() {
const data = await getData();
setData(data);
}
fetchData();
}, []);
In my getData() function I call a maintenance script cullRecords() that cleans up my data by deleting records over a certain age before returning the data:
async function getData(){
let results = await apiCall();
cullRecords(results);
return results;
}
Here's the rub: React.StrictMode fires the getData() function twice, loading up the apiCall() twice and firing the cullRecords() twice. However, by the time the second cullRecords() subscript fires, my API throws an error because those records are already gone.
While it's not the end of the world, I'm curious if I'm doing something wrong, or if this is just a fringe case, and not to worry about it.

You can read through here also:
https://beta.reactjs.org/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development
The same issue can occur if the user leaves/visits a route quickly for example (which is what development mode is simulating here). It might not be the the best approach to call a backend maintenance script when a UI component is being rendered.

As per the race condition happening on APIs, it’s useful to implement debouncing methods as it’s explaned below.
The debounce() function forces a function to wait a certain amount of
time before running again. The function is built to limit the number
of times a function is called.
You can either use debouncing on either server-side or client side as they have pretty similar implementation.

Related

Understanding Code Flow For Setting State in React

I have this basic code which updates a state from a handler:
const wait = (ms) => {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
};
export default function App() {
async function handleClick() {
setData(1);
console.log("first click");
console.log(data);
await wait(1000);
setData(2);
console.log("second click");
console.log(data);
}
const [data, setData] = React.useState(0);
return (
<div>
{data}
<button onClick={() => handleClick(setData)}>Click Me</button>
</div>
);
}
I am trying to understanding the order of operations, could someone please verify or point me in the right direction of what is happening? I have researched around but haven't found conclusive sources on what I think is happening.
we click the button, triggering the handler
the handler runs setData(1), enqueuing a task to the event loop
console.log('first click') runs
we log the state (data), which is still 0, as the setter has only been enqueued
we run into the wait function, so we exit out to the synchronous code flow as we wait for the 1000ms
the sync code finishes running, so we dequeue the next task, which is the setter function, the state is now set to 1, and the view re-renders and reflects that new state
after 1 second has elapsed, we return to the code after wait function
setData(2) enqueues another task
'second click' is logged
0 is stil logged, as our local state has not changed
the sync code finishes, we dequeue the setter, re-rendering the view, causing 2 to show up
Is this all correct? Have I misunderstood anything? Thanks for any help.
Yes, you've got this down correctly, except possibly for the bit
runs setData(1), enqueuing a task to the event loop
This may or may not involve the event loop. What happens in setData is specific to react.js, and won't necessarily enqueue a task in the browser event loop. It certainly does "somehow" schedule the state update and re-rendering of the component - within react.
If I remember correctly, react.js does schedule the render caused by setData(1) for the end of the native click handler, i.e. when the call to your onClick handler returns to react code. And for setData(2), the rendering might actually happen synchronously within the setData call, as react does not control the context. This is however subject to change (the devs are talking about batching multiple updates together asynchronously) and should be considered an implementation detail. If you're curious, just place a console.log in the render function of your component, but do not write any code that relies on the order observed in this test!

How can I use AbortController in Next js?

My application allows users to do searches and get suggestions as they type in the search box. For each time that the user enters a character, I use 'fetch' to fetch the suggestions from an API. The thing is that if the user does the search fast, he can get the result before the suggestions are fetched. In this case, I want to cancel the fetch request.
I used to have the same application in React and I could easily cancel the request using AbortController, but that isn't working in Next js.
I did some research and I think the problem is happening because Next doesn't have access to AbortController when it tries to generate the pages.
I also had this problem when I tried to use 'window.innerWidth' because it seems Next doesn't have access to 'window' either.
The solution I found was to use 'useEffect'. It worked perfectly when I used it with 'window'.
const [size, setSize] = useState(0)
useEffect(() => {
setSize(window.innerWidth)
}, [])
But it isn't working when I use AbortController. First I did it like this:
let suggestionsController;
useEffect(() => {
suggestionsController = new AbortController();
},[])
But when I tried to use 'suggestionsController', it would always be undefined.
So I tried to do the same thing using 'useRef'.
const suggestionsControllerRef = useRef(null)
useEffect(() => {
suggestionsControllerRef.current = new AbortController();
},[])
This is how I'm fetching the suggestions:
async function fetchSuggestions (input){
try {
const response = await fetch(`url/${input}`, {signal: suggestionsControllerRef.current.signal})
const result = await response.json()
setSuggestionsList(result)
} catch (e) {
console.log(e)
}
}
And this is how I'm aborting the request:
function handleSearch(word) {
suggestionsControllerRef.current.abort()
router.push(`/dictionary/${word}`)
setShowSuggestions(false)
}
Everything works perfectly for the first time. But if the user tries to do another search, 'fetchSuggestions' function stops working and I get this error in the console 'DOMException: Failed to execute 'fetch' on 'Window': The user aborted a request'.
Does anyone know what is the correct way to use AbortController in Next js?
The solution I found to the problem was create a new instance of AbortController each time that the user does the search. While the suggestions were being displayed, 'showSuggestions' was true, but when 'handleSearch' was called, 'showSuggestions' was set to false. So I just added it as a dependency to useEffect.
useEffect(() => {
const obj = new AbortController();
setSuggestionController(obj)
},[showSuggestions])
I also switched from useRef to useState, but I'm not sure if that was necessary because I didn't test this solution with useRef.
I don't know if that is the best way of using AbortController in Next js, but my application is working as expected now.
I suppose you can try an abort controller to cancel your requests if the user stops typing, but this is not the standard way of solving this common problem.
You want to "debounce" the callback that runs when the user types. Debouncing is a strategy that essentially captures the function calls and "waits" a certain amount of time before executing a function. For example, in this case you might want to debounce your search function so that it will only run ONCE, 500 ms after the user has stopped typing, rather than running on every single keypress.
Look into debouncing libraries or write a debounce function yourself, but fair warning it can be pretty tricky at first!

Why does this official React testing recipe using await/act/async actually work?

I've been working with Javascript for a couple of years now, and with my current knowledge of the event loop I'm struggling to understand why this testing recipe from the React docs work. Would someone be able to break down exactly what happens in each step there? To me, it seems magical that this works in the test:
await act(async () => {
render(<User id="123" />, container);
});
// expect something
The component looks like this (copying in case that link gets deprecated):
function User(props) {
const [user, setUser] = useState(null);
async function fetchUserData(id) {
const response = await fetch("/" + id);
setUser(await response.json());
}
useEffect(() => {
fetchUserData(props.id);
}, [props.id]);
if (!user) {
return "loading...";
}
return (
<details>
<summary>{user.name}</summary>
<strong>{user.age}</strong> years old
<br />
lives in {user.address}
</details>
);
}
There's no implicit or explicit return happening on the render, so how does act know to await the async stuff happening in the component (fetching etc)?
To me, this would make more sense:
await act(async () => render(<User id="123" />, container));
or (which is the same thing):
await act(async () => {
return render(<User id="123" />, container);
});
or even:
await act(render(<User id="123" />, container));
But that doesn't seem to be how people use it, or how it was intended to be used, so I'm a bit lost. I've seen the same examples with enzymes mount.
I don't want to create a fragile test, so I really want to understand this.
Does it have something to do with the callback being async i.e. does that append something to the event loop last, making it so that the await waits for everything inside render to happen before resolving?
I'm drawing a blank here and am struggling in the react doc jungle, because everyone seems to use this pattern, but no one really explains why or how it works.
Thanks for the help in advance!
When looking closer at the source code of react-dom and react-dom/test-utils it seems like what's making this whole thing work is this setImmediate call happening after the first effect flush in recursivelyFlushAsyncActWork.
It seems like act chooses to use this recursivelyFlushAsyncActWork simply because the callback has the signature of being "thenable", i.e. a Promise. You can see this here.
This should mean that what happens is (simplified) this:
The useEffect callback is flushed (putting fetch on the event loop).
The setImmediate callback "ensures" our mock promise / fetch is resolved.
A third flush happens by a recursion inside the setImmediate callback (called by enqueueTask) making the state changes appear in the DOM.
When there's nothing left to flush it calls the outer most resolve and our act resolves.
In the code that looks kinda like this (except this is taken from an older version of react-dom from the node_modules of my React project, nowadays flushWorkAndMicroTasks seems to be called recursivelyFlushAsyncActWork):
function flushWorkAndMicroTasks(resolve) {
try {
flushWork(); // <- First effect flush (fetch will be invoked by the useEffect?)
enqueueTask(function () { // <- setImmediate is called in here (finishes the fetch)
if (flushWork()) { // <- Flush one more time and the next loop this will be false
flushWorkAndMicroTasks(resolve);
} else {
resolve(); // <- resolve is called when flushWork has nothing left to flush.
}
});
} catch (err) {
resolve(err);
}
}
Additional info (update)
Unless I'm mistaken this should mean that await act(async () => { render(...); }); "only" awaits one event loop unless a new task is added by the latest flush. I.e. it's possible to add promises recursively if there's a flush in between, also micro tasks such as promise chains will probably resolve during the first loop (since they are technically "blocking" (source)).
That means that if you add a timer or something else in your mock or React code that might naturally need multiple loops to resolve, it's not gonna be awaited because the "after" event is not captured before resolving since the then listeners are not attached / returned to the outer promise (please correct me if I'm wrong!).

Is there a well-established way to update local state immediately without waiting for an API response in React/Redux?

TL;DR: Is there some well-known solution out there using React/Redux for being able to offer a snappy and immediately responsive UI, while keeping an API/database up to date with changes that can gracefully handle failed API requests?
I'm looking to implement an application with a "card view" using https://github.com/atlassian/react-beautiful-dnd where a user can drag and drop cards to create groups. As a user creates, modifies, or breaks up groups, I'd like to make sure the API is kept up to date with the user's actions.
HOWEVER, I don't want to have to wait for an API response to set the state before updating the UI.
I've searched far and wide, but keep coming upon things such as https://redux.js.org/tutorials/fundamentals/part-6-async-logic which suggests that the response from the API should update the state.
For example:
export default function todosReducer(state = initialState, action) {
switch (action.type) {
case 'todos/todoAdded': {
// Return a new todos state array with the new todo item at the end
return [...state, action.payload]
}
// omit other cases
default:
return state
}
}
As a general concept, this has always seemed odd to me, since it's the local application telling the API what needs to change; we obviously already have the data before the server even responds. This may not always be the case, such as creating a new object and wanting the server to dictate a new "unique id" of some sort, but it seems like there might be a way to just "fill in the blanks" once the server does response with any missing data. In the case of an UPDATE vs CREATE, there's nothing the server is telling us that we don't already know.
This may work fine for a small and lightweight application, but if I'm looking at API responses in the range of 500-750ms on average, the user experience is going to just be absolute garbage.
It's simple enough to create two actions, one that will handle updating the state and another to trigger the API call, but what happens if the API returns an error or a network request fails and we need to revert?
I tested how Trello implements this sort of thing by cutting my network connection and creating a new card. It eagerly creates the card immediately upon submission, and then removes the card once it realizes that it cannot update the server. This is the sort of behavior I'm looking for.
I looked into https://redux.js.org/recipes/implementing-undo-history, which offers a way to "rewind" state, but being able to implement this for my purposes would need to assume that subsequent API calls all resolve in the same order that they were called - which obviously may not be the case.
As of now, I'm resigning myself to the fact that I may need to just follow the established limited pattern, and lock the UI until the API request completes, but would love a better option if it exists within the world of React/Redux.
The approach you're talking about is called "optimistic" network handling -- assuming that the server will receive and accept what the client is doing. This works in cases where you don't need server-side validation to determine if you can, say, create or update an object. It's also equally easy to implement using React and Redux.
Normally, with React and Redux, the update flow is as follows:
The component dispatches an async action creator
The async action creator runs its side-effect (calling the server), and waits for the response.
The async action creator, with the result of the side-effect, dispatches an action to call the reducer
The reducer updates the state, and the component is re-rendered.
Some example code to illustrate (I'm pretending we're using redux-thunk here):
// ... in my-component.js:
export default () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(MyActions.UpdateData(someDataFromSomewhere));
});
return (<div />);
};
// ... in actions.js
export const UpdateData = async (data) => (dispatch, getStore) => {
const results = await myApi.postData(data);
dispatch(UpdateMyStore(results));
};
However, you can easily flip the order your asynchronous code runs in by simply not waiting for your asynchronous side effect to resolve. In practical terms, this means you don't wait for your API response. For example:
// ... in my-component.js:
export default () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(MyActions.UpdateData(someDataFromSomewhere));
});
return (<div />);
};
// ... in actions.js
export const UpdateData = async (data) => (dispatch, getStore) => {
// we're not waiting for the api response anymore,
// we just dispatch whatever data we want to our reducer
dispatch(UpdateMyStore(data));
myApi.postData(data);
};
One last thing though -- doing things this way, you will want to put some reconciliation mechanic in place, to make sure the client does know if the server calls fail, and that it retries or notifies the user, etc.
The key phrase here is "optimistic updates", which is a general pattern for updating the "local" state on the client immediately with a given change under the assumption that any API request will succeed. This pattern can be implemented regardless of what actual tool you're using to manage state on the client side.
It's up to you to define and implement what appropriate changes would be if the network request fails.

Process timeout | Amazon Lambda to Firebase

i've written code in node.js and my data is on Firebase. The problem i'm facing is that my code never exits. I've done it like this one Link
The problem is that firebase referance/listener never become null and therefore my function never exits. I tried using firebase.database().goOffline() but it didn't work.
On my local machine i forcefully stopped the process using process.exit(0), but when i deployed my code on AWS lambda, it doesn't return any response/call back and exits (giving error message "Process exited before completing request")
I also added wait of 5-10 seconds after invoking callback in lambda and then forcefully exited the process, but it didn't help either.
How to fix this issue? Please help.
Your going through crisis that any new lambda user has gone.
As suggested, you can use context.done for stopping.
However, this is not recommended as this is only possible due to historic runtime versions of nodejs.
why this timeout happens?
Your lambda may get to the last line of your code and still keep running. Well, it is actually waiting for something - for the event loop to be empty.
what this means?
In nodejs, when you make an async operation and register a callback function to be executed once the operation is done, the registration sort of happens in the event loop.
In one line, it's the event loop that knows which callback function to execute when an async operation ends. But that's to another thread :)
back to Lambda
Given the above information, it follows that lambda should not halt before empty event loop is reached - as this means some follow-up procedure will not execute after some async operation returns.
What if you still need to halt the execution manually? regardless of the event loop status?
At the beginning of the function, execute:
context.callbackWaitsForEmptyEventLoop = false
And then use the third parameter you get in the handler signature. Which is the callback.
the callback parameter
It is a function which you call when you want to end the execution.
If you call it with no parameters, or with the first parameter as null and text as second parameter - it is considered as a successful invocation.
To fail the lambda execution, you can call the callback function with some non-null value as the first parameter.
Add this line at the beginning of your handler function and then you should be able to use the callback without issue:
function handler (event, context, callback) {
context.callbackWaitsForEmptyEventLoop = false // Add this line
}
Setting callbackWaitsForEmptyEventLoop to false should only be your last resort if nothing else works for you, as this might introduce worse bugs than the problem you're trying to solve here.
This is what I do instead to ensure every call has firebase initialized, and deleted before exiting.
// handlerWithFirebase.js
const admin = require("firebase-admin");
const config = require("./config.json");
function initialize() {
return admin.initializeApp({
credential: admin.credential.cert(config),
databaseURL: "https://<your_app>.firebaseio.com",
});
}
function handlerWithFirebase(func) {
return (event, context, callback) => {
const firebase = initialize();
let _callback = (error, result) => {
firebase.delete();
callback(error, result);
}
// passing firebase into your handler here is optional
func(event, context, _callback, firebase /*optional*/);
}
}
module.exports = handlerWithFirebase;
And then in my lambda handler code
// myHandler.js
const handlerWithFirebase = require("../firebase/handler");
module.exports.handler = handlerWithFirebase(
(event, context, callback, firebase) => {
...
});
Calling callbackfunciton and then process.exit(0) didn't help in my case. goOffline() method of firebase didn't help either.
I fixed the issue calling context.done(error, response) (instead of callback method). Now, my code is working.
Still, if any one have better solution, kindly post here. It may help some one else :)

Categories