React Native & RTK Query - IsLoading on refetch method - react-native

Why when I use refetch method in the code below the isLoading value does not return on true during the fetch ?
const [matchFilterSelected, setMatchFilterSelected] = useState('explorer')
// Query declaration
const {
data: matches,
refetch,
error: matchesError,
isLoading: matchesIsLoading,
} = useFetchMatchesQuery(matchFilterSelected)
// Callback function from onPress event
const filterSelectedChanged = (matchesType) => {
if (matchesType && matchesType !== matchFilterSelected) {
setMatchFilterSelected(matchesType)
refetch()
}
}

No, isLoading is only true on initial load. You probably want to use isFetching - that shows actual activity.

Related

Making a useEffect cleanup for multiple axios calls in React Native

I am trying to make an axios call to a video game database with a URL specific to the chosen option by the user. Depending on the 'opt' chosen, it will get all the data linked to the option and place into a useState ('selectedResults') as shown below
function ViewContent() {
const [selectedOpt, setSelectedOpt] = useState('none');
const opts = ["Games", "Publishers and Developers", "Reviews", "Platform"];
const [selectedResults, setSelectedResults] = useState([]);
const getGames = (selectedOpt) => {
if(selectedOpt == 'Games'){
axios.get(apiURL.games)
.then(function (response){
results = JSON.stringify(response.data.data);
setSelectedResults(response.data.data);
})
.catch(function (error){
alert(error.message)
console.log('getGameTest', error);
})
}
else if(selectedOpt == 'Publishers and Developers'){
axios.get(apiURL.creators)
.then(function (response){
// results = JSON.stringify(response.data.data);
setSelectedResults(response.data.data);
})
.catch(function (error){
alert(error.message)
console.log('getCreatorTest', error);
})
}
else if(selectedOpt == 'Reviews'){
}
};
}
The error I get is
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
Is there a way I can use the useEffect cleanup for multiple axios calls or would I have to make one for each? I am trying to look over some examples but I am not getting any progress. If I missed anything, feel free to ask and I will update the question.
You might want to update react state when the component
is mounted.
export const useIsMounted = (): { readonly current: boolean } => {
const isMountedRef = React.useRef(false);
React.useEffect(() => {
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
};
}, []);
return isMountedRef;
};
You can create a hook just like the above one or you can prefer useIsMounted hook from this source in order to detect if the component is mounted. Then in your case, simply do the logic below.
if(isMounted.current) {
yourSetStateAction()
}

react native FlatList not rerendering when data prop changes

I have a FlatList component that uses Redux (indirectly) as the source for the data prop. See below
const mapStateToProps = state => ({
rdx_activeUsers: state.profile.activeUsers,
});
convertRdxActiveUsersObjectToUsableArray = () => {
let activeUsersArray = [];
Object.values(this.props.rdx_activeUsers).forEach(userObj => {
activeUsersArray.push(userObj);
});
return activeUsersArray;
};
//-----------------------------------------------------------------------
render() {
let usableArray4FlatList = this.convertRdxActiveUsersObjectToUsableArray();
return (
<View>
<FlatList
data={usableArray4FlatList}
Whenever an active user is added or removed in Firebase DB, I have listeners that increase or reduce the size of the Redux object rdx_activeUsers.......the goal is that this change in Redux should trigger the render() function (due to Redux being used in the convertRdxActiveUsersToUsableArray function....which is present in the render() function).
I can see via debugging tool that Redux rdx_activeUsers object is updating correctly whenever I add or remove a user....however the FlatList only rerenders dynamically when I add a user (i.e. the Redux object rdx_activeUsers increases in size)...but not when I remove one (i.e the Redux object rdx_activeUsers decreases in size).
I also tried adding prop extraData={this.props.rdx_activeUsers} ...but this didnt make any difference
UPDATE AS OF 12/31
Below are my reducers......
case ACTIVE_USER_CHILD_ADDED is successfully updating Redux AND triggering the rerender
case ACTIVE_USER_CHILD_REMOVED is successfully updating Redux BUT NOT triggering the rerender....looks like thats where the issue is
case ACTIVE_USER_CHILD_ADDED:
const key2Add = action.payload.userId;
const val2Add = action.payload;
return { ...state, activeUsers: { ...state.activeUsers, [key2Add]: val2Add } };
case ACTIVE_USER_CHILD_CHANGED:
const key2Updel = action.payload.userId;
const val2Updel = action.payload;
if (val2Updel.active) {
return { ...state, activeUsers: { ...state.activeUsers,[key2Updel]: val2Updel } };
}
if (state.activeUsers) {
const updatedstate = state;
delete updatedstate.activeUsers[key2Updel];
return {...updatedstate};
}
//else
return state;
case ACTIVE_USER_CHILD_REMOVED:
const key2Del = action.payload.userId;
const oldState = state;
delete oldState.activeMsgUsers[key2Del];
return {...oldState};
Below shows my action creators
export const _actActiveUserChildAdded = userObj => {
return {
type: ACTIVE_USER_CHILD_ADDED,
payload: userObj,
};
};
export const _actActiveUserChildChanged = userObj => {
return {
type: ACTIVE_USER_CHILD_CHANGED,
payload: userObj,
};
};
export const _actActiveUserChildRemoved = userObj => {
return {
type: ACTIVE_USER_CHILD_REMOVED,
payload: userObj,
};
};
change case ACTIVE_USER_CHILD_REMOVED like following it should work, you are modifying the object which was mutating the state object instead of returning a new object.
case ACTIVE_USER_CHILD_REMOVED:
const key2Del = action.payload.userId;
const {activeUsers:{[key2Del]:_,...restActiveUsers}} = state;
return {...state,activeUsers:{...restActiveUsers}};
This piece of code does not trigger a rerender
case ACTIVE_USER_CHILD_REMOVED:
const key2Del = action.payload.userId;
const oldState = state;
delete oldState.activeMsgUsers[key2Del];
return {...oldState};
By returning {...oldState} all the references inside this.state stay the same, and no update is triggered
fast solution
Put return {...oldState, activeMsgUsers: {...activeMsgUsers}} instead
better solution
Don't use delete, use Array.filter instead to create a new object reference
best solution
Refactor your conponent to hooks and make activeMsgUsers a standalone state, if you do it correctly, calling setActiveMsgUsers returning the destructured old state you cannot have that problem
try this
const mapStateToProps = state => ({
rdx_activeUsers: state.profile.activeUsers,
});
//-----------------------------------------------------------------------
render() {
let activeUsersArray = [];
Object.values(this.props.rdx_activeUsers).forEach(userObj => {
activeUsersArray.push(userObj);
});
return (
<View>
<FlatList
data={activeUsersArray}

React Native Hooks initializer not taking the correct value

What I am trying to do is sync a list of attendees from an online database, and if the current user is in the list, then disable a button, else enable the button.
I am using react native hook (I am not sure if I am using the term correctly as I am fairly new to react), in order to set the value of disabling the button.
The issue that I am facing is that the value is getting initialized to false, even tho it should clearly get initialized to true.
After adding some logging I made sure that the function is executing correctly and reaching the code where it sets the value to true.
const [buttonDisabled, changeButtonState] = useState( () => {
var database = firebase.database();
var userId = firebase.auth().currentUser.uid;
const dbRef = firebase.database().ref();
var Attendees = [];
var disable = false;
dbRef.child("gameAttendees").child(gameinfo.gameID).get().then((snapshot) => {
if (snapshot.exists()) {
Attendees = snapshot.val().Attendees;
for(var i=0;i<Attendees.length;i++){
if(Attendees[i]==userId){
return true;
}
}
} else {
console.log("no value");
return false;
}
}).catch((error) => {
console.error(error);
});
});
Adding an example of an async mount effect:
const Comp = () => {
const [s, setS] = useState(); // State will be undefined for first n renders
useEffect(() => {
// Call the async function and set the component state some time in the future
someAsyncFunction().then(result => setS(result));
}, []); // An effect with no dependencies will run only once on mount
return </>;
};

React Native hooks - correct use of useEffect()?

I'm new to hooks and ran across this setup on SO and wanted to confirm that this is the correct pattern. I was getting the RN "unmounted component" leak warning message before and this seemed to solve it. I'm trying to mimic in some way compnentDidMount. This is part of a phone number verify sign up flow and onMount I want to just check for navigation and then fire off a side effect, set mounted true and then unmount correctly.
const SMSVerifyEnterPinScreen = ({ route, navigation }) => {
const [didMount, setDidMount] = useState(false)
const { phoneNumber } = route.params
useEffect(() => {
if(navigation) {
signInWithPhoneNumber(phoneNumber)
setDidMount(true)
}
return () => setDidMount(false)
}, [])
if (!didMount) { return null }
async function signInWithPhoneNumber(phoneNumber) {
const confirmation = await auth().signInWithPhoneNumber('+1'+phoneNumber)
...
}
return (
...
)
}
RN 0.62.2 with react-nav 5 - thanks!
Since signInWithPhoneNumber is a async function and will setState you will see warning it the component is unmounted before the response is available
In order to handle such scenarios you can keep a variable to keep track whether its mounted or not and then only set state is the mounted variable is true
However you do not need to return null if component has unmounted since that doesn't accomplish anything. The component is removed from view and will anyways not render anything.
Also you do not need to maintain this value in state, instead use a ref
const SMSVerifyEnterPinScreen = ({ route, navigation }) => {
const isMounted = useRef(true)
const { phoneNumber } = route.params
useEffect(() => {
if(navigation) {
signInWithPhoneNumber(phoneNumber)
}
return () => {isMounted.current = false;}
}, [])
async function signInWithPhoneNumber(phoneNumber) {
const confirmation = await auth().signInWithPhoneNumber('+1'+phoneNumber)
...
}
return (
...
)
}

Call a function after state changes

I'm building a React Native app and when one button is pressed I want to call two functions. The first one will make a get call and set the state loading: true, the second one will show a popup with the result of that get call.
I am calling the second function only if loading === false but it is executed immediately after the first one before the state can change, because loading is false by default. I can resolve this with setTimeout but I was wondering if there was a cleaner way to do this.
onPress() {
this.props.getUsers();
setTimeout(() => {
if (this.props.loading === false) {
this.props.popUpVisible();
}
}, 1000);
}
You can create callback function for that
getUsers = (callback) => {
//do whatever you want
//when it's done
callback();
}
In onPress function
onPress = () => {
this.props.getUsers(() => {
if (this.props.loading === false) {
this.props.popUpVisible();
}
});
}
setState Function can take two param:
setState(updater, callback)
setState({loading:true},() => {
//this fires once state.loading === true
})
Use getDerivedStateFromProps. It always fire when component's props change.
Below is the example.
class EmailInput extends Component {
state = {
email: this.props.defaultEmail,
prevPropsUserID: this.props.userID
};
static getDerivedStateFromProps(props, state) {
// Any time the current user changes,
// Reset any parts of state that are tied to that user.
// In this simple example, that's just the email.
if (props.userID !== state.prevPropsUserID) {
return {
prevPropsUserID: props.userID,
email: props.defaultEmail
};
}
return null;
}
// ...
}