React Native Multiselect - react-native

I am new to React Native
I am trying to create a multiselect view where user can select and deselect the items and then the selected items should pass back to the previous container and when user comes back to the next view the selected items should be checked.
I am trying to implement but getting the issue it is not updating data accurately. It shows only 1 selected item when I came back again to the screen.
Can anyone tell me the best way to do that or if there is any tutorial.
Should I do it with Redux or using react native?
Any help would be appreciated!!
Thanks!!

I believe the issue you describe is due to the following:
In componentDidMount you are calling updateItemWithSelected in a loop. This updateItemWithSelected call is both overwriting the checked attributes for all of the arrayHolder values on each call and also not using the updater function version of setState, so the later call of the loop may overwrite the earlier calls since setState is async and batched. If you are not using updateItemWithSelected elsewhere you should simplify componentDidMount to:
componentDidMount() {
const selectedTitles = {};
const { state } = this.props.navigation
const params = state.params || {};
if (params.data.length){
params.data.forEach((element) => {
// create a map of selected titles
selectedTitles[element.title] = true;
})
}
const arrayHolder = this.array.map(item => {
// map over `this.array` and set `checked` if title is in `selectedTitles`
return {...item, checked: !!selectedTitles[item.title]};
});
this.setState({ arrayHolder });
}
and delete updateItemWithSelected.

Related

Trigger UseEffect whenever i switch tabs in React Native Tab Navigator

I have implemented react native tab navigator and added 4 screens to it.
I post some record to api in the second screen and i want to have the updated record in the 4th screens where i am getting updated records..
Useeffect only gets targeted only once, and when i put something in it's argument it gives me strange behavior.
I want useeffect to reload and call the api to get latest items in the 4th screen without putting anything in it's arguement(empty argument)
Any help would be highly appreciated.
Try doing this ;
useEffect(() => {
const unsubscribe = navigation.addListener("focus", () => {
makeApiCall();
});
return unsubscribe;
}, [navigation]);
Get navigation in component's arguments(destructuring)
like below;
const My4thTab = ({ navigation }) => {
}
This way useEffect will trigger only once, every time you come on this screen
but make sure to clear the previous state where you store your data, otherwise, there could be a record duplication.
Hope it helps :)

Render useEffect/Async function from a difference screen

I have an async function and a useEffect that fetches data once.
const [data, setData] = useState([]);
async function fetchData() {
fetch(`${baseURL}api/v1/data/${userId}`)
.then((response) => response.json())
.then((response) => {
try {
if (response.length > 0) {
setData(response);
} else {
setData([]);
// console.log(response);
}
} catch (err) {
console.log('no response');
alert(err);
}
});
}
useEffect(() => {
fetchData();
}, [userId, data]);
I could remove the array on the use effect but it will always run the function if I do that.
So when I open the screen, it will fetch the latest data. However, if I want to add a new data from a different screen, it wont trigger the async nor the useEffect function. How should I tell RN that there is a new data? Would AsyncStorage work? to update a data from one screen and apply the data here? I am open for suggestions on how to proceed.
What I meant by a different screen: A register screen and a view screen. In this case, I already opened the View Screen before I open the register screen so view screen is already rendered.
In React Navigation and most of the navigation libraries, screens don't get unmounted from the stack when it's navigated to another screen. For example if you have a list of something and then you press to "+" button to navigate to the "new item" screen to add a new one, when you press back button, since the previous "list" screen was not unmounted from the stack, useEffect won't be triggered, and you won't get the new data.
There are a couple of solutions for this case:
You can hold your data in a global state, and when you update an item from another screen, after a successful API call, you can also update the global state. You can look for React Context, MobX or Redux for this.
You can pass parent's state with a callback from one screen to another if they are not that apart from each other. So that in the "new data" screen, you can call that callback function to change the parent screen's state too.
Third, and IMO the best way is using a hook called useFocusEffect by React Navigation itself: https://reactnavigation.org/docs/use-focus-effect
I hope these will help.

React Native: Didn't rerender the component after data was updated

i have two screens one where the profile information is showing and another screen to edit the information. If i entered the first screen profile it's shows me the right data from the database. Then i move to the next screen where i can change the Information everthing worked so far. But if I go back to the previous screen i still see the old data. So there is no rerendering.
But if i navigate to the other screen that screen fetched the new data. and the call getCurrentUserProfile is executed
This ist the screen with the profile information about the user.
const ProfileScreen = props => {
const [userObj, setUserObj] = useState({});
useEffect(() => {
let mounted = true;
getCurrentUserProfile().then(user => {
if (mounted) {
setUserObj(user);
}
console.log(user)
});
return () => mounted = false;
}, []);
console.log("----b------") // This is only output on the first call
}
How can i fix this. Is there a way when in the database is something changed, so the component rerender and fetches the new data.
Thanks.
You are probably using #react-navigation package. So, if you want to refetch data when come back to the previous page, you can use https://reactnavigation.org/docs/use-is-focused on that “previous” page.
Just look at their docs, you will get the idea.
P.S: Usually we don't unmount components directly. In React Native, we use navigator to mount/unmount components

Pass variables via `this.props.navigation` multiple times

So, for begging, in react-native-navigation there's a possibility to pass some data via this.props.navigation.navigate().
Here's how you should pass the data :
this.props.navigation.navigate('RouteName', {/*Data to pass*/})
And so, moving to the problem
The case where this problem was encountered :
I have a list of items which I click on and I navigate to the next screen, the data of the pressed item being sent during the navigation process, and when I get to the next screen, the passed data is assigned to state, and I further operate with it. Here are the commands which I use for passing data:
Pass data
this.props.navigation.navigate('Screen2',{param1: value1, param2: value2})
Receive data
ComponentWillMount = () => {
const param1 = this.props.navigation.getParam('param1');
const param2 = this.props.navigation.getParam('param2');
this.setState({param1, param2)}
}
The Problem itself
My Problem is that if I go back to the first screen, and press on another item, then it's data isn't passed via this.props.navigation.navigate(), the data on the second screen remains unmodified from the first navigation process. How this problem can be resolved?
I think i figured it out,
I was able to replicate the issue using drawerNavigator and tabbed navigator in the react-navigation 3.0.5.
Basically they save the components even when you run navigation.goBack.
The screen isn't being mounted again so it doesnt call componentWillMount() and it doesn't check for data there.
there are 2 (edit 3) ways to fix this.
one is to turn off this performance enhancement
const MyApp = createDrawerNavigator(
{
Screen1: Screen1,
Screen2: Screen2
},
{
unmountInactiveRoutes: true
}
);
The second option and the more elegant one is to subscribe to navigation events
componentWillMount() {
console.log("mounting");
const willFocusSubscription = this.props.navigation.addListener(
"willFocus",
() => {
console.debug("willFocus");
const thing = this.props.navigation.getParam("thing");
const thing2 = this.props.navigation.getParam("thing2");
this.setState({thing, thing2});
}
);
}
Just dont forget to unsubscribe in componentWillUnmount
componentWillUnmount() {
willFocusSubscription.remove();
}
The third way is basically the same as the second but subscribing declaratively. This means no componentWillMount or WillUnmount.
First a callback to set the state appropriately
willFocus = ({action}) => {
console.debug("willFocus", action);
const thing = action.params["thing"];
const thing2 = action.params["thing2"];
this.setState({thing, thing2});
};
now in render add the component
render() {
console.log("data is:", this.state.thing);
return (
<View style={styles.container}>
<NavigationEvents
onWillFocus={this.willFocus}
/>
.... rest of render body
</View>
);
}
This doesn't display anything but it takes care of subscribing and unsubscribing.

Why can't I access state in ComponentDidMount?

I have this code for React Native:
componentWillMount() {
shiftKeys = []; // need to clear previously set data or we will get dupicate array key errors
// get the current user from firebase
const userData = firebaseApp.auth().currentUser;
const profileRef = firebaseApp.database().ref('userdata').child(userData.uid);
profileRef.on('value', snapshot => {
if (snapshot.val().hasOwnProperty('licenseType') && snapshot.val().hasOwnProperty('licenseState') && snapshot.val().hasOwnProperty('distance')) {
this.setState({
licenseType: snapshot.val().licenseType,
licenseState: snapshot.val().licenseState,
distancePref: snapshot.val().distance,
});
console.log('State1', this.state.distancePref)
} else {
// redirect back to profile screens because we need three values above to search.
this.props.navigation.navigate('Onboarding1')
}
});
}
componentDidMount() {
console.log('State2', this.state.distancePref)
var geoQuery = geoFire.query({
center: [45.616422, -122.580453],
radius: 1000// need to set dynamically
});
I think this is some kind of scope issue?
When I look at the console log, State 1 is set correctly, but State 2 prints nothing.
In my app I need to look up a users distance preference, then use that to run a query.
How do I pass the value from componentWillMount to componentDidMount?
https://facebook.github.io/react/docs/react-component.html#the-component-lifecycle
setState in componentWillMount - bad way. You do not solve the problem this way, because state will not be updated until componentDidMount (see lifecycle). Check your condition when creating the state, in the constructor.
Or you can solve the problem using redux.
The root issue with this problem had to do with my not understanding how react and react native render the code.
I was trying to get users info from firebase, then set preferences, then use those preferences to run a search.
I added redux and thunk to handle the getting and saving of the users preferences separately from (and before) the user has a chance to search.