Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: React Router 6 useLoaderData returning cached data after fresh data is returned by the loader function #11518

Closed
Nosherwan opened this issue May 1, 2024 · 5 comments
Labels

Comments

@Nosherwan
Copy link

What version of React Router are you using?

6.22.3

Steps to Reproduce

  • I am using a simple vite react.js (v18) app client app with react router v6.
  • In react router dom version 6.22.3, I am using useFetcher's fetcher.Form component and calling fetcher.submit on change inside the form.
  • This successfully changed the request params and the loader function of the parent route for the component is called again.
  • However the useLoaderData in that route still returns the old data to child components.

Edit:
Just to clarify the loader function itself runs correctly in the index route and returns the correct userData. However the data returned inside the component via useLoaderData is incorrect after the fetcher.submit calls.

As requested below is a link to a codeSandbox link that demonstrates the issue:
CodeSandBox Link

Below is the create router:

const router = createBrowserRouter([
	{
		path: '/',
		element: <Root />,
		errorElement: <ErrorPage />,
		children: [
			{ index: true, element: <Index />, loader: indexLoader },
		],
	}
]);

const rootElement = document.getElementById('root');

if (rootElement) {
	ReactDOM.createRoot(rootElement).render(
		<React.StrictMode>
			<RouterProvider router={router} />
		</React.StrictMode>
	);
} else {
	console.error("Element with ID 'root' not found in the document");
}

then the index route:

import RangePicker from '../components/rangePicker';


export async function loader({ request }) {
	
	const url = new URL(request.url);
	const startDate = url.searchParams.get('startDate');
	const endDate = url.searchParams.get('endDate');
    // This gets called correctly on every fetcher.submit();
	const userData = await getUserData(startDate, endDate);

	return { userData };
}

export default function Index() {
    // userData if logged gets called once with new data, but
    // after that gets called again with old data
	const { userData } = useLoaderData();
	
	
	return (
		<>
			<div>
				<RangePicker />
			</div>
			<div>
				<User data={userData} />
            </div>
		</>
	);
}

Below is the RangePicker component:

import { addDays } from 'date-fns';
import { useState } from 'react';
import { DateRangePicker } from 'react-date-range';
import { useFetcher } from 'react-router-dom';

export default function RangePicker() {
	const [state, setState] = useState([
		{
			startDate: new Date(),
			endDate: addDays(new Date(), 7),
			key: 'selection',
		},
	]);

	const fetcher = useFetcher();

	const handleDateRangeChange = (item) => {
		setState([item.selection]);
		const startDate = item.selection.startDate;
		const endDate = item.selection.endDate;
        //successfully triggers the loader for parent, but useLoaderData
        // still returns previous data.
		fetcher.submit(
			{ startDate, endDate }
		);
	};

	return (
		<fetcher.Form>
			<DateRangePicker
				onChange={handleDateRangeChange}
				moveRangeOnFirstSelection={false}
				ranges={state}
			/>
		</fetcher.Form>
	);
}

Expected Behavior

New data should be passed to child components by useLoaderData

Actual Behavior

Stale data fetched first time is returned by useLoaderData.

@Nosherwan Nosherwan added the bug label May 1, 2024
@dbergey
Copy link

dbergey commented May 1, 2024

I am also experiencing this issue

@brophdawg11
Copy link
Contributor

Loader data ia only revalidated after mutations (POST's) - but in these examples you're not performing a POST submission. In the code above, you don't have a method on <fetcher.Form> or fetcher.submit - and the default HTML behavior for form submissions is GET.

So, the fetcher is submitting a GET submission to the index route and executing the loader, and returning the new data on fetcher.data - but it's never causing a revalidation of the route loader data because no submissions were performed to mutate data.

In the codesandbox you did correctly have <fetcher.Form method="post">, but then you have a preventDefault call and a manual fetcher.submit() that was again lacking method so it was defaulting again to a GET.

The fix is to use <fetcher.Form method="post"> for declarative submissions or fetcher.submit(data, { method: 'post' }) for imperative. Once you are correctly posting, you need an action defined on your index route that handles the mutation.

I this is just a searchbox type UI and there's nothing to mutate, then your best bet is to use the route loader data initially and prefer the updated fetcher data once available.

let data = useLoaderData();
let fetcher = useFetcher();
let mostRecentData = fetcher.data || data;

@brophdawg11 brophdawg11 closed this as not planned Won't fix, can't repro, duplicate, stale May 1, 2024
@Nosherwan
Copy link
Author

Nosherwan commented May 1, 2024

Thanks @brophdawg11 this is new territory for me as it seems like we are trying to simulate the default behaviors for html forms, so by that logic this makes sense.
My thought process was that if loader is being called and data being fetched then the only thing left to do is re-validation.

@brophdawg11
Copy link
Contributor

Another simpler option I forgot to mention is that for a searchbox UI you can also just use navigations and skip the fetcher. <Form><input name="query" /></Form> will by default submit as a GET navigation and serialize the form data into the URL search params, and the changed params will trigger your loaders to revalidate. Then you don't even need a fetcher :)

@Nosherwan
Copy link
Author

@brophdawg11 I think that is what I was looking for, but somehow I started looking at useFetcher as it could serve CRUD altogether. I would suggest that RR6 docs need to be a bit more fleshed out for an easier description.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants