React Native - Delay between changing bottom tabs - reactjs

I have used react navigation's "createBottomTabNavigator" in one of my projects.
const Tab = createBottomTabNavigator();
</Tab.Navigator>
................
<Tab.Screen name="Cart" component={Cart} />
{configuration.paymentProviders.length > 0 && (
<Tab.Screen name="Payment" component={Payment} />
)}
</Tab.Navigator>
My problem is this:
If I switch between two tabs quickly, after a while, the render cannot work properly because it tries to perform the operation without getting a response from the API.
For this, I added setLoading(true) to the API request and did not set it to false until the request was finished, but I still have the same problem, although it is better.
So, I want to give a half-second pause between tabs, do you have any suggestions for this?

When you switch the tabs, you use somthing like onClick() right? Here is old question for the solution as setTimeout. It gives you the ability to delay before the code gets executed. But I advice you do disable all other buttons, before you get a response. This also means that you need to be sure that response will come, at least timeout, then you can enable buttons again.
A better option might be to consider not delaying the process but just simply disabling all buttons which have onClick functionality, to prevent too many requests. This is an example of disabling something, doesn't neccessarely needs to be a button.
export default function App() {
const [clicked, setClicked] = React.useState(false);
//fake request, just delaying things
function handleClick() {
setClicked(true); // disable all buttons if clicked
setTimeout(() => {
setClicked(false);
}, 2000);
}
useEffect(() => {
console.log(clicked);
}, [clicked]);
return (
<div className="App">
<button onClick={!clicked ? handleClick : undefined}>1</button>
<button onClick={!clicked ? handleClick : undefined}>2</button>
</div>
);
}

Related

How to listen for time change in React?

I have a button which I want to remain disabled unless in a certain time window. Is there any way to do this in react?
I dont want to hard code it like
<button disabled={isCorrectTime()}>...
because if the user is already on the page, and the time changes to the correct time, the button will not get updated right? does anyone know of any solutions?
You call setTimeout inside of the useEffect Hook . useEffect method runs when it first renders then setTimeout block runs after some seconds (these seconds passed into the second parameter of the setTimeout method) then you call the clearTimeout() to cancel a timer .
Example :
import React, {useEffect, useState} from 'react'
function App() {
const [disabled, setdisabled] = useState(true)
useEffect(() => {
const timer = setTimeout(() => {
setdisabled(false);
}, 3000);
return () => clearTimeout(timer);
}, []);
return (
<div>
<h1> hello </h1>
<button disabled= {disabled} > click me </button>
</div>
)
}
export default App
Is the time window a specific amount of time after a certain event? If so, you can use a timeout function to change a state variable that you can then use to control the disabled state of the button:
const [isDisabled, setIsDisabled] = useState(true)
setTimeout(() => {
setIsDisabled(false);
}, 1000)
// set this time to whatever the desired length of time is
...
<button disabled={isDisabled} />
If this doesn't work, you might be able to use setInterval() to periodically check the current time, but I will need more information about what your goals are to know if that's the best way forward.

How to reload a component in a bottom tab navigator

I have 4 bottom tab navigator options
<Tab.Screen name="Home" component={HomeScreen} options={{headerShown:false}}/>
<Tab.Screen name="Appointments" component={Doctors}/>
<Tab.Screen name="Lab Tests" component={Diagnostics}/>
<Tab.Screen name="Account" component={Profile} options={{ unmountOnBlur: true }}
In the Account tab, I am showing the Profile Details And Edit Profile option.
Clicking on Edit Profile I go to Edit Profile Page, Edit Save,
const Save = navigation.navigate("Account")
After hitting Save, I am returned to the Account tab but the component Profile, which I am using as an Account tab component, is not reloading, so the Profile Details I am using are still the old Details.
As You Can see I already used unmountOnBlur : true, It works only when I am switching tabs, I want the same behavior when I came back from the Edit Profile page to the Account Tab.
If your getting the profile data from an API's that means your calling the same API in the Account Tab as well. You can add an event listener and every time your screen get into focus you can call that apis and get the updated result.
Ref: https://reactnavigation.org/docs/navigation-events.
Example:
React.useEffect(() => {
const unsubscribe = props.navigation.addListener('focus', () => {
// call your api here
});
return unsubscribe;
}, [navigation]);
Without a complete example, it's hard to figure out the exact issue. Maybe the profile component is using the useEffect without deps will not call again.
You can Call a function when focused screen changes or re-render a screen with the useIsFocused hook -
useIsFocused - this will cause a re-render. May introduce unnecessary component re-renders as a screen comes in and out of focus.
import * as React from 'react';
import { Text } from 'react-native';
import { useIsFocused } from '#react-navigation/native';
function Profile() {
// This hook returns `true` if the screen is focused, `false` otherwise
const isFocused = useIsFocused();
......
}
Or Triggering an action with a focus event listener​, you can control the re-renders
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// screen is focused
});
// Return the function to unsubscribe from the event so it gets removed on unmount
return unsubscribe;
}, [navigation]);
There's also a useFocusEffect.

Components aren't re-rendered after setState update, requires updating twice before re-render

I am creating a custom blog listing page in React for a HubSpot site (I've already searched there, couldn't find anything relevant to my issue). I'm still somewhat new to React but it is my first project from scratch in over a year. Basically what I am trying to do is combine three separate blogs into one page and add filtering options to show only posts from certain blogs and/or posts with certain tags attached to them. Currently I am successfully pulling the post data and sorting it. I am almost finished with the filtering functionality, but I am hitting a snag when trying to reset all filters.
I understand that setState is asynchronous and you can't expect the state value to updated immediately. My issue is that even after I have successfully updated the state, the useEffect hook doesn't fire consistently. I have tabs at the top of the page to switch between blogs and that works perfectly, as soon as I click one it filters out every other blog post and I can go back and forth between them easily. The problem is on the reset, I have to click the button twice for it to work. I'm completely at a loss as to why this is. In the console I see that the useEffect function for blogActive fires after the first time I click the reset button, but the blog view doesn't change. The second time I click the reset button, the blog view changes but the useEffect function doesn't fire.
I know that useEffect isn't supposed to fire if the state isn't actually updated (its being set to undefined when the button is clicked, so the second time it won't change) but I am still confused as to why this happens with the reset button and not with the blog switch function. I was originally resetting the blog from the reset function instead of just the sort function but that wasn't working either and calling it from the sort function is simpler in terms of displaying results with resetting the other filters (I have removed the tag filtering functions and components and still get the same results). I've also tried calling the handleBlogSwitch method instead of using the setState hook but I get the same results.
I've pasted the most relevant code below, but I've also included a sandbox link with a skeleton of the whole project including the code to fetch the post data.
Any help would be much appreciated!
function Blog({ blogName, blogID, handleBlog }) {
return (
<div className="blog">
<button className={"blog-" + blogName} onClick={() => handleBlog(blogID)}>
{blogName}
</button>
</div>
);
}
export default Blog;
function Archive() {
const [page, setPage] = useState(1);
const [blogActive, setBlog] = useState();
const [resetCheck, setChecked] = useState([]);
const [postState, setPosts] = useState(postArray);
let postArray = [];
function handleBlogSwitch(id) {
setBlog(id);
}
function sortPosts(sortme) {
let resetSort = false;
if (sortme) {
sortme.sort((a, b) => b["dataset"]["date"] - a["dataset"]["date"]);
postArray = [...postArray, ...sortme];
} else if (sortme === false) {
resetSort = true;
setBlog(() => {
return undefined;
});
}
let filtered = postArray;
if(!resetSort) {
if (blogActive) {
filtered = blogFilter(filtered);
setLoader("spinner-loaded");
}
}
setPosts(filtered);
setLoader("spinner-loaded");
}
function resetFilters(resetThis) {
setChecked([]); // Uncheck everything
sortPosts(resetThis);
}
// Handle logic for blog and tag filters
useEffect(() => {
setBtnLoad("btn-load-more"); // add this to the useEffect hook for tags too.
if(blogActive) {// Don't run this on page load.
sortPosts();
console.log("test blogactive effect");
}
}, [blogActive]);
return (
<div className="results__container">
<div className="tabs__container">
{Blogs.blogs.map((blog, key) => (
<Blog
key={key}
blogName={blog.name}
blogID={blog.id}
handleBlog={handleBlogSwitch}
></Blog>
))}
</div>
<div className="filter__container">
<button onClick={() => resetFilters(false)}>Reset Filters</button>
<div className="posts__container">
{postState.slice(0, page * resultsPerPage).map((html, key) => (
<Posts key={key} postHtml={html.outerHTML}></Posts>
))}
<img className={loader} src={logoSrc} />
<button className={btnClass} onClick={() => setPage(page + 1)}>
Load More
</button>
</div>
</div>
</div>
);
}
export default Archive;
https://codesandbox.io/s/wonderful-lalande-0w16yu?file=/src/App.js
Update: I didn't really fix this bug I'm facing, but I found a workaround. Instead of relying on the value of blogActive in sortPosts(), I created a new boolean in sortPosts which is set to true when it is called from the reset function. This way I know that the states should be resetting and skip that code, even if they aren't caught up yet.

React component switch animation with a loader - Glitchy

I have a requirement where i have couple of components out of which only one could be displayed at a particular point of time. The components are of varying heights.
When a user clicks on the button in the currently displayed component i need to
Show a loader to the user
Render the component in background.
Wait till the rendered component calls onLoad callback.
Once onLoad callback is recived, hide the loader.
Something like below (Code sandbox here)
const [loading, setLoading] = useState(false);
const [activeElement, setActiveElement] = useState("Component1");
const onLoad = () => {
setLoading(false);
};
const onClick = () => {
setLoading(true);
setActiveElement(
activeElement === "Component1" ? "Component2" : "Component1"
);
};
return (
<div className="container">
<ViewWrapperHOC loading={loading}>
{activeElement === "Component1" ? (
<Component1 onLoad={onLoad} onClick={onClick} />
) : (
<Component2 onLoad={onLoad} onClick={onClick} />
)}
</ViewWrapperHOC>
</div>
);
I was planning to write a wrapper component (ViewWrapperHOC in above example) to show the transition between the components and show the loader. Now the problem is when i try to animate, since i have to render both the progressbar and children in viewwrapper, the animation seems to be very glitchy.
Code sandbox here
Could someone suggest a way to fix this. I am open to any alternate animations or any alternate approach to achieve this in any form of pleasant ux. Thanks in advance.
In that second code sandbox, your issue is that as soon as you click, you are changing which element is active, therefore it gets rendered.
You could put a small timeout in the onClick function:
const onClick = () => {
setLoading(true);
setTimeout(() => {
setActiveElement(
activeElement === "Component1" ? "Component2" : "Component1"
);
}, 100);
};
Then in view wrapper you'll need to get rid of transition: "200ms all ease-in-out, 50ms transform ease-in-out".
You need to have a known height here to be able to make a smooth change in height transition.
See codesandbox fork
Version 2:
Here is version 2, using refs. Main changes are moving the box shadow out of the components to the container in view wrapper. Adding refs to the components, passing the active ref to the wrapper and then setting the height with height transition.
To be honest, I'd probably restructure the code a lot if I had the time.

How to remove delay on useMutation?

I have a button and I want to using the loading prop from the useMutation hook to activate the spinner, but there is the slightest lag after i click the button due to the delay in response. The loading prop is not immediately set to true.
const SomeComponent = () => {
const [addTodo, { data }] = useMutation(ADD_TODO);
return (
...
<Button onClick={() => addTodo("test")} loading={loading}>Submit</Button>
// Will take 500ms or so until the spinner spins due to delay in receiving
// which defeats the purpose of the loading spinner
</form>
)}
Is there an elegant solution for ensuring loading is set to true the instant I fire the mutation?

Resources