How to save an array of Items in AsyncStorage React Native? - javascript

What i want to make is a bookmark list, but i don't know how i can save an array of items in AsyncStorage, my skills are basic on react.
I just need the function to save posts (post image, title and id) when i press the button "Save to bookmark".
export default class PostDetails extends Component {
constructor(props) {
super(props);
const {params} = props.navigation.state;
this.state = {
item: params.item
};
}
render() {
const {item} = this.state;
return (
<Image source={{uri: item.post_image}}/>
<Text> {item.post_id} </Text>
<Text> {item.post_title} </Text>
<Button>
<Text> Save to Bookmark </Text>
</Button>
);
}
}

I think you want to use JSON.stringify(arrayToSave); see the docs for JSON.stringify(). It will convert the array to a JSON string that can be saved to AsyncStorage and then can be retrieved at a later stage.
const saveBookmarks = async (bookmarksArray) => {
try {
const bookmarksString = JSON.stringify(bookmarksArray);
await AsyncStorage.setItem('#MyStore:bookmarks',bookmarksString);
} catch (error) {
// Error saving data
}
};
<Button onClick={() => saveBookmarks(yourArray)} />
To retrieve it you can use const theSavedArray = JSON.parse(stringFromAsyncStorage); the docs for JSON.parse()
try {
const bookmarksString = await AsyncStorage.getItem('#MyStore:bookmarks');
if (bookmarksString !== null){
// We have data!!
const bookmarksArray = JSON.parse(bookmarksString);
}
} catch (error) {
// Error retrieving data
}

Related

How to update a react component after a fetch

I am learning react.
I have a simple react app sample that :
Fetch users
Once users are fetched, show their name on a Card
What I'd like to do is to expand this sample. Instead of using a simple list of users, I'd like to use a list of pokemons. What I try to do is :
Fetch the list of pokemon and add in state.pokemons
Show the Card with the pokemon name from state.pokemons
From that list, get the URL to fetch the detail of the given pokemon and add in state.pokemonsDetails
From the state.pokemonsDetails, update the Cards list to show the image of the pokemon.
My problem is: I don't even know how to re-render the Cards list after a second fetch.
My question is: How to update the Cards list after the second fetch?
See my code below:
import React from "react";
import CardList from "../components/CardList";
import SearchBox from "../components/SearchBox"
import Scroll from "../components/Scroll"
import './App.css';
class App extends React.Component{
constructor(){
super();
this.state = {
pokemons:[],
pokemonsDetails:[],
searchfield: ''
}
}
getPokemons = async function(){
const response = await fetch('https://pokeapi.co/api/v2/pokemon/?offset=0&limit=20');
const data = await response.json();
this.setState({pokemons:data.results})
}
getPokemonDetails = async function(url){
//fetch function returns a Promise
const response = await fetch(url);
const data = await response.json();
//console.log('getPokemonDetails', data);
this.setState({pokemonsDetails:data});
}
componentDidMount(){
this.getPokemons();
}
onSearchChange = (event) => {
this.setState({searchfield: event.target.value})
}
render(){
const {pokemons, pokemonsDetails, searchfield} = this.state;
if(pokemons.length === 0){
console.log('Loading...');
return <h1>Loading....</h1>
}else if (pokemonsDetails.length === 0){
console.log('Loading details...');
pokemons.map(pokemon => {
return this.getPokemonDetails(pokemon.url);
});
return <h1>Loading details....</h1>
}else{
return(
<div>
<h1>Pokedex</h1>
<SearchBox searchChange={this.onSearchChange}/>
<Scroll>
<CardList pokemons={pokemons}/>
</Scroll>
</div>
);
}
}
}
export default App;
Some remarks :
I can see a problem where my Cards list is first created with state.pokemons, then, I would need to update Cards list with state.pokemonsDetails. The array is not the same.
Second problem, I don't even know how to call the render function after state.pokemonsDetails is filled with the fetch. I set the state, but it looks like render is not called every time
More a question than a remark. The way I update my state in getPokemonDetails might be incorrect. I keep only one detail for one given pokemon. How to keep a list of details? Should I use something else than setState to expand pokemonsDetails array?
You can combine 2 API calls before pokemons state update that would help you to control UI re-renderings better
You can try the below approach with some comments
Side note that I removed pokemonDetails state, so you won't see the loading elements for pokemonDetails as well
import React from "react";
import CardList from "../components/CardList";
import SearchBox from "../components/SearchBox";
import Scroll from "../components/Scroll";
import "./App.css";
class App extends React.Component {
constructor() {
super();
this.state = {
pokemons: [],
searchfield: ""
};
}
getPokemons = async function () {
const response = await fetch(
"https://pokeapi.co/api/v2/pokemon/?offset=0&limit=20"
);
const data = await response.json();
//try to get all pokemon details at once with fetched URLs
const pokemonDetails = await Promise.all(
data.results.map((result) => this.getPokemonDetails(result.url))
);
//map the first and second API response data by names
const mappedPokemons = pokemonDetails.map((pokemon) => {
const pokemonDetail = pokemonDetails.find(
(details) => details.name === pokemon.name
);
return { ...pokemon, ...pokemonDetail };
});
//use mapped pokemons for UI display
this.setState({ pokemons: mappedPokemons });
};
getPokemonDetails = async function (url) {
return fetch(url).then((response) => response.json());
};
componentDidMount() {
this.getPokemons();
}
onSearchChange = (event) => {
this.setState({ searchfield: event.target.value });
};
render() {
const { pokemons, searchfield } = this.state;
if (pokemons.length === 0) {
return <h1>Loading....</h1>;
} else {
return (
<div>
<h1>Pokedex</h1>
<SearchBox searchChange={this.onSearchChange} />
<Scroll>
<CardList pokemons={pokemons} />
</Scroll>
</div>
);
}
}
}
export default App;
Sandbox
If you want to update pokemon details gradually, you can try the below approach
import React from "react";
import CardList from "../components/CardList";
import SearchBox from "../components/SearchBox";
import Scroll from "../components/Scroll";
import "./App.css";
class App extends React.Component {
constructor() {
super();
this.state = {
pokemons: [],
searchfield: ""
};
}
getPokemons = async function () {
const response = await fetch(
"https://pokeapi.co/api/v2/pokemon/?offset=0&limit=20"
);
const data = await response.json();
this.setState({ pokemons: data.results });
for (const { url } of data.results) {
this.getPokemonDetails(url).then((pokemonDetails) => {
this.setState((prevState) => ({
pokemons: prevState.pokemons.map((pokemon) =>
pokemon.name === pokemonDetails.name
? { ...pokemon, ...pokemonDetails }
: pokemon
)
}));
});
}
};
getPokemonDetails = async function (url) {
return fetch(url).then((response) => response.json());
};
componentDidMount() {
this.getPokemons();
}
onSearchChange = (event) => {
this.setState({ searchfield: event.target.value });
};
render() {
const { pokemons, searchfield } = this.state;
if (pokemons.length === 0) {
return <h1>Loading....</h1>;
} else {
return (
<div>
<h1>Pokedex</h1>
<SearchBox searchChange={this.onSearchChange} />
<Scroll>
<CardList pokemons={pokemons} />
</Scroll>
</div>
);
}
}
}
export default App;
Sandbox
Side note that this approach may cause the performance issue because it will keep hitting API for fetching pokemon details multiple times and updating on the same state for UI re-rendering

Render Item in Agenda using database data - React Native

I'm trying to show the items I get from my database in the calendar, everything is working fine (maybe not), but in short I got the data from the database with an array and then I converted it to an object (because the calendar only accepts objects), but it doesn't show anything and it doesn't give an error either
import React, { useEffect, useState } from 'react'
import { StyleSheet, View, Text } from 'react-native'
import { LocaleConfig, Agenda } from 'react-native-calendars'
import DateTimePicker from 'react-native-modal-datetime-picker';
import { getAuth } from 'firebase/auth';
import { getDatabase, ref, onValue, set, push, get, child } from 'firebase/database';
const Calendario = () => {
const dbRef = ref(getDatabase());
const data = []
var obj = {}
// getting data from the database
useEffect(() => {
getInDB()
} ,[])
const getInDB = () => {
get(child(dbRef, 'users/' + app.currentUser.uid)).then((snapshot) => {
snapshot.forEach(childsnap => {
let dateD = childsnap.child("date").val()
let titleD = childsnap.child("title").val()
let dtsD = childsnap.child("details").val()
// "yyyy-MM-dd": [{any: "whatever", any2: "whatever"}],
data.push({
[dateD] : [{ title: titleD, details: dtsD }],
});
})
obj = Object.assign({}, ...data)
console.log(obj)
})
}
const renderItem = (item) => {
return(
<View style={styles.itemContainer}>
<Text style={styles.textInf}>{item.title}</Text>
<Text style={styles.textInf}>{item.details}</Text>
</View>
)
}
return (
<>
<Agenda
items={obj}
renderEmptyDate={() => {
return <View />;
}}
renderEmptyData={() => {
return <View />;
}}
selected={new Date()}
minDate={null}
renderItem={renderItem}
markingType="custom"
/>
</>
}
You need to use state and set it or otherwise your component will not be rerendered with the new data.
Furthermore, the Agenda component expects an object. By using data as an array and the spread operator, we won't get the desired result.
You can implement this correctly as follows.
...
const [obj, setObj] = useState({});
...
const getInDB = () => {
get(child(dbRef, 'users/' + app.currentUser.uid)).then((snapshot) => {
const temp = {}
snapshot.forEach(childsnap => {
let dateD = childsnap.child("date").val()
let titleD = childsnap.child("title").val()
let dtsD = childsnap.child("details").val()
Object.assign(temp, {dateD: [{ title: titleD, details: dtsD }]})
})
setObj(temp)
})
}
I have implemented a little snack.

React Native Async Storage setItem call fails

I'm writing a simple Async Storage set Item call in React Native to store my store's cart data. But the call fails. I can't find possible reason why it so as there is no mention of such in the documentation. I have added the relevant code. Please help would be appreciated.
const ProductDetailScreen = (props) => {
const {product} = props.route.params;
const getCart = async () => {
return await AsyncStorage.getItem('cart');
};
const cart = getCart();
const addToCart = async () => {
try {
await AsyncStorage.setItem('cart', [
...cart, JSON.stringify(product),
]);
} catch (e) {
console.error(`Failed to add item: {${JSON.stringify(product)}} to cart`);
}
};
return (
<>
<TouchableOpacity onPress={addToCart}>
<Button>
<Text style={styles.btnText}>Add to Cart</Text>
</Button>
</TouchableOpacity>
</>
)
};
export default ProductDetailScreen;
error log
ERROR Failed to add item: {{"id":2,"name":"Scene Stealers","description":"Corset cuts","price":250,"image":5,"colors":["#0F140D","#DD8560","#E1E0DB"],"sizes":["S","M","L"]}} to cart
You can only store/set string data in AsyncStorage while you are trying to store Array
await AsyncStorage.setItem('cart', JSON.stringify([...cart, JSON.stringify(product)]))

How can I re render a component? REACT-NATIVE

im new in react native, and im doing a note block, the problem now its that once i click save, it saves it to the array but when i get back to home screen, where i show the notes that are saved it doesnt show the last one, until i re load the entire project, how can I do to re render it? i have seen that i have to use this.forceUpdate(), but it doesnt working either, heres the code:
this is the home screen, the first screen the user will see, it shows the notes that are saved calling the component Notes
render() {
return (
<>
<View style = {this.styles.container}>
<View>
<Text style = {this.styles.Text}>Welcome to home!</Text>
</View>
<Notes></Notes>
<View style = {this.styles.View}>
<Button title = "Create new note" styles = {this.styles.Button} onPress = {() => this.props.navigation.navigate("Create_note")}></Button>
</View>
<View style = {this.styles.View}>
<Button title = "Notes" styles = {this.styles.Button} onPress = {() =>this.props.navigation.navigate("See_notes")}></Button>
</View>
</View>
</>
);
}
heres the component Notes:
class Notes extends Component {
constructor(props) {
super(props);
this.state = {
array_notes: [],
}
}
componentDidMount() {
this.fetch_notes();
}
fetch_notes = async() => {
try {
const data = await AsyncStorage.getItem("array_notes");
if (data != null) {
const array_notes = JSON.parse(data);
this.setState({array_notes: array_notes});
}else {
console.log("with no data");
}
}catch (error) {
console.log(error);
}
}
render() {
return (
<>
<View style = {this.styles.View}>
<FlatList data = {this.state.array_notes} renderItem = {({item}) => (<Text style = {this.styles.Text}>{item.title}</Text>)} keyExtractor = {(item) => item.title}></FlatList>
</View>
</>
);
}
and heres the create a new note screen, where the user type a new note:
class Create_note extends Component {
constructor() {
super();
this.state = {
title: "",
content: "",
}
}
save_Data = async() => {
try {
const array_notes = await AsyncStorage.getItem("array_notes");
if (array_notes === null) {
const array_notes = [];
await AsyncStorage.setItem("array_notes", JSON.stringify(array_notes));
}else {
const new_note = {'title': this.state.title, 'content': this.state.content};
const array_notes = JSON.parse(await AsyncStorage.getItem("array_notes"));
array_notes.push(new_note);
await AsyncStorage.setItem("array_notes", JSON.stringify(array_notes));
}
}catch(error) {
console.log(error);
}
}
}
render() {
return (
<>
<Text style = {this.styles.Text }>Welcome to Shum Note!</Text>
<View>
<TextInput style = {this.styles.TextInput_title} placeholder = "Title" multiline = {true} maxLength = {80} onChangeText = {(title) => this.setState({title: title})}></TextInput>
<TextInput style = {this.styles.TextInput_content} placeholder = "Content" multiline = {true} onChangeText = {(content) => this.setState({content: content})}></TextInput>
<Button title = "Save" onPress = {this.save_Data}></Button>
</View>
<View style = {this.styles.back_Button}>
<Button title = "Go back home" onPress = {() => this.props.navigation.navigate("Home")}></Button>
</View>
</>
);
}
once i saved the new note and press the go back home it doesnt show the last one until like i said, i reload the entire project, but something curious, is if i go to create_note screen it will re render each time, but it doesnt happend with home, why?
You have to pass in fetch_notes as a prop in Create_note.
this.props.navigation.navigate("Create_note", fetch_notes: this.fetch_notes)
In your Create_note get the function from navigation.
const { fetch_notes} = this.props.route.params; //https://reactnavigation.org/docs/params/
After saving the note you have to call it like this: this.props.fetch_notes()
You can add the.props.navigation.addListener. When you are back from next screen to previous screen API calling because of addListener focus on current screen and UI is render because of state changes.
componentDidMount() {
this.focusListener =
this.props.navigation.addListener("didFocus", () => {
this.fetch_notes()
});
}

React Native - Invariant Violation: Objects are not valid as a React child

In my application, I'm trying to fetch a data from my api. I've already tried to fetch data in my other modules and they're all working fine, but here it's not.
In here I'am trying to fetch a single object/data in my api.
Here's my code
Category.js
export default class Category extends Component {
constructor(props){
super(props)
this.state = {
data: [],
orderDet: '',
};
}
fetchDataOrderNo = async () => {
const response = await fetch("http://192.168.254.105:3308/OrderNo/order_no")
const json = await response.json()
this.setState({ orderDet: json })
}
componentDidMount() {
this.fetchDataOrderNo();
}
render() {
return (
<View>
<View style={{flexDirection: 'row'}}>
<Text>Table No: { this.state.orderDet }</Text>
</View>
</View>
)
}
}
You are getting an array as response to your request. You have to access the first object in the array, and get the order_no key:
fetchDataOrderNo = async () => {
const response = await fetch("http://192.168.254.105:3308/OrderNo/order_no")
const json = await response.json()
this.setState({ orderDet: json[0].order_no })
}

Categories