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;
Related
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);
Why is my aync call fetchButtonTeams() below not being called. I am trying to print its results in console.log(this.state.data) below. Even if i call it in the render() I get infinite loops or errors. Can anyone suggest what to do?
I just want to print the results in console.log in render()
class TeamFilter extends Component {
constructor() {
super();
this.state = { data: [] };
}
async fetchButtonTeams() {
const response = await fetch(`/api/teams`);
const json = await response.json();
console.log(json)
this.setState({ data: json });
}
handleTeamSelection = e => {
this.props.setTeam(e.target.title);
this.props.fetchTeams(e.target.title)
};
render() {
let test = ['Chaos', 'High Elves', 'Orcs']
this.fetchButtonTeams()
console.log(this.state.data)
return (
<DropdownButton id="dropdown-team-button" title={this.props.team_name}>
{test.map(cls => (
<div key={cls}>
<Dropdown.Item onClick={this.handleTeamSelection} title={cls}>{cls}</Dropdown.Item>
</div>
))}
</DropdownButton>
)
}
}
const mapStateToProps = state => {
return {
team_name: state.team_name
}
};
const mapDispatchToProps = dispatch => {
return {
fetchCards: path => dispatch(fetchCards(path)),
fetchTeams: params => dispatch(fetchTeams(params)),
setTeam: team_name => dispatch({ type: "SET_TEAM", team_name })
}
};
export default connect(mapStateToProps, mapDispatchToProps)(TeamFilter)
The reason you get infinite loops when you call the function on the render method is because each time the function is calling setState which in turn runs the function again and again, triggering an infinite loop.
I don't see where you are calling fetchButtonTeams() anywhere in your component, but a good idea for fetching data is putting the method inside a componentDidMount lifecycle method and console log inside the render method.You can learn more about lifecycle hooks here.
For your code:
class TeamFilter extends Component {
constructor() {
super();
this.state = { data: [] };
}
componentDidMount() {
this.fetchButtonTeams();
}
async fetchButtonTeams() {
const response = await fetch(`/api/teams`);
const json = await response.json();
console.log(json);
this.setState({ data: json });
}
handleTeamSelection = e => {
this.props.setTeam(e.target.title);
this.props.fetchTeams(e.target.title);
};
render() {
let test = ["Chaos", "High Elves", "Orcs"];
console.log(this.state.data);
return (
<DropdownButton id="dropdown-team-button" title={this.props.team_name}>
{test.map(cls => (
<div key={cls}>
<Dropdown.Item onClick={this.handleTeamSelection} title={cls}>
{cls}
</Dropdown.Item>
</div>
))}
</DropdownButton>
);
}
}
const mapStateToProps = state => {
return {
team_name: state.team_name
};
};
const mapDispatchToProps = dispatch => {
return {
fetchCards: path => dispatch(fetchCards(path)),
fetchTeams: params => dispatch(fetchTeams(params)),
setTeam: team_name => dispatch({ type: "SET_TEAM", team_name })
};
};
export default connect(mapStateToProps, mapDispatchToProps)(TeamFilter);
I'm trying to make a basic app by fetching data from an API.
I'm now trying to filter a value from that data through an array.
I'm using react-search-input and getting "TypeError: Cannot read property 'filter' of undefined" and I don't understand why.
What do I need to do to avoid this error ?
Code here :
import React, { Component } from 'react';
import SearchInput, {createFilter} from 'react-search-input'
const API = 'https://swapi.co/api/people/';
class App extends Component {
constructor(props) {
super(props);
this.state = {
items: [],
searchTerm: '',
}
this.searchUpdated = this.searchUpdated.bind(this)
}
componentWillMount() {
this.fetchData();
}
fetchData = async () => {
const response = await fetch(API);
const json = await response.json();
this.setState({ items: json.results })
}
render() {
const items = this.state.items;
const filteredData = items.results.filter(createFilter(this.state.searchTerm, items.results))
return (
<div>
<SearchInput className="search-input" onChange={this.searchUpdated} />
<div className="row">
{filteredData.map((item, i) =>
<div className="col-md-4" key={i}>
<a href={item.url}>{item.name}</a>
<p>Caractéristiques :</p>
<ul>
<li>Année de naissance : {item.birth_year}</li>
<li>Taille : {item.height} cm</li>
<li>Masse : {item.mass}</li>
<li>Couleur des yeux : {item.eye_color}</li>
<li>Couleur de cheveux : {item.hair_color}</li>
<li>Couleur de peau : {item.skin_color}</li>
</ul>
</div>
)}
</div>
</div>
)
}
searchUpdated (term) {
this.setState({searchTerm: term})
}
}
export default App;
You are trying to access the results property on this.state.items which is declared as an empty array. You should declare items like this:
this.state = {
items: { results: [] },
searchTerm: '',
}
...
const items = this.state.items;
items.results.filter(createFilter(this.state.searchTerm, items.results))
or simply declare results as an array and use it:
this.state = {
results: [],
searchTerm: '',
}
...
this.state.results.filter(createFilter(this.state.searchTerm, this.state.results))
You have a syntax error. You're trying to access a result key on an array.:
// items is an array, arrays don't have keyword result
const filteredData = items.results.filter(createFilter(this.state.searchTerm, items.results))
I'm having issues with setting this.setState from within my API call. If I console.log the stocks array inside the axios call the data is available in the array. It is not available outside if it.
Is the problem because this.setState is merging objects? I'm having a hard time conceptualizing what is happening here. How do I fix this problem so I can pass the contents to props?
import React, { Component } from 'react';
import axios from 'axios';
import SearchBar from './components/search_bar';
import StockList from './components/StockList';
import './App.css';
class App extends Component {
constructor() {
super();
this.state = {
stocks: [],
term: null,
value: ''
};
this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({
value: e.target.value
});
}
handleClick(e) {
if(e) e.preventDefault();
this.setState({
value: '',
term: this.state.value
});
let term = this.state.value;
const key = 'F41ON15LGCFM4PR7';
const url = `https://www.alphavantage.co/query?function=BATCH_STOCK_QUOTES&symbols=${term}&apikey=${key}`;
axios.get(axios.get(url)
.then(res => {
let stocks = Array.from(res.data['Stock Quotes']).map((stock) => [{symbol: stock['1. symbol'], price: stock['2. price'], volume: stock['3. volume'], timestamp: stock['4. timestamp']}]);
this.setState((state, props) => {
return [...this.state.stocks]
})
})
.catch(error => console.log(error))
)
}
render () {
let stocks = this.state.stocks;
const value = this.state.value;
return (
<div className="App">
<h1>Stock Search</h1>
<SearchBar value={ value }
onChange={ this.handleChange }
onClick={ this.handleClick }/>
<StockList stockItems={ stocks }/>
</div>
);
}
}
export default App;
Your setState there is the issue, it's messing up the structure of your state.
this.setState((state, props) => {
return [...this.state.stocks]
});
Should be either:
this.setState({
// set stocks to that array you parsed from the axios response
stocks
});
or
this.setState((state, props) => {
return {
...state,
// set stocks to that array you parsed from the axios response
stocks
};
});
I suggest that because you're accessing the stocks via this.state.stocks in your render
So I have a large set of data that I'm retrieving from an API. I believe the problem is that my component is calling the renderMarkers function before the data is received from the promise.
So I am wondering how I can wait for the promise to resolve the data completely before calling my renderMarkers function?
class Map extends Component {
componentDidMount() {
console.log(this.props)
new google.maps.Map(this.refs.map, {
zoom: 12,
center: {
lat: this.props.route.lat,
lng: this.props.route.lng
}
})
}
componentWillMount() {
this.props.fetchWells()
}
renderMarkers() {
return this.props.wells.map((wells) => {
console.log(wells)
})
}
render() {
return (
<div id="map" ref="map">
{this.renderMarkers()}
</div>
)
}
}
function mapStateToProps(state) {
return { wells: state.wells.all };
}
export default connect(mapStateToProps, { fetchWells })(Map);
You could do something like this to show a Loader until all the info is fetched:
class Map extends Component {
constructor () {
super()
this.state = { wells: [] }
}
componentDidMount() {
this.props.fetchWells()
.then(res => this.setState({ wells: res.wells }) )
}
render () {
const { wells } = this.state
return wells.length ? this.renderWells() : (
<span>Loading wells...</span>
)
}
}
for functional components with hooks:
function App() {
const [nodes, setNodes] = useState({});
const [isLoading, setLoading] = useState(true);
useEffect(() => {
getAllNodes();
}, []);
const getAllNodes = () => {
axios.get("http://localhost:5001/").then((response) => {
setNodes(response.data);
setLoading(false);
});
};
if (isLoading) {
return <div className="App">Loading...</div>;
}
return (
<>
<Container allNodes={nodes} />
</>
);
}
Calling the render function before the API call is finished is fine. The wells is an empty array (initial state), you simply render nothing. And after receiving the data from API, your component will automatically re-render because the update of props (redux store). So I don't see the problem.
If you really want to prevent it from rendering before receiving API data, just check that in your render function, for example:
if (this.props.wells.length === 0) {
return null
}
return (
<div id="map" ref="map">
{this.renderMarkers()}
</div>
)
So I have the similar problem, with react and found out solution on my own. by using Async/Await calling react
Code snippet is below please try this.
import Loader from 'react-loader-spinner'
constructor(props){
super(props);
this.state = {loading : true}
}
getdata = async (data) => {
return await data;
}
getprops = async (data) =>{
if (await this.getdata(data)){
this.setState({loading: false})
}
}
render() {
var { userInfo , userData} = this.props;
if(this.state.loading == true){
this.getprops(this.props.userData);
}
else{
//perform action after getting value in props
}
return (
<div>
{
this.state.loading ?
<Loader
type="Puff"
color="#00BFFF"
height={100}
width={100}
/>
:
<MyCustomComponent/> // place your react component here
}
</div>
)
}