redux state not updating (React-Native) - react-native

I have 8 APIs when called save on the same redux state, and I am saving them like this in the reducer:
case types.EXPLORE_CHEF_BY_TYPE:
return {
...state,
[action.data.type + '_chefs']: action.data.data,
};
for example it will be trending_chefs, new_chefs ...etc.
and in my component I have this:
function ExploreScreen({
...,
...exploreData
}) {
...
const mapStateToProps = (state) => {
return state.explore;
};
and to access the data I am doing this
const handleCategoryClick = useCallback((categoryName) => {
const selected =
selectedQuery.name.toLowerCase() + '_' + categoryName.toLowerCase();
if (exploreData[selected]) {
setSelectedCategory(categoryName);
setData(exploreData[selected]);
} else {
selectedQuery[`dataFunc_${categoryName}`]();
setSelectedCategory(categoryName);
}
}, []);
the problem is the data is not persisting, in the above function I am checking If I already called that API and if I did then I just fetch the data without calling the API. Am I doing something wrong here ?
I can't figure out what the problem is, since sometimes it works and sometimes it doesn't. Also. I think the exploreData in the state is causing the problem.
Thank you for the Help.

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}

Unable to set useState variable in async method and console log it

Im working with my friends on a app project and we find an issue many times when we tring to set a use state and the console log the variable, i've looking for a solution in the website and saw that the reason is that the usestate is an async awiat which means the variable that i set in the use state isn't immidatly set in it, so i tried many solution that i found in the websites but none of them work for me.
in the screenShot you can see that the json variable is in console log before and the set varaible doesn't show after the setActiveUser , any help?
Thanks!
If you want to do something upon setting state then your best bet is to use the useEffect hook from React and adding your state in the dependency array. This will then call the function in your useEffect every time the state changes. See below a rough example:
import { Text } from 'react-native';
const MyComponent = () => {
const [activeUser, setActiveUser] = useState(null);
useEffect(() => {
// This should log every time activeUser changes
console.log({ activeUser });
}, [activeUser]);
const fetchAuthentication = async user => {
var flag = false;
await fetch('/api/authUser/', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user),
})
.then(res => {
res.ok && (flag = true);
return res.json();
})
.then(json => {
if (flag) {
setActiveUser(json);
}
})
.catch(error => console.error(error));
return flag;
};
return <Text>Hi</Text>;
};
Full documentation: https://reactjs.org/docs/hooks-effect.html

Confused about how should I work with Redux and Database

I'm trying to figure out how to use Redux in my React Native application.
I use Realm as a local database, and I'm pretty confused about how to implement it.
In Realm, I save an array of custom objects, so when the app starts I want to fetch the array and set it in the state of the app.
As far as I understand, I need to have an action, which looks like this:
export const fetchItems = () => {
return (dispatch) => {
dispatch({
type: "FETCH_ITEMS",
})
}
}
And have a reducer that looks kinda like this:
const initialState = {
items: [],
}
const reducer = (state = initialState, action: AnyAction) => {
switch (action.type) {
case ActionType.fetchItems:
return {
...state,
items: action.payload
}
break;
default:
return state;
}
}
But I'm not really sure how should I use this in my Home Screen for example.
I guess it should be something like this:
const { items } = useSelector(state => store.getState().items)
But then, when I'm adding a new item to the database, I should of course update the database, so when I should update the state? I tried to read articles, and watch some tutorials, but everyone works a little different and it is even more confusing.
So far, is what I wrote about Redux is right? should be using it like this?

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 (
...
)
}