React hook isn't updating data received from a fetch function - javascript

The Text object continues to display "Pokemon" instad of pokemon.name. Any ideas?
export default function App() {
const getRandomPokemon = async () => {
const randomID = Math.floor(Math.random() * 898);
const uri = `https://pokeapi.co/api/v2/pokemon/${randomID}`;
return fetch(uri)
.then(response => response.json())
.then(data => {
return {
name: data.forms[0].name,
height: data.height,
weight: data.weight,
};
})
}
const [FirstCard, setFirstCard] = React.useState("Pokemon");
return (
<View style={styles.container}>
<CardStack
loop={true}
style={styles.cardStack}
onSwipeStart={async () => {
const pokemon = await getRandomPokemon();
setFirstCard(await pokemon.name)
console.log(pokemon)
}}
>
<Card style={styles.card}><Text>{FirstCard}</Text></Card>
<Card style={styles.card}><Text>2</Text></Card>
</CardStack>
</View>
);
}
const styles = StyleSheet.create({
...
});

Probably pokemon.name is not a Promise. You should remove the await before pokemon.name in this case.

it's a problem with the package you are using. see
github.com/lhandel/react-native-card-stack-swiper/issues/76
you don't need to await for anything, just fetch and set the first card in the then function.
export default function App() {
const [FirstCard, setFirstCard] = React.useState("Pokemon");
const getRandomPokemon = () => {
const randomID = Math.floor(Math.random() * 898);
const uri = `https://pokeapi.co/api/v2/pokemon/${randomID}`;
fetch(uri)
.then(response => response.json())
.then(data => {
setFirstCard(data.forms[0].name);
})
}
useEffect(()=>{console.log(FirstName);},[FirstCard]);
return (
<View style={styles.container}>
<CardStack
loop={true}
style={styles.cardStack}
onSwipeStart={() => {
getRandomPokemon();
}}
>
<Card style={styles.card}><Text>{FirstCard}</Text></Card>
<Card style={styles.card}><Text>2</Text></Card>
</CardStack>
</View>
);
}

Related

How do I rerender a FlatList when a value of an object changes?

The idea is to have a toggle switch that switches from To Watch and Watched bookings. When I update a movie in the To Watch list, I want it to rerender the FlatList without that booking because it's now in the Watched list. And so when I press the toggle to see the Watched list I would now see the booking there. Same for reverse. This is the code I have:
const WatchList = () => {
const uid = auth.currentUser.uid;
const docRef = doc(db, 'users', uid);
const [user, setUser] = useState({});
const [watched, setWatched] = useState(true);
const [text, setText] = useState('To watch');
const [filteredBookings, setFilteredBookings] = useState(bookings);
const bookingsRef = collection(db, "booking"); // imamo ref ka bazi
const [bookings, setBookings] = useState({});
useEffect(() => {
getUser();
getBookings();
},[])
const getUser = async () => {
const snap = await getDoc(docRef)
setUser({user, ...snap.data()})
}
const getBookings = async () =>{
const q = query(bookingsRef, where("users","array-contains",auth.currentUser.uid));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const a = [];
querySnapshot.forEach((doc) => {
a.push(doc.data());
});
setBookings(querySnapshot.docs);
});
}
const toggleSwitch = () =>{
if(!watched){
setFilteredBookings(bookings.filter(function(item){
return item.data().watched == true;
}))
setText('Watched');
} else {
setText('To watch');
setFilteredBookings(bookings.filter(function(item){
return item.data().watched == false;
}))
}
setWatched(previousState => !previousState);
}
const updateBooking = async(id) => {
try {
await setDoc(doc(db, 'booking', id), {
watched: true
}, {merge: true})
}
catch(e) {
console.log(e)
}
}
const updateBooking1 = async(id) => {
try {
await setDoc(doc(db, 'booking', id), {
watched: false
}, {merge: true})
}
catch(e) {
console.log(e)
}
}
const deleteBooking = async(id) => {
try {
await deleteDoc(doc(db, 'booking', id));
}
catch(e) {
console.log(e)
}
}
useEffect(() => {
const resultOfFiltering = bookings
setFilteredBookings(resultOfFiltering)
}, [bookings])
return (
<View>
<View>
<Text>{text}</Text>
<Switch
value = {watched}
/>
</View>
<FlatList
data = {filteredBookings}
renderItem = {({item}) => (
<View>
<View>
<Text>{item.data().movie}</Text>
<Text>{item.data().day} - {item.data().showtime}</Text>
</View>
<View>
{item.data().watched == true ? (
<TouchableOpacity style = {styles.delete} onPress = {() => {updateBookings(item.id); setFilteredBookings();}}>
<Image source={require('../assets/watched.png')}/>
</TouchableOpacity>
) : (
<TouchableOpacity onPress = {() => {updateBookings1(item.id); setFilteredBookings();}}>
<Image source={require('../assets/towatch.png')}/>
</TouchableOpacity>)}
<TouchableOpacity onPress = {() => {deleteBooking(item.id); setFilteredBookings();}}>
<Image source={require('../assets/remove.png')}/>
</TouchableOpacity>
</View>
</View>
)}
/>
</View>
)
}
export default WatchList
It's not really working properly at the moment and I'm not sure what I'm doing wrong so I'm kind of stuck. If I could get some help, I'd really appreciate it. Thank you!

How do I get a FlatList to show correct items when I open the screen?

The idea is to have a toggle switch that switches from To Watch and Watched bookings. When I update a movie in the To Watch list, I want it to rerender the FlatList without that booking because it's now in the Watched list. And so when I press the toggle to see the Watched list I would now see the booking there. Same for reverse.
I have to press the toggle twice to see the change. Even when I open the screen for the first time it shows all the movies without the filter - this is the main thing I'm having trouble with. How can I get around this? This is the code I have:
const WatchList = () => {
const uid = auth.currentUser.uid;
const docRef = doc(db, 'users', uid);
const [user, setUser] = useState({});
const [watched, setWatched] = useState(true);
const [text, setText] = useState('To watch');
const [filteredBookings, setFilteredBookings] = useState(bookings);
const bookingsRef = collection(db, "booking"); // imamo ref ka bazi
const [bookings, setBookings] = useState({});
useEffect(() => {
getUser();
getBookings();
},[])
const getUser = async () => {
const snap = await getDoc(docRef)
setUser({user, ...snap.data()})
}
const getBookings = async () =>{
const q = query(bookingsRef, where("users","array-contains",auth.currentUser.uid));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const a = [];
querySnapshot.forEach((doc) => {
a.push(doc.data());
});
setBookings(querySnapshot.docs);
});
}
const toggleSwitch = () =>{
if(!watched){
setFilteredBookings(bookings.filter(function(item){
return item.data().watched == true;
}))
setText('Watched');
} else {
setText('To watch');
setFilteredBookings(bookings.filter(function(item){
return item.data().watched == false;
}))
}
setWatched(previousState => !previousState);
}
const updateBooking = async(id) => {
try {
await setDoc(doc(db, 'booking', id), {
watched: true
}, {merge: true})
}
catch(e) {
console.log(e)
}
}
const updateBooking1 = async(id) => {
try {
await setDoc(doc(db, 'booking', id), {
watched: false
}, {merge: true})
}
catch(e) {
console.log(e)
}
}
const deleteBooking = async(id) => {
try {
await deleteDoc(doc(db, 'booking', id));
}
catch(e) {
console.log(e)
}
}
useEffect(() => {
const resultOfFiltering = bookings
setFilteredBookings(resultOfFiltering)
}, [bookings])
return (
<View>
<View>
<Text>{text}</Text>
<Switch
value = {watched}
/>
</View>
<FlatList
data = {filteredBookings}
extraData = {watched}
renderItem = {({item}) => (
<View>
<View>
<Text>{item.data().movie}</Text>
<Text>{item.data().day} - {item.data().showtime}</Text>
</View>
<View>
{item.data().watched == true ? (
<TouchableOpacity style = {styles.delete} onPress = {() => {updateBookings(item.id); setFilteredBookings();}}>
<Image source={require('../assets/watched.png')}/>
</TouchableOpacity>
) : (
<TouchableOpacity onPress = {() => {updateBookings1(item.id); setFilteredBookings();}}>
<Image source={require('../assets/towatch.png')}/>
</TouchableOpacity>)}
<TouchableOpacity onPress = {() => {deleteBooking(item.id); setFilteredBookings();}}>
<Image source={require('../assets/remove.png')}/>
</TouchableOpacity>
</View>
</View>
)}
/>
</View>
)
}
export default WatchList
It's not really working properly at the moment and I'm not sure what I'm doing wrong so I'm kind of stuck. If I could get some help, I'd really appreciate it. Thank you!

How can i turn this React class component into a functional component?

I'm working on implementing a braintree payment method in my react/mui app. I've found a way that works, but it's in a class component. How can I convert this info a proper functional component?
const BraintreeDropInPaymentMethod = () => {
class Store extends React.Component {
instance;
state = {
clientToken: '<BRAIN TREE KEY>'
};
async componentDidMount() {
const response = await fetch("server.test/client_token");
const clientToken = await response.json();
this.setState({
clientToken,
});
}
async buy() {
const { nonce } = await this.instance.requestPaymentMethod();
await fetch(`server.test/purchase/${nonce}`);
}
render() {
if (!this.state.clientToken) {
return (
<div>
<h1>Loading...</h1>
</div>
);
} else {
return (
<div>
<DropIn
options={{ authorization: this.state.clientToken }}
onInstance={(instance) => (this.instance = instance)}
/>
<Button
variant='contained'
onClick={this.buy.bind(this)}
>
Create Account
</Button>
<Button
variant='outlined'
sx={{ marginLeft: 3 }}
color='warning'
onClick={(e) => handleCancelAccountCreation(e)}
href='/store-front'
>
Cancel
</Button>
</div>
);
}
}
}
const [user, setUser] = useState({})
const handleCancelAccountCreation = (event) => {
setUser({})
document.getElementById('signInBtn').hidden = false
}
return (
<Store/>
)
}
this is my attempt, but I'm coming up short on how I should handle componentDidMount(). I know how to handle useState in some situations, except for this one. Also, how can I handle the 'instance' section in a functional format? thanks.
const BraintreeDropInPaymentMethod = () => {
const [token, setToken] = useState('<BRAIN TREE KEY>')
const [user, setUser] = useState({})
const contactServer = async () => {
const res = await fetch('server.test/client_token')
const clientToken = await res.json()
console.log(clientToken)
setToken(token)
}
const buy = async () => {
const { nonce } = await this.instance.requestPaymentMethod()
await fetch(`server.test/purchase/${nonce}`)
}
const handleCancelAccountCreation = (event) => {
setUser({})
document.getElementById('signInBtn').hidden = false
}
const createAccountOptions = () => {
if (!token) {
return (
<div>
<h1>Loading...</h1>
</div>
) else {
return (
<div>
<DropIn
options={ authorization: {setToken})
onInstance={(instance) => (this.instance = instance)}
/>
<Button
variant="contained'
onClick={buy}
>
Create Account
</Button
variant='outlined'
sx={{ marginLeft: 3 }}
color='warning'
onClick={(e) => handleCancelAccountCreation(e)}
href='/store-front'
>
<Button>
Cancel
</Button>
</div>
)
}
}
}
return(
<>
<createAccountOptions/>
</>
)
}
The functional equivalent of componentDidMount() is the useEffect hook.
In this case you would change this:
async componentDidMount() {
const response = await fetch("server.test/client_token");
const clientToken = await response.json();
this.setState({
clientToken,
});
}
Into something like this:
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
const response = await fetch("server.test/client_token");
const clientToken = await response.json();
setState((old) => clientToken);
};
Using the useEffect hook with an empty array as a dependency makes the function in it only run once as the component mounts.

React-Native Flatlist RenderItem doesn't render

I don't get any errors. I use flatlist in the home page of my code without any errors but it doesn't render in the second page. It also doesn't display console.log inside renderItem.
const NamazTakibi = () => {
const [ayet, setAyet] = useState('');
const fetchAyet = async () => {
try {
const ayet = await axios.get('https://api.acikkuran.com/surah/6/verse/1?author=14');
setAyet(ayet)
} catch {
console.log("error");
}
}
useEffect(() => {
fetchAyet();
}, []);
const renderItem = ({item}) => {
return (
<Cards item={item} />
)
}
return (
<FlatList
style={styles.container}
data={ayet}
renderItem={renderItem}
/>
)
};
export default NamazTakibi;

AsyncStorage not working properly when saving multiple items

I have a main screen where you can see multiple items displayed in card form you can access an item by pressing it to see its details, on the details screen I added a bookmark option that save the item using async storage, you can also check the saved items on a different screen called savedItems screen.
The problem is :
when i bookmark an item it get saved properly and i can go to the savedItems screen and find it there, but some times i have to reload the app for the item to appear on the savedItems screen why is that ?
if i book multiple items they all get saved ( on console.log ) but only the last one appears for some reason i never get more then one item displayed on the SavedItems screen
Bellow is snippets of the code used to book mark ( saved an item on its details screen )
Details.js
const DetailScreen = (props) => {
const [saved, setSaved] = useState([]);
const [items, setItems] = useState(props.route.params);
const onSave = (item) => {
const newItems = [...saved, item];
setSaved(newItems);
const items = JSON.stringify(newItems);
SaveItem("saved", items).then((res) => {
console.log("saved", res);
});
};
const goToDetails = () => {
setSaved([]);
props.navigation.navigate("SaveScreen");
};
const { width, height } = Dimensions.get("window");
const { data } = props.route.params; // this returns the data from each article
//ReadItem("saved").then((res) => console.log(res));
return (
<TouchableOpacity
...
onPress={() => {
onSave(data);
}}
>
<MaterialCommunityIcons
name="bookmark"
size={35}
color={colors.shade2}
/>
</TouchableOpacity>
)
SaveScreen.js
export default class Details extends Component {
state = {
saved: [],
};
removeItem = () => {
DeleteItem("saved")
.then((res) => {
this.setState({
saved: [],
});
console.log(res);
})
.catch((e) => console.log(e));
};
componentDidMount = () => {
ReadItem("saved")
.then((res) => {
if (res) {
const saved = JSON.parse(res);
this.setState({
saved: saved,
});
}
})
.catch((e) => console.warn(e));
};
render() {
return (
<View style={styles.container}>
<FlatList
keyExtractor={(item, index) => index.toString()}
data={this.state.saved}
renderItem={({ item }) => {
return (
<TouchableScale
activeScale={0.9}
tension={50}
friction={7}
useNativeDriver
onPress={() =>
this.props.navigation.navigate("DetailScreen", { data: item })
}
>
<Card item={item} />
</TouchableScale>
);
}}
/>
{this.state.saved.length > 0 && (
<TouchableOpacity onPress={this.removeItem} style={styles.button}>
<Text style={styles.save}>Remove Key</Text>
</TouchableOpacity>
)}
</View>
);
}
}
the code used to save data using async storage
Dbhelper.js
import { AsyncStorage } from "react-native";
export const SaveItem = async (key, value) => {
try {
await AsyncStorage.setItem(key, value);
console.log("saved");
} catch (e) {
console.log(e);
}
};
export const ReadItem = async (key) => {
try {
var result = await AsyncStorage.getItem(key);
return result;
} catch (e) {
return e;
}
};
export function MultiRead(key, onResponse, onFailure) {
try {
AsyncStorage.multiGet(key).then((values) => {
let responseMap = new Map();
values.map((result, i, data) => {
let key = data[i][0];
let value = data[i][1];
responseMap.set(key, value);
});
onResponse(responseMap);
});
} catch (error) {
onFailure(error);
}
}
export async function DeleteItem(key) {
try {
await AsyncStorage.removeItem(key);
return true;
} catch (exception) {
return false;
}
}
if i book multiple items they all get saved ( on console.log ) but only the last one appears for some reason i never get more then one item displayed on the SavedItems screen
I think the problem is when you save new item you remove old items. You have to read old items when you mount DetailScreen. Or you can read items inside onSave method. It's more safety. It depends your application architecture
const DetailScreen = (props) => {
const [items, setItems] = useState(props.route.params);
const onSave = (item) => {
let saved = [];
ReadItem("saved")
.then((res) => {
if (res) {
saved = JSON.parse(res);
}
const newItems = [...saved, item];
const items = JSON.stringify(newItems);
SaveItem("saved", items).then((res) => {
console.log("saved", res);
});
})
.catch((e) => console.warn(e));
};
const goToDetails = () => {
setSaved([]);
props.navigation.navigate("SaveScreen");
};
const { width, height } = Dimensions.get("window");
const { data } = props.route.params; // this returns the data from each article
//ReadItem("saved").then((res) => console.log(res));
return (
<TouchableOpacity
...
onPress={() => {
onSave(data);
}}
>
<MaterialCommunityIcons
name="bookmark"
size={35}
color={colors.shade2}
/>
</TouchableOpacity>
)
when i bookmark an item it get saved properly and i can go to the savedItems screen and find it there, but some times i have to reload the app for the item to appear on the savedItems screen why is that ?
I have only one idea why it may happens. In some cases, you don't unmount SaveScreen. When you return to the SaveScreen componentDidMount is not called. Just try to add console.log to componentDidMount and when you reproduce this behavior if console.log is missed it means you SaveScreen wasn't unmounted
To handle all cases you can try to do that:
export default class Details extends Component {
state = {
saved: [],
};
removeItem = () => {
DeleteItem("saved")
.then((res) => {
this.setState({
saved: [],
});
console.log(res);
})
.catch((e) => console.log(e));
};
componentDidMount = () => {
this.readItems();
this.unsubscribe = navigation.addListener('focus', this.readItems);
};
componentWillUnmount() {
this.unsubscribe();
}
readItems = () => {
ReadItem("saved")
.then((res) => {
if (res) {
const saved = JSON.parse(res);
this.setState({
saved: saved,
});
}
})
.catch((e) => console.warn(e));
}
render() { ... }
}

Categories