Reputation: 412
We have upgraded from React 18 to React 19, and it has introduced an interesting timing error. We have a generic lookup form, using react-hook-form and react-hook-form-mui, which is driven by the URL. This form criteria can have nested lookups which are used to populate the parent form before search. The form is driven by the URL as the source of truth on load, or return from nested lookup. So, for example, the flow would be /lookup/Account
-> Search for a Person -> /lookup/Person
-> Return Person -> /lookup/Account?personId=1234
.
We have a hook that we are using to sync this data, which had no issues prior to our React 19 update. However, after the update, it appears that the updates in the hook are being swallowed by some sort of timing/batching issue. When we go from a parent to a nested lookup, and return the values back to the parent form, using the router navigate function, I've confirmed in the hook that the appropriate values are in the querystring, and the setValue is being called, but it is missing in the form.
What makes me believe this is a timing/batching issue, is if I debug and step through, and watch it all happen, the values are being set in the form as expected, but running at regular speed does not happen.
Here is our hook:
import dot from 'dot-object';
import isEmpty from 'lodash/isEmpty';
import isEqual from 'lodash/isEqual';
import isNil from 'lodash/isNil';
import qs from 'qs';
import { useFormContext } from 'react-hook-form';
import { useLocation } from 'react-router';
import { useEffect, useMemo, useState } from 'react';
export const useSyncUrlParamsToForm = (hideForm) => {
const { setValue, getValues } = useFormContext();
const { search } = useLocation();
const [executeInitialSearch, setExecuteInitialSearch] = useState(false);
const queryParams = useMemo(
() => qs.parse(search, { ignoreQueryPrefix: true }),
[search]
);
useEffect(() => {
if (!hideForm) {
if (isEmpty(queryParams)) {
return;
}
}
dot.keepArray = true;
const executeSearch = hideForm ?? false;
const currentValues = dot.dot(getValues());
for (const [key] of Object.entries(currentValues)) {
if (queryParams[key] && !isEqual(queryParams[key], currentValues[key])) {
setValue(key, queryParams[key]);
} else if (key === 'sortModel' && queryParams.sort) {
// executeSearch = true;
let field = queryParams.sort;
const sort = field.startsWith('-') ? 'desc' : 'asc';
if (field.startsWith('-')) {
field = field.substring(1);
}
const sortModel = [{ field, sort }];
if (!isEqual(sortModel, currentValues[key])) {
setValue(key, sortModel);
}
} else if (
key === 'paginationModel' &&
(queryParams.limit || queryParams.skip)
) {
// executeSearch = true;
const limit = isNil(queryParams.limit)
? 100
: parseInt(queryParams.limit, 10);
const skip = isNil(queryParams.skip)
? 0
: parseInt(queryParams.skip, 10);
const pagination = {
pageSize: limit,
page: skip / limit
};
if (!isEqual(pagination, currentValues[key])) {
setValue(key, pagination);
}
}
}
if (executeSearch) {
setExecuteInitialSearch(true);
}
}, [getValues, hideForm, queryParams, setValue]);
return [executeInitialSearch, setExecuteInitialSearch];
};
I'm unsure what would be causing this in React 19, and unsure how to go forward. I've tried things like flushSync
or wrapping the updates in a setTimeout
to try and get them to run after a short delay, but nothing has worked. The only thing that I was able to get working was to use a location.assign
vs router navigate, but that gives a weird user experience where the page will load the new form since the url has been updated, but then reload after the navigation finishes.
Upvotes: -1
Views: 41