Are you using useForm hook or inertia's router for a request that returns a JSON? if your API endpoint returns a JSON you must use fetch/AXIOS.
"All Inertia requests must receive a valid Inertia response, however a plain JSON response was received."
Hi,
I have an inertia + react laravel app where I sometimes run into this issue. It's usually directly after a user logs in, and I believe related to fetch/axios requests firing before inertia is fully initialised. E.g. fetching a users notifications, or loading comments on a page.
"All Inertia requests must receive a valid Inertia response, however a plain JSON response was received."
Followed by a JSON dump of whatever request was being made. It can be hard to replicate but it seems to be related to post-login redirects.
Does anyone know what exactly causes this? I have tried to add some basic timeouts before non-inertia requests fire, but it still happens here and there.
From everything I have read (including from the creator of inertia), there should be no issue firing off fetch/axios requests, and I can't find anything solid about resolving this issue.
Is there a simple/best practice way I can check inertia has fully initialised, or a way to exclude these requests from being picked up by inertia?
Thanks
@k_nadam I can't see anywhere that this would be happening. I'll try and have another review this afternoon
I believe related to fetch/axios requests firing before inertia is fully initialised
This is not the case. Normal JS requests can send and receive whatever they want. This error only comes from Inertia requests: router methods, form helper methods, and Link components. If one of these gets a non-Inertia response, e.g. after a redirection or an error, then Inertia can't work with the response.
hmmm okay I will keep looking. I just had this pop up again a moment ago, still don't know exactly how to replicate.
here's the error i saw:
All Inertia requests must receive a valid Inertia response, however a plain JSON response was received. {"notifications":[],"unread_count":0}
And here's where the spaghetti that's being called:
useEffect(() => {
// Fetch unread notifications count periodically
const fetchUnreadCount = async () => {
try {
const response = await fetch(route('api.notifications.unread', { limit: 1 }));
if (!response.ok) {
throw new Error('Failed to fetch notifications');
}
const data = await response.json();
setCount(data.unread_count);
} catch (error) {
console.error('Error fetching notifications:', error);
}
};
// Fetch every minute
const interval = setInterval(fetchUnreadCount, 60000);
return () => {
clearInterval(interval);
};
}, []);
@chrislove show the controller function please
public function unread(Request $request): JsonResponse
{
$limit = $request->input('limit', 20);
$notifications = $this->notificationService->getUnread(Auth::user(), $limit);
return response()->json([
'notifications' => $notifications,
'unread_count' => Notification::where('recipient_user_id', Auth::id())
->unread()
->notExpired()
->count()
]);
}
@chrislove the issue is probably from a middleware
try adding this header to your request:
headers: {
Accept: 'application/json',
'X-Requested-With': 'XMLHttpRequest',
},
@swez_k That doesn't matter. Nothing that happens during this request should throw an Inertia error because this isn't an Inertia request at all. It's a plain JS fetch call. If it were an Inertia request, the JSON response itself would throw the error.
@chrislove Are you sure you're not using the same api.notifications.unread route (or its URL) in an Inertia request somewhere, e.g. in a Link component, form, or a redirection? The JS code you posted can't cause this error.
@JussiMannisto This only ever seems to happen after login – i dont know exactly what causes it though.
It just happened again and I added a flag to ensure im checking the correct controller methods. E.g.
/**
* Get unread notifications for the authenticated user.
*/
public function unread(Request $request): JsonResponse
{
$limit = $request->input('limit', 20);
$notifications = $this->notificationService->getUnread(Auth::user(), $limit);
return response()->json([
'is_api_unread' => true,
'notifications' => $notifications,
'unread_count' => Notification::where('recipient_user_id', Auth::id())
->unread()
->notExpired()
->count()
]);
}
This is the only single location in the entire app where this route is called:
import React, { useState, useEffect } from 'react'; import { Link } from '@inertiajs/react'; import { Bell } from 'lucide-react';
const NotificationButton = ({ unreadCount = 0 }) => { const [count, setCount] = useState(unreadCount);
// Update count when unreadCount prop changes
useEffect(() => {
setCount(unreadCount);
}, [unreadCount]);
useEffect(() => {
// Fetch unread notifications count periodically
const fetchUnreadCount = async () => {
try {
const response = await fetch(route('api.notifications.unread', { limit: 1 }));
if (!response.ok) {
throw new Error('Failed to fetch notifications');
}
const data = await response.json();
setCount(data.unread_count);
} catch (error) {
console.error('Error fetching notifications:', error);
}
};
// Fetch every minute
const interval = setInterval(fetchUnreadCount, 60000);
return () => {
clearInterval(interval);
};
}, []);
return (
<Link
href={route('notifications.index')}
className="flex items-center justify-start gap-2 px-4 py-3 font-medium text-sm text-gray-500 hover:bg-gray-100"
>
<div className="relative">
<Bell size={16} className="" />
</div>
<span className="text-sm font-medium">Notifications</span>
{count > 0 && (
<div className="bg-red-500 text-white text-xs w-5 h-5 rounded-full flex items-center justify-center">
{count > 9 ? '9+' : count}
</div>
)}
</Link>
);
};
export default NotificationButton;
It's definitely something related to login and perhaps lingering requests that get fired again after the session is reinitialised. Perhaps as this runs every 60 sec, if a session is destroyed while this is running in the client it may cause issues? Honestly giving me a bit of a headache. But it definitely seems like inertia is intercepting requests that it shouldnt be
@chrislove The error comes from Inertia's front-end library. It means the original request must have been triggered by Inertia. I don't see the useEffect code being the culprit. If you want confirmation, add another query parameter to the request in useEffect, e.g. useEffectDebug: 1, and add that to the JSON response on the backend. I suspect it'll be null when you get this error.
The error may be caused by a redirection somewhere, such as a middleware or the login method. Or there may be mistake in the with the route definitions, e.g. another route points to the unread method or has the same URL.
I'd start by checking the routes and the login method. Then I'd check any middleware that might be redirecting Inertia requests to non-Inertia endpoints. I'd also check how exceptions are handled.
If you don't have Telescope installed in the dev environment, you might want to install it. It makes it easier to track what happens leading up to the error.
P.S. You should validate the "limit" parameter. If bots start sending invalid values, you'll get exceptions. You should always validate client input.
Hey everyone, hard to have certainty around this as the issue was so intermittent, but this is the latest on that request to fetch unread notifications, as suggested by @swez_k
// Update count when unreadCount prop changes
useEffect(() => {
setCount(unreadCount);
}, [unreadCount]);
useEffect(() => {
// Fetch unread notifications count periodically
const fetchUnreadCount = async () => {
try {
const response = await fetch(route('api.notifications.unread', { limit: 1 }), {
headers: {
Accept: 'application/json',
'X-Requested-With': 'XMLHttpRequest',
},
});
if (!response.ok) {
throw new Error('Failed to fetch notifications');
}
const data = await response.json();
setCount(data.unread_count);
} catch (error) {
console.error('Error fetching notifications:', error);
}
};
// Fetch every minute
const interval = setInterval(fetchUnreadCount, 60000);
return () => {
clearInterval(interval);
};
}, []);
With this change in place, I haven't seen the Inertia error in a few weeks, so perhaps this fixed it. Hard to say for sure but if you're having this issue slap these headers in your axios/fetch and pray for the best.
And for even more context, this only seemed to occur when there was an automatic redirect after session expiry and logging back into the app. So i think @jussimannisto had some good insight on where to look if you're having similar issues
Hi @chrislove ,
Inertia is meant to be used with props - so that when you "reload" data, but not the whole page, you can just fetch a prop with the only: ['yourpropname'] and in the controller method you can then return Inertia::render('SamePage', ['yourpropname'=>$value]);. More here https://inertiajs.com/partial-reloads
So, in other words, in your initial load, you set the 'yourpropname' to null or empty, then on the second visit you set the only option.
That being said, if you really need to do an async call without inertia, you can always use axios, but then you have to manually handle the response. axios.get('route that returns json').then((response) => { manual transformation of data to your vue/react props,states,properties });
Full example:
// your controller
public function index(){
$initial_needed_data = $initial_data
return Inertia::render('YourPage', [
'prop1'=>$initial_needed_data ,
'prop2'=>null,
]
}
public function requestedData(){
$prop2data = 'somevalue';
return Inertia::render('YourPage', [
'prop2'=>$prop2data,
]
}
//giving a Vue example because I am not used to React
props:['prop1','prop2'],
methods:{
requestData(){
this.someForm.get('/requestedDataRoute', {
only:['prop2'],
}
}
}
data(){
return {
someForm: this.$inertia.form({}),
}
}
Best regards,
Aleksandar
Please or to participate in this conversation.