How to get AsyncStorage data without waiting in react native? - javascript

I have trouble trying to retrieve data from AsyncStorage, I can't directly assign a state like that, since it always returns undifined, how can I avoid that?
export default class ListTodo extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {},
};
}
componentDidMount() {
//promise
GetDataAsyncStorage('#TODOS').then((data) => {
this.setState({
data: data,
});
});
}
render() {
const {data} = this.state;
console.log(data); // undifined
return (
<>
<Header />
<View>
<FlatList
data={data}
renderItem={({item}) => <TodoItemComponent data={item} />}
keyExtractor={(item) => item.id}
/>
</View>
</>
);
}
}
Here is my function to get data from asynStorage
export const GetDataAsyncStorage = async (key) => {
try {
let data = await AsyncStorage.getItem(key);
return {status: true, data: JSON.parse(data)};
} catch (error) {
return {status: false};
}
};

Add a state variable isLoading and toggle it after the data is got from AsyncStorage
snack: https://snack.expo.io/#ashwith00/async
code:
export default class ListTodo extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {},
isLoading: false,
};
}
componentDidMount() {
this.getData();
}
getData = () => {
this.setState({
isLoading: true,
});
//promise
GetDataAsyncStorage('#TODOS').then((data) => {
this.setState({
data: data,
isLoading: false,
});
});
};
render() {
const { data, isLoading } = this.state;
return (
<View style={styles.container}>
{isLoading ? (
<ActivityIndicator />
) : data.data ? (
<FlatList
data={data}
renderItem={({ item }) => <Text>{item}</Text>}
keyExtractor={(item, i) => i.toString()}
/>
) : (
<Text>No Data Available</Text>
)}
</View>
);
}
}

Because AsyncStorage itself is asynchronous read and write, waiting is almost necessary, of course, another way to achieve, for example, to create a memory object, bind the memory object and AsyncStorage, so that you can read AsyncStorage synchronously.
For example, using the following development library can assist you to easily achieve synchronous reading of AsyncStorage react-native-easy-app
import { XStorage } from 'react-native-easy-app';
import { AsyncStorage } from 'react-native';
// or import AsyncStorage from '#react-native-community/async-storage';
export const RNStorage = {
token: undefined,
isShow: undefined,
userInfo: undefined
};
const initCallback = () => {
// From now on, you can write or read the variables in RNStorage synchronously
// equal to [console.log(await AsyncStorage.getItem('isShow'))]
console.log(RNStorage.isShow);
// equal to [ await AsyncStorage.setItem('token',TOKEN1343DN23IDD3PJ2DBF3==') ]
RNStorage.token = 'TOKEN1343DN23IDD3PJ2DBF3==';
// equal to [ await AsyncStorage.setItem('userInfo',JSON.stringify({ name:'rufeng', age:30})) ]
RNStorage.userInfo = {name: 'rufeng', age: 30};
};
XStorage.initStorage(RNStorage, AsyncStorage, initCallback);

Related

I am having trouble rendering data from an array React Native

i can log the data but cant seem to display it, the screen is completely blank.
im not getting any errors but still nothing on the screen
import React from 'react';
import { View, StyleSheet, Text } from 'react-native';
import firestore from '#react-native-firebase/firestore';
import { FlatList } from 'react-native-gesture-handler';
export default class StockAdjustmentScreen extends React.Component {
constructor(props) {
super(props)
this.state = {
stockAdjustments: [],
}
}
componentDidMount() {
firestore().collection('stockAdjustments')
.get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
this.state.stockAdjustments.push({
id: doc.id, ...doc.data()
})
})
console.log(this.state.stockAdjustments)
})
.catch((error) => {
console.log
(error)
})
}
render() {
let Adjustments = this.state.stockAdjustments.map((val, key) => {
return (
<View key={key}>
<FlatList style={styles.texts}>{val.comments}</FlatList>
</View>
)
})
return (
<View>
{Adjustments}
</View>
)
}
}
here is the data i am trying to display
You have to use:
this.setState({...})
instead of:
this.state.stockAdjustments.push({...})
in order to trigger ui update.
const stockAdjustments = []
querySnapshot.forEach((doc) => {
stockAdjustments.push({
id: doc.id, ...doc.data()
})
})
this.setState({ stockAdjustments });

How to filter pokemon in react native

I am trying to filter out pokemon in my searchbar component however when I type into the search bar the name of the component, nothing happens to the list. I have been searching online for solutions but other examples are too complex to implement into my code. I am consoling.log the input from the search bar component and it logs the input text. But just dont know how to filter out the pokemon. If anyone can help me I will really appreciate it!
// Home.js(Where pokemon ifo is coming from in the componentDidiMount abd then I pass down a function to the searchbar component)
import React, { useState } from "react";
import { View, Text , Button, FlatList, ActivityIndicator, TouchableOpacity } from "react-native";
import { GlobalStyles } from "../styles/GlobalStyles";
import PokeDetails from "./PokeDetails";
import SearchBarComponent from "../components/SearchBar";
import PokeBanner from "../components/PokeBanner";
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
dataSource: [],
filteredPokemon:[]
}
}
componentDidMount() {
fetch(`https://pokeapi.co/api/v2/pokemon/?limit=27`)
.then((res)=> res.json())
.then((response)=> {
this.setState({
isLoading: false,
dataSource: response.results,
})
console.log("RESPONSE",response)
console.log("RESPONSE.RESSSULTS",response.results)
})
}
filterPokemon =(textToSearch)=> {
const allPokemon = [...this.state.dataSource];
this.setState({
dataSource: allPokemon.filter(pokemon=> pokemon.name.toLowerCase().includes(textToSearch.toLowerCase()))
});
console.log("TextToSearch",textToSearch)
}
render() {
const showIndicator = this.state.isLoading == true ? <ActivityIndicator size="large" color="#0000ff" /> : null;
return(
<View style={GlobalStyles.container}>
<SearchBarComponent filterPoke={this.filteredPokemon} style={GlobalStyles.searchBar}/>
<PokeBanner/>
<View style={GlobalStyles.activityIndicator}>{showIndicator}</View>
<View style={GlobalStyles.pokeFlatList}>
<FlatList
contentContainerStyle={{paddingBottom: 70}}
keyExtractor={(item, index) => item.name}
numColumns={3}
data={this.state.dataSource}
renderItem={({item})=>
<View style={{flex: 1,justifyContent:"center", alignItems:"center", flexDirection: "row", marginBottom: 50, padding: 10}}>
<TouchableOpacity onPress={()=> this.props.navigation.navigate('PokeDetails',
{item ,imageUrl: `https://projectpokemon.org/images/normal-sprite/${item.name}.gif`})}>
<PokeDetails imageUrl={`https://projectpokemon.org/images/normal-sprite/${item.name}.gif`} name={item.name}/>
</TouchableOpacity>
</View>
}/>
</View>
</View>
)
}
}
export default Home;
// SearchBarComponent(Where I take the function passed down as a prop and use it in the updateSearch method)
import React from "react";
import {View, StyleSheet } from "react-native";
import { SearchBar } from 'react-native-elements';
import { GlobalStyles } from "../styles/GlobalStyles";
class SearchBarComponent extends React.Component {
state = {
search: '',
};
updateSearch=()=> {
this.props.pokeFilter(this.state.search);
console.log(this.state.search)
}
render() {
const { search } = this.state;
console.log(search)
return (
<View style={GlobalStyles.searchBar}>
<SearchBar
placeholder="Search pokemon..."
onChangeText={text=>this.setState({search: text})}
value={search}
/>
</View>
);
}
}
export default SearchBarComponent;
[![enter image description here][1]][1]
You need to call your updateSearch function when the user wants to search for a pokemon.
There are multiple ways to do that such as you can keep a separate button to handle submit function or call updateSearch inside onChangeText of your search bar component as below,
<SearchBar
placeholder="Search pokemon..."
onChangeText={this.updateSearch}
value={search}
/>
now change your updateSearch to handle serach text
updateSearch = (text) => {
this.setState({ search: text });
this.props.pokeFilter(this.state.search);
}
Also change the props of SearchBarComponent component as (make sure to use correct name)
<SearchBarComponent pokeFilter={this.filterPokemon} style={GlobalStyles.searchBar}/>
But you have to keep a temp variable to store all your pokemons. Because you need to filter data from all pokemons when user midified the search field.
componentDidMount() {
fetch(`https://pokeapi.co/api/v2/pokemon/?limit=27`)
.then((res) => res.json())
.then((response) => {
this.setState({
isLoading: false,
// keep a temp to store all pokemons
pokemons: response.results,
dataSource: response.results,
});
});
}
Now you can use your filter function
filterPokemon = (textToSearch) => {
// load all pokemons from temp
const allPokemon = [...this.state.pokemons];
this.setState({
dataSource: allPokemon.filter(pokemon => pokemon.name.toLowerCase().includes(textToSearch.toLowerCase()))
});
}
Hope this helps you. Feel free for doubts.
You should set FilteredPokemon as all pokemon when you do the first petition and pass that state to the FlatList. That way you will only show the filtered pokemon:
Then when you modify the search you will just filter on the allPokemon state and set it to the filtered. Let me just show it:
componentDidMount() {
fetch(`https://pokeapi.co/api/v2/pokemon/?limit=27`)
.then((res)=> res.json())
.then((response)=> {
this.setState({
isLoading: false,
dataSource: response.results,
filteredPokemon: response.results,
})
console.log("RESPONSE",response)
console.log("RESPONSE.RESSSULTS",response.results)
})
}
filterPokemon =(textToSearch)=> {
const allPokemon = [...this.state.dataSource];
const filteredPokemon = allPokemon.filter((pokemon) =>
pokemon.name.toLowerCase().includes(textToSearch.toLowerCase()))
this.setState({
filteredPokemon
});
console.log("TextToSearch",textToSearch)
}
Any problem just let me know and I will be happy to help!

React-Native How to have a different state for each item

I have a component where when I click on an icon, I execute a function that modify a state and then i can check the state and modify the icon. In that comonent, I am mapping datas and it renders several items.
But when I click on one icon all the icons of the components change too.
Here is the code for the component
export default class DiscoveryComponent extends Component {
constructor(props) {
super(props)
this.state = {
starSelected: false
};
}
static propTypes = {
discoveries: PropTypes.array.isRequired
};
onPressStar() {
this.setState({ starSelected: !this.state.starSelected })
}
render() {
return (
this.props.discoveries.map((discovery, index) => {
return (
<Card key={index} style={{flex: 0}}>
<CardItem>
<TouchableOpacity style={[styles.star]}>
<Icon style={[styles.iconStar]} name={(this.state.starSelected == true)?'star':'star-outline'} onPress={this.onPressStar.bind(this)}/>
</TouchableOpacity>
</CardItem>
</Card>
)
})
);
}
}
And here is the code for my screen that uses the component
export default class DiscoveryItem extends Component {
constructor(props) {
super(props);
this.state = {
discoveries: [],
loading: true
};
}
componentDidMount() {
firebase.database().ref("discoveries/").on('value', (snapshot) => {
let data = snapshot.val();
let discoveries = Object.values(data);
this.setState({discoveries: discoveries, loading: false});
});
}
render() {
return (
<Container>
<Content>
<DiscoveryComponent discoveries={this.state.discoveries} />
</Content>
</Container>
)
}
}
Your initiation is correct but you are missing INDEX of each item. Inside this.onPressStar() method check if item's index = currentItem. Also don't forget to set item id = index onpress.
I hope this has given you idea how to handle it.
You have to turn your stars into an Array and index them:
change your constructor:
constructor(props) {
super(props)
this.state = {
starSelected: []
};
}
change your onPressStar function to :
onPressStar(index) {
this.setState({ starSelected[index]: !this.state.starSelected })
}
and your icon to
<Icon style={[styles.iconStar]} name={(this.state.starSelected[index] == true)?'star':'star-outline'} onPress={()=>this.onPressStar(index)}/>
Well, the problem is that you have a single 'starSelected' value that all of your rendered items in your map function are listening to. So when it becomes true for one, it becomes true for all.
You should probably maintain selected state in the top level component, and pass down the discovery, whether its selected, and how to toggle being selected as props to a render function for each discovery.
export default class DiscoveryItem extends Component {
constructor(props) {
super(props);
this.state = {
discoveries: [],
selectedDiscoveries: [] // NEW
loading: true
};
}
toggleDiscovery = (discoveryId) => {
this.setState(prevState => {
const {selectedDiscoveries} = prevstate
const discoveryIndex = selectedDiscoveries.findIndex(id => id === discoveryId)
if (discoveryIndex === -1) { //not found
selectedDiscoveries.push(discoveryId) // add id to selected list
} else {
selectedDiscoveries.splice(discoveryIndex, 1) // remove from selected list
}
return {selectedDiscoveries}
}
}
componentDidMount() {
firebase.database().ref("discoveries/").on('value', (snapshot) => {
let data = snapshot.val();
let discoveries = Object.values(data);
this.setState({discoveries: discoveries, loading: false});
});
}
render() {
return (
<Container>
<Content>
{
this.state.discoveries.map(d => {
return <DiscoveryComponent key={d.id} discovery={d} selected={selectedDiscoveries.includes(d.id)} toggleSelected={this.toggleDiscovery} />
//<DiscoveryComponent discoveries={this.state.discoveries} />
</Content>
</Container>
)
}
}
You can then use your DiscoveryComponent to render for each one, and you're now maintaining state at the top level, and passing down the discovery, if it is selected, and the toggle function as props.
Also, I think you may be able to get snapshot.docs() from firebase (I'm not sure as I use firestore) which then makes sure that the document Id is included in the value. If snapshot.val() doesn't include the id, then you should figure out how to include that to make sure that you use the id as both key in the map function as well as for the selectedDiscoveries array.
Hope that helps
It works now, thanks.
I've made a mix between Malik and Rodrigo's answer.
Here is the code of my component now
export default class DiscoveryComponent extends Component {
constructor(props) {
super(props)
this.state = {
tabStarSelected: []
};
}
static propTypes = {
discoveries: PropTypes.array.isRequired
};
onPressStar(index) {
let tab = this.state.tabStarSelected;
if (tabStar.includes(index)) {
tabStar.splice( tabStar.indexOf(index), 1 );
}
else {
tabStar.push(index);
}
this.setState({ tabStarSelected: tab })
}
render() {
return (
this.props.discoveries.map((discovery, index) => {
return (
<Card key={index} style={{flex: 0}}>
<CardItem>
<Left>
<Body>
<Text note>{discovery.category}</Text>
<Text style={[styles.title]}>{discovery.title}</Text>
</Body>
</Left>
<TouchableOpacity style={[styles.star]}>
<Icon style={[styles.iconStar]} name={(this.state.tabStarSelected[index] == index)?'star':'star-outline'} onPress={()=>this.onPressStar(index)}/>
</TouchableOpacity>
</CardItem>
</Card>
)
})
);
}
}

State is being initialized with [object Object] in setState

In the async function below, I call stationData just to confirm that I'm passing an array of objects into bartData (which is just an empty array). Attached is a response of the array of Objects that I am receiving. However, when trying to use this.state.bartData (to confirm that it does have the array of objects), my return function is returning bartData as undefined. Any ideas?
import React from 'react';
const bartKey = process.env.REACT_API_BART_API_KEY;
class StationBaseRoutes extends React.Component{
constructor(props){
super(props);
this.state = {
isLoading: true,
station: [],
stationAbbv: 'ALL',
destination: '',
bartData: []
}
}
componentDidMount(){
this.getAllStationRoutes();
}
async getAllStationRoutes(){
try{
setInterval(async () => {
const response = await fetch(`http://api.bart.gov/api/etd.aspx?cmd=etd&orig=${this.state.stationAbbv}&key=${bartKey}&json=y`);
const jsonResponse = await response.json();
const apiData = jsonResponse.root;
const stationData = apiData.station;
console.log(stationData);
this.setState(({
isLoading: false,
bartData: stationData
}), () => {
console.log(`Callback: ${this.state.bartData}`)
})
}, 20000)
} catch(error){
console.log(error);
}
}
getRoutes = () => {
console.log(`bartData: ${this.bartData}`)
}
render(){
const {station, destination} = this.state;
return(
<div>
<h2>Calling get routes: {this.getRoutes()}</h2>
<h2>Origin: {station}</h2>
<h3>Destination: {destination}</h3>
</div>
)
}
}
export default StationBaseRoutes;
Responses: https://imgur.com/gallery/Luk9MCX
There's a couple of bugs here.
First of all, getRoutes() is using this.bartData instead of this.state.bartData
Secondly, all your objects in console.log are being converted to strings. You can change it to
console.log('bartData:', this.state.bartData);
to be able to see the actual data.
I was unable to get the Bart API to work in a codesandbox, so I had to mock the API... however, the data is still structured the same.
On that note, the API is working as expected, you just need to map over the objects in the this.state.bartData array and deconstruct the properties you want to show.
Working example: https://codesandbox.io/s/031pn7w680
import map from "lodash/map";
import React, { Component, Fragment } from "react";
import { fakeAPI } from "../../api/fakeAPI";
class StationBaseRoutes extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
station: [],
stationAbbv: "ALL",
destination: "",
bartData: []
};
this.getAllStationRoutes = this.getAllStationRoutes.bind(this);
}
componentDidMount() {
this.getAllStationRoutes();
}
async getAllStationRoutes() {
try {
const res = await fakeAPI.get();
const apiData = res.data.root;
const stationData = apiData.station;
this.setState({
isLoading: false,
bartData: stationData
});
} catch (error) {
console.log(error);
}
}
render() {
const { bartData, isLoading } = this.state;
return (
<div className="app-container">
{isLoading ? (
<p className="t-a-c">Loading...</p>
) : (
<Fragment>
<h1 className="t-a-c">Bart Stations</h1>
{map(bartData, ({ name, etd }) => (
<div className="jumbotron station" key={name}>
<h1>Origin: {name}</h1>
{map(etd, ({ destination }) => (
<li key={destination}>Destination: {destination}</li>
))}
</div>
))}
<pre className="preview">
<code>{JSON.stringify(bartData, null, 4)}</code>
</pre>
</Fragment>
)}
</div>
);
}
}
export default StationBaseRoutes;

React Native display FlatList with Firebase data

Trying to get FlatList to display data from Firebase.
Setup is correct and I can see the date in my console, but don't know how to visualise it.
I'd like to see 'recipeOne' 'recipeTwo' 'recipeThree' in the list.
I am sure I am missing something basic.
Here is the code
...
import {DataConfig} from '../data/DataConfig';
const firebaseApp = firebase.initializeApp(DataConfig);
globalTexts = require('../styles/Texts.js');
globalColors = require('../styles/Colors.js');
export default class ListSort extends Component {
constructor(props) {
super(props);
this.dataItem = firebaseApp.database().ref('recipes');
this.state = {
item: []
}
};
componentWillMount() {
this._createItemList();
};
_createItemList = (dataItem) => {
this.dataItem.on('value', (snapshot) => {
var itemList = [];
snapshot.forEach((doc) => {
itemList.push({
key:doc.key,
itemType: doc.toJSON()
});
this.setState({item: itemList});
console.log(this.state.item);
})
})
};
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.item}
renderItem={({item, index}) => (
<View style={styles.cell}>
<Text style={globalText.btnFlatPrimary}>{item.recipes}</Text>
</View>
)}
/>
</View>
)
}
}
and here is the data. The rules in Firebase are setup as read:true only.
{
"recipes": {
"recipeOne": {...
"recipeTwo": {...
"recipeThree": {...
}
}

Categories