How do I update state inside function in react native? - react-native

I have a simple functional component, where depending on the state I show "add item" or "remove item" button:
const SimpleComponent = ({ route }) => {
const { id } = route.params;
const [add, setAdd] = useState(false);
useEffect(() => {
{ isCurrentUserHavingThisItem(route, id) ?
setAdd(true)
:
setAdd(false)
}
}, [])
return (
<View >
{ add ?
(
<TouchableOpacity onPress={() => removeItem(route, id)}>
<Text> Remove this item </Text>
</TouchableOpacity>
)
:(
<TouchableOpacity onPress={() => addItem(route, id)}>
<Text> Add item </Text>
</TouchableOpacity>
)
}
</View>
);
};
const isCurrentUserHavingThisItem = (route, id) => {
firebase.firestore()
.collection('Users')
.doc(id)
.collection('Items')
.doc(route.params.item.key)
.get()
.then((docSnapshot) => {
console.log("my return statement: ", docSnapshot.exists);
setAttending(docSnapshot.exists);
return docSnapshot.exists;
})
.catch((error) => {
console.log(error);
return false;
});
}
I think that isCurrentUserHavingThisItem is working fine. However, when I press the "Add item" I trigger the following function:
const addItem = (route, id) => {
firebase.firestore()
.collection('Users')
.doc(id)
.collection('Items')
.doc(route.params.item.key)
.set({})
.then(setAdd(true));
}
Which gives me the following error: ReferenceError: Can't find variable: setAdd I also tried the following:
const addItem = (route, id) => {
firebase.firestore()
.collection('Users')
.doc(id)
.collection('Items')
.doc(route.params.item.key)
.set({});
setAdd(true);
}
but it gave me the same error. Here is the whole component structure for better visibility:
const SimpleComponent = ({ route }) => {
const { id } = route.params;
const [add, setAdd] = useState(false);
useEffect(() => {
{ isCurrentUserHavingThisItem(route, id) ?
setAdd(false)
:
setAdd(true)
}
}, [])
return (
<View>
{ add ?
(
<TouchableOpacity onPress={() => removeItem(route, id)}>
<Text style={styles.title}>Remove item</Text>
</TouchableOpacity>
)
:(
<TouchableOpacity onPress={() => addItem(route, id)}>
<Text style={styles.title}>Add item</Text>
</TouchableOpacity>
)
}
</View>
);
};
const isCurrentUserHavingThisItem = (route, id) => {
//same as I described it above; I think it works fine
}
const addItem = (route, id) => {
//same as described above
//gives me error when I try to update the state
}
const leaveMeetup = (route, id) => {
//same as described above
//gives me error when I try to update the state
}
export default SimpleComponent;
Basically, after I add the item successfully, I want to display "remove item" button and vice versa. In order to do so I try to update the state but as I said I have ReferenceError: Can't find variable: setAdd error and I dont see why. Can someone help?

Your setAdd method is outside the component that's why it is showing that error...Please move your methods inside your functional component like this.
const SimpleComponent = ({ route }) => {
const { id } = route.params;
const [add, setAdd] = useState(false);
const isCurrentUserHavingThisItem = (route, id) => {}
const addItem = (route, id) => {}
const leaveMeetup = (route, id) => {}
useEffect(() => {
{ isCurrentUserHavingThisItem(route, id) ?
setAdd(false)
:
setAdd(true)
}
}, [])
return (
<View>
{ add ?
(
<TouchableOpacity onPress={() => removeItem(route, id)}>
<Text style={styles.title}>Remove item</Text>
</TouchableOpacity>
)
:(
<TouchableOpacity onPress={() => addItem(route, id)}>
<Text style={styles.title}>Add item</Text>
</TouchableOpacity>
)
}
</View>
);
};
export default SimpleComponent;
and i think you can't do it like this.
.then(setAdd(true));
You get a callback in .then()
So the correct way to do this is
.then(()=>{
setAdd(true)
}).

Related

How to use asyncStorage inside useEffect

I'm building a mobile game using react native and I'm trying to retrieve the best value storage on it to display on the screen. The problem is that it seems that react native is rendering the screen before it retrieves the value and then it doesn't re-render when the value is updated using setBest(), so no value is displayed.
Here is the code:
const navigation = useNavigation()
const [result, setResult] = useState('')
const [best, setBest] = useState('')
useEffect(() => {
const Storage = async (key,value) => {
await AsyncStorage.setItem(key,value)
}
const Retrieve = async (key) => {
const value = await AsyncStorage.getItem(key)
setBest(()=>value)
}
Retrieve('1').catch(console.error)
setResult(route.params.paramKey)
if(route.params.paramKey>best){
var aux = result.toString()
Storage('1',aux)
console.log(best)
}
}, [])
return (
<View style={styles.container}>
<View style={styles.textView}>
<Text style={styles.tituloText}>Melhor pontuação</Text>
<Text style={styles.tituloText}>{best}</Text>
<Text style={styles.tituloText}>Sua pontuação</Text>
<Text style={styles.resultText}>{result}</Text>
<View style={styles.viewBtn}>
<TouchableOpacity style={styles.viewBack} onPress={() => navigation.navigate('Modo1')}>
<Icon style={styles.iconBack} name="backward" />
</TouchableOpacity>
<TouchableOpacity style={styles.viewHome} onPress={() => navigation.dispatch(StackActions.popToTop)}>
<Icon style={styles.iconBack} name="home" />
</TouchableOpacity>
</View>
</View>
</View>
);
}
Thanks for the help guys! I've been struggling with this for days and any help will be appreciated!
This is how you retrieve the value..
useEffect(() => {
AsyncStorage.getItem('key').then(value => {
if (value != null) {
console.log(value);
setBest(value);
}
});
}, []);
also don't forget to add the import statement..
To set the value you must use
AsyncStorage.setItem('key', value);
You can use Async Functions inside of ~useEffect()` like this:
useEffect(() => {
(async () => {
async function getData() {
try {
const value = await AsyncStorage.getItem('myKey');
if (value !== null) {
setData(value);
}
} catch (error) {
console.log(error);
}
}
getData();
})();
}, []);
}

Warning: Each child in a list should have a unique "key" prop. React Native

Hello i'm trying to build a mini social media app with react native, getting the following error:
Warning: Each child in a list should have a unique "key" prop.
Check the render method of VirtualizedList
tried to use results.map instead of flat list also.
and also to change the key extractor with solutions on stackoverflow, but no success.
anyone can help please ?
code:
return (
<FlatList
style={{flex:1}}
data={results}
keyExtractor={(item, index) => {
return item.id;
}}
renderItem={({item}) => <TouristCard key={item.id} item={item} onPress={()=> navigation.navigate('HomeProfile', {userId: item.userId})} /> }
/>
);
TouristCard:
const TouristCard = ({item,onPress}) => {
return (
<View key={item.id} style={styles.listItem} >
<UserImg source={{uri: item.userImg ? item.userImg : 'https://lh5.googleusercontent.com/-b0PKyNuQv5s/AAAAAAAAAAI/AAAAAAAAAAA/AMZuuclxAM4M1SCBGAO7Rp-QP6zgBEUkOQ/s96-c/photo.jpg'}}/>
<View style={{alignItems:"center",flex:1}}>
<Text style={{fontWeight:"bold"}}>{item.fname ? item.fname : 'Annonymous' } {item.lname ? item.lname : '' }</Text>
<Text>{item.flightDesc}</Text>
<Text style={{fontWeight:"bold"}}>{item.age}</Text>
</View>
<Text style={{color:"blue"}}>View Profile</Text>
</View>
);
results object:
const filterByDates = (results,startDate,endDate) => {
return results.filter(res => {
return startDate<res.endDate && res.startDate<endDate;
});
};
useEffect(() => {
const fetchCommunity = async() => {
try {
//const list = [];
await firestore()
.collection('flights')
.where("destination","==",destination)
.where("user","!=",user.uid)
.get()
.then((querySnapShot) => {
querySnapShot.forEach(doc => {
const {user,flightDesc,startDate,endDate} = doc.data();
list.push({
user: user,
flightDesc,
startDate,
endDate
});
})
})
setResults(filterByDates(list,startDate,endDate));
fetchProfiles();
} catch(e) {
console.log(e);
}
}
const fetchProfiles = async() => {
try {
//const list2=[];
list.forEach(async obj => {
await firestore()
.collection('users')
.where("userId","==",obj.user)
.get()
.then((querySnapShot) => {
querySnapShot.forEach(doc => {
const {userImg,age,fname,lname} = doc.data();
list.push({
id: doc.id,
userImg,
age,
fname,
lname
});
})
})
setResults(list);
console.log(list);
})
} catch (e) {
console.log(e);
}
}
fetchCommunity();
},[]);
Replace your Flatlist with this.
<FlatList
style={{flex:1}}
data={results}
keyExtractor={(item) => item.id.toString()} // Here was the error
renderItem={({item}) => <TouristCard item={item} onPress={()=> navigation.navigate('HomeProfile', {userId: item.userId})} /> }
/>
Also check whether each element of your results has a unique id or not?
And MAIN point... Check does your array items has a property called id or not..and if userId is unique then use that as shown above

Show ActivityIndicator in React Native?

I have a function to fetch items from an API that is inside UseEffect. And i'm looking to call this function every time the status of the selectedItem or the items changes and show an ActivityIndicator before the function returns the result. The ActivityIndicator appears when the items are uploading but not when the status of the selectedItem changes ?
I have my code like this :
export default () => {
const [items, setItems] = useState();
const [selectedItem, setSelectedItem] = useState(null);
const [isLoading, setLoading] = useState(true);
const getItems = () => {
get('/api/items').then((rep) => {
setItems(rep);
setLoading(false);
}
});
};
useEffect(() => {
getItems();
}, [selectedItem.status]);
return (
<SafeAreaView style={styles.container}>
{isLoading ? (
<View style={[styles.spinnerContainer, styles.horizontal]}>
<ActivityIndicator />
</View>
) : ((items !== [])
&& (
<SectionList
stickySectionHeadersEnabled={false}
style={{ paddingHorizontal: 20, }}
sections={items}
refreshing={isLoading}
keyExtractor={(item, index) => item + index}
...
/>
))}
</SafeAreaView>
);
};
You can try setLoading(true) inside getItems
const getItems = () => {
setLoading(true);
get('/api/items').then((rep) => {
setItems(rep);
setLoading(false);
});
};

React-Native FlatList item clickable with data to another screen

I'm trying to access a screen when you click on an item in my flatlist by passing the date I retrieved from the firebase before, I've tried several things without success so I come to you.
Basically when I click on one of the elements -> A screen with details should appear.
export default function Notifications() {
const dbh = firebase.firestore();
const [loading, setLoading] = useState(true); // Set loading to true on component mount
const [deliveries, setDeliveries] = useState([]); // Initial empty array of users
useEffect(() => {
const subscriber = dbh
.collection("deliveries")
.onSnapshot((querySnapshot) => {
const deliveries = [];
querySnapshot.forEach((documentSnapshot) => {
deliveries.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setDeliveries(deliveries);
setLoading(false);
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}, []);
if (loading) {
return <ActivityIndicator />;
}
return (
<FlatList
style={{ flex: 1 }}
data={deliveries}
renderItem={({ item }) => (
<TouchableOpacity
onPress={() => { * HERE I NEED TO PASS DATA AND SHOW AN ANOTHER SCREEN FOR DETAILS * }}>
<View style={styles.container}>
<Text>DATE: {item.when}</Text>
<Text>ZIP DONATEUR: {item.zip_donator}</Text>
<Text>ZIP BENEFICIAIRE: {item.zip_tob_deliv}</Text>
</View>
</TouchableOpacity>
)}
/>
);
}
EDIT: Small precision this screen is located in a Tab.Navigator
you can pass params in navigation,
export default function Notifications(props) {
const { navigation } = props
const dbh = firebase.firestore();
const [loading, setLoading] = useState(true); // Set loading to true on component mount
const [deliveries, setDeliveries] = useState([]); // Initial empty array of users
useEffect(() => {
const subscriber = dbh
.collection("deliveries")
.onSnapshot((querySnapshot) => {
const deliveries = [];
querySnapshot.forEach((documentSnapshot) => {
deliveries.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setDeliveries(deliveries);
setLoading(false);
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}, []);
if (loading) {
return <ActivityIndicator />;
}
return (
<FlatList
style={{ flex: 1 }}
data={deliveries}
renderItem={({ item }) => (
<TouchableOpacity
onPress={() => {
navigation.navigate('screenName', {
//pass params here
})
}}>
<View style={styles.container}>
<Text>DATE: {item.when}</Text>
<Text>ZIP DONATEUR: {item.zip_donator}</Text>
<Text>ZIP BENEFICIAIRE: {item.zip_tob_deliv}</Text>
</View>
</TouchableOpacity>
)}
/>
);
}
you can access params in the navigated screen by props.route.params

Calling function in main file from component

I have recently refactored my app from using Class components to Functional components and having issues with a few last things.
My Home.js looks like the following (simplified):
// imports....
import { StartStopButtons } from "../components/Button";
export default ({ navigation }) => {
const [scrollEnabled, setScrollEnabled] = useState(false);
const [elapsedMilliseconds, setElapsedMilliseconds] = useState(0);
const [isRunning, setIsRunning] = useState(false);
const [startTime, setStartTime] = useState(false);
const [stopTime, setStopTime] = useState(false);
const [isReset, setIsReset] = useState(true);
start = () => {
console.log("START");
// stuff
};
reset = () => {
console.log("RESET");
// stuff
};
stop = () => {
console.log("STOP");
// stuff
};
return (
<View style={styles.container}>
<StartStopButtons
isRunning={isRunning}
isReset={isReset}
elapsedMilliseconds={elapsedMilliseconds}
/>
</View>
);
};
My StartStopButtons has a different look, depending of the current state, it will either display Start, Stop or Reset and call the corresponding function. I am currently putting this intelligence in another file, my Button.js file.
Button.js :
//imports....
export const StartStopButtons = ({
isRunning,
isReset,
elapsedMilliseconds,
}) => {
if (isRunning && isReset === false) {
return (
<View>
<TouchableOpacity onPress={stop}>
<Text>Stop</Text>
</TouchableOpacity>
<TouchableOpacity onPress={pause}>
<Text>Pause</Text>
</TouchableOpacity>
</View>
);
} else {
if (elapsedMilliseconds === 0) {
return (
<TouchableOpacity onPress={start}>
<Text>Start</Text>
</TouchableOpacity>
);
} else {
return (
<TouchableOpacity onPress={reset}>
<Text>Reset</Text>
</TouchableOpacity>
);
}
}
};
Before the refactoring, I was using this.state.start, this.state.stop to call my start and stop functions, located in Home.js.
How can I achieve that now? Is there a better approach?
You can pass the functions as props exactly like how you pass isRunning, isReset, and elapsedMilliseconds.
But please add const before function names as well.
// imports....
import { StartStopButtons } from "../components/Button";
export default ({ navigation }) => {
const [scrollEnabled, setScrollEnabled] = useState(false);
const [elapsedMilliseconds, setElapsedMilliseconds] = useState(0);
const [isRunning, setIsRunning] = useState(false);
const [startTime, setStartTime] = useState(false);
const [stopTime, setStopTime] = useState(false);
const [isReset, setIsReset] = useState(true);
const start = () => {
console.log("START");
// stuff
};
const reset = () => {
console.log("RESET");
// stuff
};
const stop = () => {
console.log("STOP");
// stuff
};
const pause = () => {};
return (
<View style={styles.container}>
<StartStopButtons
start={start}
stop={stop}
reset={reset}
pause={pause}
isRunning={isRunning}
isReset={isReset}
elapsedMilliseconds={elapsedMilliseconds}
/>
</View>
);
};
and use them like
//imports....
export const StartStopButtons = ({
start,
stop,
reset,
pause,
isRunning,
isReset,
elapsedMilliseconds,
}) => {
if (isRunning && isReset === false) {
return (
<View>
<TouchableOpacity onPress={stop}>
<Text>Stop</Text>
</TouchableOpacity>
<TouchableOpacity onPress={pause}>
<Text>Pause</Text>
</TouchableOpacity>
</View>
);
} else {
if (elapsedMilliseconds === 0) {
return (
<TouchableOpacity onPress={start}>
<Text>Start</Text>
</TouchableOpacity>
);
} else {
return (
<TouchableOpacity onPress={reset}>
<Text>Reset</Text>
</TouchableOpacity>
);
}
}
};