Laravel Inertia React preserve state not working - reactjs

I'm trying to reload my page but keep the filters state with Inertia.
Like this:
const [allProducts, setAllProducts] = useState([]);
const [filters, setFilters] = useState([]);
const fetchProducts = (page) => {
Inertia.get(
`/products?page=${page}`,
{ filters },
{
preserveState: true,
preserveScroll: true,
only: ['products'],
onSuccess: (response) => {
setAllProducts(allProducts.concat(response.props.products.data));
page += 1;
}
}
)
});
The problem is that filters and allProducts are reset even though preserveState = true.

What do you mean by "reload my page"? State in React is not preserved on "real" page refresh.
Maybe you can save data in local storage, then load it by useEffect. Another solution is to store data you want to keep between pages in Laravel session storage.

Related

React Query: rerun onSuccess method of a cached query

What I have
I am getting the user's location (latitude/longitude) which I use to call a google geocode API, unless the user's coords change, the request is not running again, since the query it uses the user's coords as queryKey array dependecy.
The problem
the problem is that I'm running some operations in the onSuccess query method, this method is only run when any of the queryKey dependencies change, and I mentioned this not happen.
How to run the onSuccess method whether the queryKey dependencies change or not?
Reference code
export const useGoogleReverseGeocoding = (coords) => {
const url = 'someUrl';
const request = createClient(); // axios abstraction
return useQuery({
queryKey: ['google-geocode', coords],
queryFn: request,
enabled: !!coords,
onSuccess: (data) => {
const searchTerm = removeGlobalCodeText(data?.plus_code?.compound_code);
// set searchterm in a global store. This searchterm change with
// different user actions, so if the user re-share his location
// I need to run onSuccess transformation again.
setSearchTerm(searchTerm);
},
});
};
As I was explaining in my comment, onSuccess can't be fired without the query itself firing again. Since certain user actions should trigger the transformations on onSuccess, you have a couple of ways to go about this, one of them would be to move these transformations on a useEffect hook and add some user action related flag on the dependencies array. The other proposed solution would be to invalidate the query upon these user actions, so it will be refetched and the transformations on onSuccess will execute.
You can achieve this using useQueryClient hook which returns the current QueryClient instance. You can invalidate the query from anywhere as long as the component is wrapped by QueryClientProvider. For this example and for convenience, I will include this hook on useGoogleReverseGeocoding custom hook.
Example:
Custom hook:
export const useGoogleReverseGeocoding = (coords) => {
const queryClient = useQueryClient()
const url = 'someUrl';
const request = createClient(); // axios abstraction
const geocodingData = useQuery({
queryKey: ['google-geocode', coords],
queryFn: request,
enabled: !!coords,
onSuccess: (data) => {
const searchTerm = removeGlobalCodeText(data?.plus_code?.compound_code);
// set searchterm in a global store. This searchterm change with
// different user actions, so if the user re-share his location
// I need to run onSuccess transformation again.
setSearchTerm(searchTerm);
},
});
const invalidateQueryOnAction = () => queryClient.invalidateQueries(['google-geocode'])
return { geocodingData, invalidateQueryOnAction }
};
Some component:
const dummyCoords = {
lat: 33.748997,
lng: -84.387985
}
const SomeComponent = () => {
const { geocodingData, invalidateQueryOnAction } =
useGoogleReverseGeocoding(dummyCoords)
const handleSomeUserAction = () => {
// handle action...
// Invalidate query, so the query gets refetched and onSuccess callback executes again
invalidateQueryOnAction()
}
}
PS: If #TkDodo comes along with a different solution for this, I would suggest to go for it instead.

React not re-rendering items, when array gets replaced

We are using Next.js + ApolloClient and we have a simple pagination, where, when I click on "2" the next page should be fetched and displayed.
This looks somewhat like this
export default function Reviews ({ rating }) {
const router = useRouter()
const id = router.query.id
const [page, setPage] = useState(1)
const [comments, setComments] = useState(rating.comments) // This is fetched by the server. Initial data
const [fetchComments] = useLazyQuery(GET_RATING_COMMENTS, {
variables: {
id,
page,
first: 3
},
onCompleted: data => {
setComments(data.comments)
}
})
const handleOnPageChange = page => {
setPage(page)
fetchComments()
}
const reviews = comments.map(rating => (
So, it seems like ApolloClient caches the result, which is cool. So the pagination AND rerender works perfectly fine, when the result gets initially fetched per a HTTP Request (not cached) but after ApolloClient has cached the result and I revisit a page that I've clicked before (for instance 2) then reviews is not getting updated.
Which is weird because, onCompleted gets called with the cached result. So I set it but its not updating anymore. The list does not change.
The list only changes initially, when the Page has not been visited yet. If its cached, nothing happens anymore. Why?
Btw, if I add fetchPolicy: 'no-cache' it magically works.

React does not wait for async function before mounting components

I am currently using a mock json-server to hold user information in my React app. I am working on storing settings and preferences for users. I have a setting page implemented through a Route component. I am displaying the settings configurations on this page. I am fetching the user settings in App.tsx :
const fetchUser = async (id:number) => {
const res = await fetch(`http://localhost:5001/users/${id}`)
const user = await res.json()
return user
}
const getSettings = async () => {
const user = await fetchUser(0)
setSettings(user.settings);
}
Then I am passing down the state variable for settings through useContext.
const [settings, setSettings] = useContext(userContext);
This works fine when I start on the root page and then go to the settings. However, if the user goes directly to the settings page, the setting state is initially null and I cannot access its values. I tried to fetch the settings again in the setting page component with useEffect but React does not wait for async functions to complete before mounting the components.
const getSettings = async (id:number) => {
const res = await fetch(`http://localhost:5001/users/${id}`)
const user = await res.json()
const settings = user.settings
setSettings(settings);
}
useEffect(() => {
if (!settings) getSettings(0);
}, [])
Is there a way to get around this? I would like to access the settings state throughout the app but the user should not have to start with the root component.
Note: It does work if I check that the value is null before use like this :
settings?.test.difficulty
Please try the below change for useEffect:
useEffect(async () => {
if (!settings) await getSettings(0);
}, [])

refetch in reactQuery is not return the data

I am using reactQuery in my react application. I need to call one get API in button click. for that i am using refetch option in reactQuery. API call is working fine but my response data is coming undefined. I checked in browser network there i can see the response.
My API call using reactQuery
const { data: roles, refetch: roleRefetch } = useQuery('getRoles', () => api.getRoles('ID_234'), { enabled: false });
My click event
const handleAdd = (e) => { roleRefetch(); console.log(roles) }
My action call using axios
export const getRoles = (name) => axios.get(roles/list?sa_id=${name}, { headers: setHeader }).then(res => res);
const handleAdd = (e) => { roleRefetch(); console.log(roles) }
this not how react works, and it's not react-query specific. calling a function that updates some state will not have your state be available in the next line. It will make it available in the next render cycle. Conceptually, you want this to work, which cannot with how react is designed:
const [state, setState] = React.useState(0)
<button onClick={() => {
setState(1)
console.log(state)
}}
here, the log statement will log 0, not 1, because the update doesn't happen immediately, and this is totally expected.
With react-query, what you can do is await the refetch, because its async, and it will give you the result back:
const handleAdd = async (e) => {
const { data } = await roleRefetch();
console.log(data)
}
or, depending on what you actually want to do, you can:
use data in the render function to render something - it will always be up-to-date.
use theonSuccess callback of useQuery to trigger side-effects whenever data is fetched
spawn a useEffect in the render function that does the logging:
const { data: roles, refetch: roleRefetch } = useQuery('getRoles', () => api.getRoles('ID_234'), { enabled: false });
React.useEffect(() => {
console.log(roles)
}, [roles])
on a more general note, I think disabling a query and then calling refetch on a button click is very likely not idiomatic react-query. Usually, you have some local state that drives the query. in your case, that's likely the id. Dependencies of the query should go to the queryKey, and react-query will trigger a refetch automatically when the key changes. This will also give you caching by id. You can use enabled to defer querying when your dependencies are not yet ready. Here's what I would likely do:
const [id, setId] = React.useState(undefined)
const { data: roles } = useQuery(['getRoles', id], () => api.getRoles(id), { enabled: !!id });
const handleAdd = (e) => { setId('ID_234') }
of course, id doesn't have to come from local state - it could be some other form of client state as well, e.g. a more global one.

delay rendering untilthe fetching is complete

I am using react Context and useReducer to fetch the data. I am working with front end right now. i have some problems.
this is State
const initialState = {
users: [
{
name: "Kyaw Zin Thih",
type: "Private University",
subjects: ["IT", "Business"],
phNumbers: ["111-111-111", "222-222-222"],
about: "llorem",
location: "loremlorem",
},
],
currentProfile: {},
currentPosts: [],
};
Inside users there will be a lot of users data from database. inside the currentProfile there will only be the data of that the user is viewing. same for currentPosts. when the user leave profile page both of the state will be empty again
My UI is like that. In main page there will be containers that show some data from "users". if someone click the container router will lead to Profile page. when someone reach the ProfilePage i will use useEffect to load the data to the "currentProfile" part
useEffect(() => {
setcurrent(username);
return () => {
deletecurrent();
};}, []);
const publicdata = useContext(PulicDataContext):
let { url } = useRouteMatch();
const { username } = useParams();
const {
setcurrent,
currentProfile,
currentPost,
deletecurrent,
loading,} = publicdata;
const { name, type, subjects, phNumbers, location, about } = currentProfile;
The Problem is that when the profile page is loaded current profile is emply until it is loaded in useEffect. So names, types and so on, they are also emply when the component load. Therefore react will only show blanks in "return" part.
how can i fix that thing.

Resources