RTK Query Mutation in StrictMode - reactjs

I use RTK Query for data fetching and I am having a small issue in one of the use cases.
I use a mutation to verify the email address of the app user, as follows:
// ...imports
const ConfirmEmail = () => {
const [params] = useSearchParams();
const [confirmEmail, { isLoading, isSuccess, isUninitialized, error }] = useConfirmEmailMutation();
useEffect(() => {
if (isUninitialized) {
confirmEmail({
email: params.get('email'), // from the verification link
code: params.get('code'), // from the verification link
})
.unwrap()
.then(() => {
// handling success...
})
.catch((e) => {
// handling error...
});
}
});
// ...other component code
}
The problem is that with StrictMode the mutation is running twice in development and it is causing a concurrency error at the API side. I see two network requests in the dev tools one is successful and the other is not and depending on which one runs first the component is showing inconsistent result.
I am aware that this will only happen during development and I tried to follow the instructions in the official react documentation and I tried to use fixedCacheKey as described here but I wasn't able to make this work with RTK Query so that I get a consistent result during development.
Is there a way, or am I missing something?

That's pretty much the point of this strictness check though: to show you that you are automatically doing a problematic api call.
In the future, React can choose to execute a useEffect with an empty dependency array on more occasions than just on first mount - for example in combination with the offscreen features they are working on.
This just tells you in advance that you are doing this in probably a dangerous place.
Can you maybe incorporate this api call in some kind of actual user interaction like a button click? That would be a lot safer going forward.

Related

Is there any benefit of using React Query when response is not being returned from the component?

I am wondering if there is any benefit of using React Query instead of the traditional following approach in cases where the response does not target the output of the component.
useEffect( () => {
const callback = async () => {
const env = await axios.get(`/api/environment`).then(res=>res.data);
window.localUrl = env.url;
};
callback();
}, []);
The above code assigns an API response to a global variable. Would it be beneficial to use React Query in that case and why?
P.S. Please do not make any comments about the usage of global variables. Take is as an example.
There's no significant benefit to using React Query instead of the traditional approach for this particular case, as the response does not directly target the output of the component. However, if you plan on using the API response in multiple places or if you need to handle API errors and loading states, React Query can provide a convenient and centralized way to manage API requests in your React components. Additionally, React Query has features for caching and optimizing API requests, which can improve the performance and UX of your application.

FetchBaseQuery API Call management

I am making a fetchBaseQuery api call with const response = useQuery(request)
Then, I have a useEffect in place which doesSomethings when response is fetched
React.useEffect(() => {
if (!!response.data) {
// do Something
}
}
}, [response.data]);
Then I also have a clear button which clears the UI. So what happens is, if the user clears the UI, and the data is fetched for the same request through useQuery it returns results from cache, but the useEffect doesn't gets executed due to which it doesn't get displayed on the UI.
Can anyone please help me with the optimal solution to fix this. Actually I have been stuck at many such similar problems but not able to find a proper solution for the same.
I have already tried using, JSON.stringify(response.data) or a passing response.isFetching as well in the dependency array but these hacks doesn't cover all test cases

InfiniteLoop in useEffect when one of the dependency is a function from useContext

2021 UPDATE
Use a library that makes requests and cache them - react-query, swr, redux-toolkit-query
ORIGINAL QUESTION
I've been struggling with this for quite a long time and didn't find an answer.
I have a component that is the last step of some registration process during which I ask a user to enter its data through several forms. In this component, I send collected data to API. If the request is successful I show ok, if not I show error.
I have useEffect that sends the data. The function that performs this task lives in a context
const { sendDataToServer } = useContext(context)
useEffect(() => {
const sendData = async () => {
setLoading(true)
await sendDataToServer(...data)
setLoading(false)
}
sendData()
}, [sendDataToServer, data])
If I include sendDataToServer in the dependencies list this useEffect would go into an infinite loop, causing endless rerendering. I suppose this is because a reference to the function has a different value on every render.
I can of course redesign the app and do not keep the function in the context, but I do like it and don't consider it a bad practice (correct me if I am wrong)
So what are my options here? How do I keep the flow with the context API, but use useEffect with the correct list of dependencies?
You're right with your guess, that's why we got useCallback for referential equality.
You didn't post the sendDataToServer function, but it should look something like this with useCallback:
const sendDataToServer = useCallback(data => {
// your implementation
}, [your, dependencies])
After that you can safely use it in your useEffect.
I highly recommend Kent C. Dodd's blog posts: When to useMemo and useCallback
Smartassing now: If it's only purpose is sending data to the server (and not changing the app's state), I don't know why it should be part of the context. It could be a custom hook or even a static function.
Btw: There could be another problem: If the data dependency in your useEffect is changed when executing sendDataToServer, you will still have an endless loop (e. g. when you fetch the new data after executing sendDataToServer), but we can't see the rest of the code.

Why does async work in ComponentDidMount lead to visual lag when navigating unless a simple timeOut is awaited?

I have a tabbed react native app and have 2 related questions:
Why does the navigation between screens visually lag when doing async work (such sending a network request) in the target view's componentDidMount method? The official documentation mentions this method as the appropriate place for handling network requests but even when componentDidMount and the functions it calls are declared to be async () => the visual delay when initiating navigation is glaring. This visual delay occurs at the beginning of the navigation, before the target component is pushed or focused and occurs even when the async work does not modify the app state in any way that would affect the rendering of the target component.
Why does this visual delay disappear entirely when I await a brief timeout before doing the real async work?
const timeOut = t => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`Completed in ${t}`);
}, t);
});
};
componentDidMount = async () => { // async or not, delay occurs
await timeOut(0); //This line prevents navigation lag
NetworkRequest.getData(payload);
}
...
static getData = payload => {
fetch("API_ENDPOINT", {
method: "POST",
body: payload
})
.then(response => response.json())
.then(response => {
//process response
})
}
Whether NetworkRequest.getData is awaited or not, the navigation delay is present without awaiting this artificial mini-promise beforehand. I feel like I'm misunderstanding react native lifecycles or some other key part of the rendering process because I can't imagine why this little await is necessary to 'trick' the method into being visually asynchronous...
EDIT: Now that I know more about Message Queues and Job Queues, it makes sense that a potential source of the visual lag could come from jamming up the Job Queue with a large task. By awaiting a timeOut in an async method, that method effectively becomes the lowest-priority entity in the job queue. It seems weird screen transitions would be non-blocking since the UI is unavailable during this time, but I'm sure the react native developers had a reason... It also seems quite strange that the documentation recommends performing asynchronous work in this method when it will block without this queue relegation voodoo... Is there a better way to specify message/job queue priority on a Javascript Method beyond simple async? Or is awaiting empty timeOuts the only way to indicate lowest-priority processing.
Using expo sdk 34 which uses react-native 0.59
react-navigation 3.11
And React 16.8.3
EDIT: What a coincidence! Experimental support for Suspense for Data Fetching just announced by Dan 5 hours ago :) More here
Seems like your NetworkRequest.getData(payload) function is doing something expensive synchronously. Because JavaScript is single threaded, the next action doesn't happen until this function returns which can cause a lag.
By adding await timeOut(0), you no longer make it synchronous. It's now called in the next event loop which means any synchronous actions before that would finish already.
Why does the navigation between screens visually lag when doing async...
Maybe it can be laggy because you are on dev mode. Please try it on production.
Why does this visual delay disappear entirely when I await a brief timeout before doing the real async work?
I'm not sure what is NetworkRequest.getData doing, I can be something about it, but if it returns a promise, maybe you should try using await on it.
You are also forgetting to add async to your componentDidMount.
async componentDidMount(){
await NetworkRequest.getData(payload);
}

Redux avoid stale data?

I'm trying to implement, let's say Twitter. I'm doing something like
initialState = {
tweets: {id => tweet}
}
then, a user goes to his timeline, now the action fetchTweets fetches all his tweets. However, then he can post a tweet, tweet T. But if I don't manually insert the posted T into state.tweets, he will not see this tweet in his timeline.
So here comes the question, when a user did some actions on his page, is that a good point to refresh the data? How does redux avoid data stale in this kind of case?
Thanks!
It's a bit of a paradigm shift when using Redux, but you don't want any functions to be your state. So, that initial state should actually just be either null or an empty object (depending on the design of the components that receive those props). But to answer your question a bit more directly, if you want to make sure that data stays "fresh" you need to make it happen. There is no magic in Redux, which is a GOOD THING.
However, if you design your code properly, the user shouldn't experience something resembling a full page refresh. At a high level, here is how I might design what you are describing.
Write actions for requesting and receiving tweets, i.e.:
export function requestTweets() => {
return {
type: REQUEST_TWEETS
}
}
export function receiveTweets(tweets) => {
return {
type: RECEIVE_TWEETS,
payload: tweets
}
}
Then wrap that in a "public" function that can be reused wherever:
export const fetchTweets = () => {
return (dispatch, getState) => {
dispatch(requestTweets())
//network code here is pseudocode
fetch('https://tweeter/api/v1/tweets')
.then(data => dispatch(receiveTweets(data))
.catch(err => displayErrorMsg(err))
}
}
Then in your action handlers just reduce the tweets into the next state.
Now with fetchTweets, you can call that on the first load, after posting or on an interval. The nice thing is React will handle the diffing well for you and not re-render the entire page. It's just up to you to design it well so the user notices when and where new tweets come in.
Hope that helps!
Redux manages your state for you. It tells anyone who wants to hear about any changes in the state, such as React-Redux.
It does not do anything to help you with getting data, such as tweets. Nor does it help you decide when to get this data.
You'll have to decide for yourself when to do this and you can use setTimeout or anything else you feel like.
It's probably best if you manually insert the tweet into the state rather than refetch when you post. It's so much more responsive.

Resources