Strategy to overcome caching of Axios requests? - reactjs

I've noticed that if I make a GET call to the same API endpoint twice in a row then the previous result appears to get cached. This is causing a problem for the logic of my React component which is a multi-page Wizard.
I define my API request like this:
const [{ data, isLoading, isError }, userNameDoFetch] = useFetch(
null,
{ }
)
The user can then enter a username into an input element and its existence is checked liked this:
const handleBlur = (event) => {
const propName = event.target.name;
const propValue = event.target.value;
if (propValue.length > 0) {
setIsFetching(true);
userNameDoFetch( `${API_ROOT()}account_management/users/name/${propValue}` );
}
}
}
I then have a useEffect that processes the data coming back from the server. It begins like this:
useEffect(() => {
setIsFetching(false);
...
The problem is that the useEffect isn't called if say, the user tabs back into the Username field and then tabs out again. This sets isFetching to true, which disables the Next button in my wizard, leaving the user trapped on that page.
Any thoughts on how to resolve this?

Related

Supabase onAuthStateChange() triggers when switching tabs in React

I have the following code in my React project using Supabase:
// supabaseClient.ts
export const onAuthStateChangedListener = (callback) => {
supabase.auth.onAuthStateChange(callback);
};
// inside user context
useEffect(() => {
const unsubscribe = onAuthStateChangedListener((event, session) => {
console.log(event);
});
return unsubscribe;
}, []);
However, every time I switch tabs away from the tab rendering the website to something else, and back, I see a new log from this listener, even if literally no change happened on the website.
Does anyone know the reason for this? The useEffect inside my user context component is the only place in my app where the listener is being called. To test, I wrote this dummy function inside my supabaseClient.ts file:
const testFunction = async () => {
supabase.auth.onAuthStateChange(() => {
console.log("auth state has changed");
});
};
testFunction()
This function also renders every time I switch tabs. This makes it a little annoying because my components that are related to userContext re render every time a tab is switched, so if a user is trying to update their profile data or something, they cannot switch tabs away in the middle of editing their data.
Supabase onAuthStateChange by default triggers every time a tab is switched. To prevent this, when initializing the client, add {multiTab: false} as a parameter.
Example:
const supabase = createClient(supabaseUrl, supabaseAnonKey, {multiTab: false,});
Here is my solution to the same problem. The way I've found is saving the access token value in a cookie every time the session changes, and retrieve it when onAuthStateChange get triggered, so I can decide to not update anything if the session access token is the same.
// >> Subscribe to auth state changes
useEffect(() => {
let subscription: Subscription
async function run() {
subscription = Supabase.auth.onAuthStateChange(async (event, newSession) => {
// get current token from manually saved cookie every time session changes
const currentAccessToken = await getCurrentAccessToken()
if (currentAccessToken != newSession?.access_token) {
console.log('<<< SUPABASE SESSION CHANGED >>>')
authStateChanged(event, newSession)
} else {
console.log('<<< SUPABASE SESSION NOT CHANGED >>>')
}
}).data.subscription
// ** Get the user's session on load
await me()
}
run()
return function cleanup() {
// will be called when the component unmounts
if (subscription) subscription.unsubscribe()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

My react component never displays the information from the database

I have a small web app displays game information.
I am using React hooks so that the component is modern.
When this component loads, I want it to connect to the api via axios, and get the description of the game.
But when it loads, the value inside the <GameVault /> is always null.
When I look in the database, it is not null. If I hit the api directly, it does return the game description.
My console.log is hit twice for some reason. The first time it's null, the second time it has the needed value.
I am also not getting any errors, so I don't know why this isn't working.
Here is the code:
const Vault = ({ game }) => {
const [gameText, setGameText] = useState(null);
async function fetchGameText() {
const response = await axios.get(`/api/gamermag/${game.id}/gameDescriptionText`);
setGameText(response.data);
}
useEffect(() => {
fetchGameText();
}, []);
console.log("gameText: ", gameText);
const gamerValue = useMemo(() => {
return gameText ? gameText : "";
}, [gameText]);
return (
<GameVault value={gamerValue} />
)
}
export default Vault;
Is there a way to get this to work?
Thanks!
You need to wait for the data to load from the server. While the data is being fetched, gameText will be null and when it's done fetching, it stores the response. That is why your console.log hit twice. The first time is the component's first render, and the second time is when the gameText changes its state which caused a rerender.
You need to add logic to wait for the data.
if(!gameText){
return <div>loading...</div>
}

React query how to handle a search

I'm just playing around with react query
I'm building sort of a simple github clone
I have to use useQuery twice one for the current user
as router param the other with a new search.
I ended up with:
const history = useHistory();
const currentUser: string = useRouterPathname();
const [user, setUser] = useState(currentUser);
const handleFormSubmit = (data: SearchFormInputs) => {
history.push(`/${data.search}`);
setUser(data.search);
};
const { isLoading, error, data } = useGetUserData(user);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>An error has occurred: " + {error}</p>;
console.log(user, data);
Is it the right way?
The important part is probably that in useGetUserData, you put the user into the queryKey of react-query, something like:
const useGetUserData = (user) => useQuery(['users', user], () => fetchUserData(user))
so that you always refetch data when the user changes and users are not sharing a cache entry between them.
Something not react-query related though: The good thing about react-router is that you can also use it as a state manager - their hooks also subscribe you to changes, so there is no real need to duplicate that with local state:
const history = useHistory();
const currentUser: string = useRouterPathname();
const handleFormSubmit = (data: SearchFormInputs) => {
history.push(`/${data.search}`);
};
const { isLoading, error, data } = useGetUserData(currentUser);
once you push to the history, all subscribers (via useParams or useLocation) will also re-render, thus giving you a new currentUser.
Lastly, I would recommend checking for data availability first rather than for loading/error:
const { error, data } = useGetUserData(user);
if (data) return renderTheData
if (error) return <p>An error has occurred: " + {error}</p>;
return <p>Loading...</p>
it obviously depends on your use-case, but generally, if a background refetch happens, and it errors, we still have data (albeit stale data) that we can show to the user instead. It's mostly unexpected if you see data on the screen, refocus your browser tab (react-query will try to update the data in the background then per default), and then the data disappears and the error is shown. It might be relevant in some cases, but most people are not aware that you can have data and error at the same time, and you have to prioritise for one or the other.

How to to be displayed reactour just first time in nextjs?

I'm using reactour in next.js
I want to just first time when page is rendered reactor to be displayed, and when route change and come back to this page reactor not to be displayed
how can do this?
this is how I call it
import { disableBodyScroll, enableBodyScroll } from "body-scroll-lock";
import dynamic from "next/dynamic";
const Tour = dynamic(() => import("reactour"), { ssr: false });
const tourConfig = [
{
selector: ".step_1",
content:
'Click "View future forecast earning" to look at all....',
},
{
selector: ".step_2",
content:
"Chose different earning forecasts to see how your property...",
},
];
export default function MyPage(props) {
const disableBody = target => disableBodyScroll(target);
const enableBody = target => enableBodyScroll(target);
const [isTourOpen, setIsTourOpen] = useState(false);
const closeTour = () => setIsTourOpen(false);
useEffect(() => {
const timing = setTimeout(() => {
setIsTourOpen(true);
}, 2000);
return () => {
clearTimeout(timing);
};
}, []);
return (
<>
<OtherComponent />
<Tour
onRequestClose={closeTour}
steps={tourConfig}
isOpen={isTourOpen}
rounded={5}
onAfterOpen={disableBody}
onBeforeClose={enableBody}
className={classes.root}
disableInteraction
inViewThreshold={50}
/>
</>
);
}
This entirely depends on your logic you can do this in 3 ways if you like:
The local storage way:
localStorage.setItem('displayTour', true);
If you're not clearing out the local storage then you can always check your storage whenever the user signs in to check (in useEffect) if the tour is displayed to them or not.
Storing in cookies:
You can always set cookies for the first time the user signs in and check, but this approach has a con, cookies are set in the browser so if the user has already gone through the tour and they sign in from another browser the tour will be rendered again.
Tour Flag from API (Network Request):
This may be the right thing to do if you are not trying to save your network requests. You can always send back a flag from backend e.g is_tour_displyed: false and can always validate whether to show the tour or not.
Note: The localStorage and the cookies may be cleared out anytime, considering if that's not a problem for you. You can always go for the third option.
you can make condition and add a value to localStorage like blow:
localStorage.setItem('playTour', 'true');
and when ever you played the tour at first time you can set this value to false

Re-calling react hook "useEffect" problem

I am working on a new small project in React and I am using React hooks. The ultimate target of this app is to fetch the data of the weather of a certain city from openweather API and display it on the screen. I have created a custom hook to fetch the data from the endpoint and passed in three arguments as shown below :
export const useHttp = (baseURL, dependancies, isSubmit) => {
// Inizialize isLoading to false
const [isLoading, setLoading] = useState(false);
// Initialize the fetched data to an empty string
const [fetchedData, setFetchedData] = useState('');
useEffect(() => {
/*Check if isSubmit is true before fetching the corresponding
data*/
if (isSubmit) {
// set isLoading to true until we get the data
setLoading(true);
// Start fetching the data from the url received
fetch(baseURL)
.then(response => {
if (!response.ok) {
throw new Error('Failed to fetch. ');
}
return response.json();
})
// Return the data when fetched successfully
.then(data => {
setLoading(false);
setFetchedData(data);
})
/*Show an alert when fetching encounters an error and stop the
loader accordingly*/
.catch(err => {
alert("Please insert a valid name")
setLoading(false);
})
}
}, dependancies)
// Returning the data to use them later in displaying the weather
return [isLoading, fetchedData];
};
And here is how my form component works :
// initialized the input to an empty string
const [searchTerm, setSearchTerm] = useState('');
// Initialize the state of submit to false
const [isSubmit, setIsSubmit] = useState(false);
// Use array destruction to get isLoading and fetchedData from the imported userHttp hook
const [isLoading, fetchedData] = useHttp(`http://api.openweathermap.org/data/2.5/weather?q=${searchTerm}
&APPID=b8c1572d189de60f5480324c6b53d9ab`, [isSubmit], isSubmit);
// Use object destruction to get the desired properties out of the fetched data
const { name, sys, weather, main } = fetchedData ? fetchedData : '';
// Get the user input in the search bar to pass it to submitInput function
const getSearchTerm = (e) => {
setSearchTerm(e.target.value);
}
// Submit the userinput and call the custom hook to fetch the data matched with the input
const submitInput = (event) => {
// Prevent the form from actually submitting
event.preventDefault();
// Change the state of isSubmit so that useEffect can be re-called
setIsSubmit(!isSubmit);
}
As you can see, I wanted to change the value of the state "isSubmit" whenever the user submits in order to recall useEffect as "isSubmit" is also passed as a dependency. Moreover, I created a condition to prevent useEffect from working whenever the app is rendered because I want it to work only when the user submits.
The thing is, it works perfectly the first time but when I enter another value, I have to click twice on the button to make it work. I spent a while thinking about this issue but I came to nothing in the end. Hopefully, someone can help me with this. Thanks in advance.
Here is also a link to the project rep on GitHub :
https://github.com/Saifsamirk/weatherApp
Your useEffect hook only fires when isSubmit = true. When you call submitInput you only change the value of isSubmit to !isSubmit. It will only be true every second time. You might wanna reset your isSubmit state to false after firing the event.

Resources