Update state in react - big change in DOM works with delay - reactjs

I render in my app transcription lines, 100 on average.
There is an option to translate the transcription lines.
Clinking a button toggles between show translation and not showing translation.
When I click on it, there is a delay of 1-2 seconds that the site gets stuck, then the change happens...
Anyone knows a better way to update a big change of state in react (with mobx)?
Somehow I managed to control the stuckness with setTimeout, but I don't believe this is the way...
Thanks!
const updateStateWithTimeout = async (time: number, requestForTranscript?: boolean) => {
requestForTranscript && await getTranscriptionsTranslations(sessionId!);
setTimeout(async () => {
requestForTranscript && await loadCallLogDataForTranslation(sessionId!);
setShowTranslation(!showTranslations);
setIsLoadingTranslations(false);
}, time)}
const handleTranslateButton = async () => {
setIsLoadingTranslations(true);
if (language?.includes("en")) {
ToastUtil.error("The language of the call is English");
return;
}
if (online) await showTranslationOnlineCall();
else {
if (hasTranslations) await updateStateWithTimeout(1000);
else await updateStateWithTimeout(3000, true);
}
}

Related

Can't fetch right data on the button click from fireStore

I am doing my first bigger react project, where I am biulding web app for barbershop.
Idea is this. When I want to make reservation, I need to choose service type (regular haircut, beard trim or both) and then I need to choose barber. Depending which barber I choosed I fetch data from firestore. Barbers name is actually firebase collection name of the barber where is his schedule when there is free apointment.
Problem is this. When I choose service and then choose barber John, nothing happens, but if I then click on barber Carl I get John's schedule.
In the picture there is my component and how it is set up.
Here is the code of my function which is not working right.
I tried many things, it is not working even with timeOut.
useEffect(() => {
if (!service || !barber) {
return;
}
if (fetching) {
setTimeout(async () => {
try {
const barbers_name = barber;
const freeTimeRef = collection(db, `${barbers_name}`);
const q = query(freeTimeRef);
const querySnap = await getDocs(q);
let freeTimeArr = [];
querySnap.forEach((doc) => {
freeTimeArr.push(doc.data().radno_vrijeme);
return freeTimeArr;
});
setSchedule(freeTimeArr);
} catch (error) {
toast.error(error);
} finally {
setLoading(false);
setFetching(false);
}
}, 100); // delay the call by 100 milliseconds
}
}, [fetching, barber]);
setFetching is being used in this function, and this function changes state of the button from the picture, to the barbers name or service type.
const HandleChangeBarber = (e) => {
setBarber(e.currentTarget.textContent);
setFetching(true);
};

ReactJS delay update in useState from axios response

I am new to react js and I am having a hard time figuring out how to prevent delay updating of use state from axios response
Here's my code:
First, I declared countUsername as useState
const [countUsername, setUsername] = useState(0);
Second, I created arrow function checking if the username is still available
const checkUser = () => {
RestaurantDataService.checkUsername(user.username)
.then(response => {
setUsername(response.data.length);
})
.catch(e => {
console.log(e);
})
}
So, every time I check the value of countUsername, it has delay like if I trigger the button and run checkUser(), the latest response.data.length won't save.
Scenario if I console.log() countUseranme
I entered username1(not available), the value of countUsername is still 0 because it has default value of 0 then when I trigger the function once again, then that will just be the time that the value will be replaced.
const saveUser = () => {
checkUser();
console.log(countUsername);
}
Is there anything that I have forgot to consider? Thank you
usually there is a delay for every api call, so for that you can consider an state like below:
const [loading,toggleLoading] = useState(false)
beside that you can change arrow function to be async like below:
const checking = async ()=>{
toggleLoading(true);
const res = await RestaurantDataService.checkUsername(user.username);
setUsername(response.data.length);
toggleLoading(false);
}
in the above function you can toggle loading state for spceifing checking state and disable button during that or shwoing spinner in it:
<button onClick={checking } disabled={loading}>Go
i hope this help
.then is not synchronous, it's more of a callback and will get called later when the api finishes. So your console log actually goes first most of the time before the state actually saves. That's not really something you control.
You can do an async / await and return the data if you need to use it right away before the state changes. And I believe the way state works is that it happens after the execution:
"State Updates May Be Asynchronous" so you can't really control when to use it because you can't make it wait.
In my experience you use the data right away from the service and update the state or create a useEffect, i.g., useEffect(() => {}, [user]), to update the page with state.
const checkUser = async () => {
try {
return await RestaurantDataService.checkUsername(user.username);
} catch(e) {
console.log(e);
}
}
const saveUser = async () => {
const user = await checkUser();
// do whatever you want with user
console.log(user);
}

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.

Does setState or dispatch (useReducer hook) make component re-render before calling next lines?

I realize a problem when using useState and useReducer hook that any lines of code after the update state function (setState, dispatch) will be called in the next re-rendering (with its previous state before update). It means that update state function causes re-rendering immediately and not waiting the whole function executed.
const [aaa, setAAA] = useState<boolean>(false);
const updateMyBiddingList = async (atDate?: string) => {
try {
console.log('step 0');
const result = await getBiddingCartFromService(atDate ? atDate : myBiddingListState.myBiddingList[0].updatedAt);
if (result.responseCode.toString().startsWith('2')) {
setAAA(true);
console.log('step 1');
}
console.log('step 2 ', aaa);
}
catch (err) {
if (timeOut.current) clearTimeout(timeOut.current);
timeOut.current = setTimeout(() => updateMyBiddingList(), TIMEOUT);
}
}
console.log('Component is re-rendering... ', aaa);
return ...
The above codes will log in the following order:
step 0
Component is re-rendering... true
step 1
step 2 true
Does anyone explain the workflow of update state hook for me? Thank in advance.
This is because React doesn't rely on an async task like that, there are two ways to simulate the result you want. One I'm going to call standard and one unstable version.
Unstable version
The first way is to wrap updates inside unstable_batchedUpdates callback. As the name suggests, this API will batch your updates in a single reconciliation pass, resulting in fewer component renders.
So the updated code will be something like:
import { unstable_batchedUpdates } from "react-dom";
const updateMyBiddingList = async (atDate?: string) => {
try {
console.log('step 0');
const result = await getBiddingCartFromService(atDate ? atDate : myBiddingListState.myBiddingList[0].updatedAt);
// Add unstable API where you cause re-render ⭐
unstable_batchedUpdates(() => {
if (result.responseCode.toString().startsWith('2')) {
setAAA(true);
console.log('step 1');
}
console.log('step 2 ', aaa);
})
}
catch (err) {
if (timeOut.current) clearTimeout(timeOut.current);
timeOut.current = setTimeout(() => updateMyBiddingList(), TIMEOUT);
}
}
This will change the order of the logs to this
Component is re-rendering... false
step 0
step 1
step 2 false
Component is re-rendering... true
you can see code running with fake data from JSON placeholder in the following code sandbox.
Standard version (react common pattern)
It is not the only pattern you can do what you want, but it seems more react-ish like to me :)
you are requesting (an async action)
you want to change the state of the component that will cause re-render
this phase also change function definition for the very next render
you want to re-fetch on failure
I'm sticking to your implementation and don't want to split useEffect sections into tiny reusable parts, although it might be something you want to think about it.
The solution is to move your logic where it belongs, which means where you are going to call that function call in your dom tree.
// instead of aaa and setAAA :)
const [updated, setUpdated] = useState<boolean>(false)
const [hasError, setHasError] = useState<boolean>(false)
const timeOut = useRef<number>()
useEffect(() => {
const updateMyBiddingList = async (atDate?: string) => {
try {
console.log('step 0');
const result = await getBiddingCartFromService(atDate ? atDate : myBiddingListState.myBiddingList[0].updatedAt);
setHasError(false)
if (result.responseCode.toString().startsWith('2')) {
setUpdated(true);
console.log('step 1');
}
console.log('step 2 ', updated);
}
catch (err) {
setHasError(true)
if (timeOut.current) clearTimeout(timeOut.current);
timeOut.current = setTimeout(() => updateMyBiddingList(), TIMEOUT);
}
}
if (!updated || hasError) updateMyBiddingList()
// timeOut.current can also be added here but it is not recommended
}, [updated, hasError])
Well that happens due to async logic of javascript. I think you're calling updateMyBiddingList function somewhere during render-phase, javascript starts it's process and since it's async react won't wait for it to finish. That would cause your application to freeze whenever you made an async request.

React hook missing dependency

I'm hoping someone can explain to me the correct usage of React hook in this instance, as I can't seem to find away around it.
The following is my code
useEffect(() => {
_getUsers()
}, [page, perPage, order, type])
// This is a trick so that the debounce doesn't run on initial page load
// we use a ref, and set it to true, then set it to false after
const firstUpdate = React.useRef(true);
const UserSearchTimer = React.useRef()
useEffect(() => {
if(firstUpdate.current)
firstUpdate.current = false;
else
_debounceSearch()
}, [search])
function _debounceSearch() {
clearTimeout(UserSearchTimer.current);
UserSearchTimer.current = setTimeout( async () => {
_getUsers();
}, DEBOUNCE_TIMER);
}
async function _getUsers(query = {}) {
if(type) query.type = type;
if(search) query.search = search;
if(order.orderBy && order.order) {
query.orderBy = order.orderBy;
query.order = order.order;
}
query.page = page+1;
query.perPage = perPage;
setLoading(true);
try {
await get(query);
}
catch(error) {
console.log(error);
props.onError(error);
}
setLoading(false);
}
So essentially I have a table in which i am displaying users, when the page changes, or the perPage, or the order, or the type changes, i want to requery my user list so i have a useEffect for that case.
Now generally I would put the _getUsers() function into that useEffect, but the only problem is that i have another useEffect which is used for when my user starts searching in the searchbox.
I don't want to requery my user list with each and every single letter my user types into the box, but instead I want to use a debouncer that will fire after the user has stopped typing.
So naturally i would create a useEffect, that would watch the value search, everytime search changes, i would call my _debounceSearch function.
Now my problem is that i can't seem to get rid of the React dependency warning because i'm missing _getUsers function in my first useEffect dependencies, which is being used by my _debounceSearch fn, and in my second useEffect i'm missing _debounceSearch in my second useEffect dependencies.
How could i rewrite this the "correct" way, so that I won't end up with React warning about missing dependencies?
Thanks in advance!
I would setup a state variable to hold debounced search string, and use it in effect for fetching users.
Assuming your component gets the query params as props, it would something like this:
function Component({page, perPage, order, type, search}) {
const [debouncedSearch, setDebouncedSearch] = useState(search);
const debounceTimer = useRef(null);
// debounce
useEffect(() => {
if(debounceTime.current) {
clearTimeout(UserSearchTimer.current);
}
debounceTime.current = setTimeout(() => setDebouncedSearch(search), DEBOUNCE_DELAY);
}, [search]);
// fetch
useEffect(() => {
async function _getUsers(query = {}) {
if(type) query.type = type;
if(debouncedSearch) query.search = debouncedSearch;
if(order.orderBy && order.order) {
query.orderBy = order.orderBy;
query.order = order.order;
}
query.page = page+1;
query.perPage = perPage;
setLoading(true);
try {
await get(query);
}
catch(error) {
console.log(error);
props.onError(error);
}
setLoading(false);
}
_getUsers();
}, [page, perPage, order, type, debouncedSearch]);
}
On initial render, debounce effect will setup a debounce timer... but it is okay.
After debounce delay, it will set deboucedSearch state to same value.
As deboucedSearch has not changed, ferch effect will not run, so no wasted fetch.
Subsequently, on change of any query param except search, fetch effect will run immediately.
On change of search param, fetch effect will run after debouncing.
Ideally though, debouncing should be done at <input /> of search param.
Small issue with doing debouncing in fetching component is that every change in search will go through debouncing, even if it is happening through means other than typing in text box, say e.g. clicking on links of pre-configured searches.
The rule around hook dependencies is pretty simple and straight forward: if the hook function use or refer to any variables from the scope of the component, you should consider to add it into the dependency list (https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies).
With your code, there are couple of things you should be aware of:
1.With the first _getUsers useEffect:
useEffect(() => {
_getUsers()
}, [page, perPage, order, type])
// Correctly it should be:
useEffect(() => {
_getUsers()
}, [_getUsers])
Also, your _getUsers function is currently recreated every single time the component is rerendered, you can consider to use React.useCallback to memoize it.
2.The second useEffect
useEffect(() => {
if(firstUpdate.current)
firstUpdate.current = false;
else
_debounceSearch()
}, [search])
// Correctly it should be
useEffect(() => {
if(firstUpdate.current)
firstUpdate.current = false;
else
_debounceSearch()
}, [firstUpdate, _debounceSearch])

Resources