I have a component that uses axios to access the PubMed api (in componentDidMount), retrieves some publication ids then stores them in state as "idlist". A second function is then called (addPapers) which passes in this id list and makes a second api call to retrieve further details (title, journal, authors) for each id. All this seems to work fine and when I use react tools to check state there is an array ("paperList") full of objects that have the expected key:value pairs. However, when I try to map over this array and access the values within the objects in the render function (ie paper.title, paper.author, paper.journal) they are returning as undefined. I haven't been using react for long and suspect I am making a basic mistake but cant figure it out.
I have tried console.logging each step and the expected data is in state and correct in react tools
import axios from 'axios'
import './App.css';
import rateLimit from 'axios-rate-limit';
class App extends Component {
state= {
idlist: [],
papersList : ""
}
componentDidMount () {
console.log("incomponent")
axios.get("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmode=json&retmax=1000&term=((Australia%5Bad%5D)%20AND%20(%222019%2F07%2F01%22%5BDate%20-%20Publication%5D%20%3A%20%223000%22%5BDate%20-%20Publication%5D))%20AND%20(%22nature%22%5BJournal%5D%20OR%20%22Nature%20cell%20biology%22%5BJournal%5D%20OR%20%22Nature%20structural%20%26%20molecular%20biology%22%5BJournal%5D)")
.then (response =>
this.setState({idlist: response.data.esearchresult.idlist}, () => {
this.addPapers(this.state.idlist)
}
)
)}
addPapers = (idlist) => {
if (idlist) {
const http = rateLimit(axios.create(), { maxRequests: 6, perMilliseconds: 1000 })
const list = this.state.idlist.map(id => {
let paperObj ={};
let paperList =[]
http.get(`https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&retmode=json&rettype=abstract&id=${id}&api_key=9476810b14695bd14f228e63433facbf9c08`)
.then (response2 => {
const title = response2.data.result[id].title
const journal = response2.data.result[id].fulljournalname
const authorList = []
const authors = response2.data.result[id].authors
authors.map((author, idx) =>
idx > 0 ? authorList.push(" " + author.name) : authorList.push(author.name))
paperObj.title = title
paperObj.journal = journal
paperObj.authors = authorList.toString()
paperList.push(paperObj)
})
return paperObj
})
this.setState({papersList: list})
}
}
render () {
let article = ""
if (this.state.papersList.length){
article = this.state.papersList.map(paper =>
console.log (paper.title)
console.log (paper.authors)
console.log (paper.journal)
)
}
return (
<div className="App">
<h1>Publications</h1>
{article}
</div>
);
}
}
export default App;
I expect that when I map over paperList and extract each paper I should be able to return the title, journal or authors using console.log(paper.title), console.log(paper.title), console.log(paper.title). These are all returning undefined.
You have two issues in code
1) paperList array declaration should be out of map loop.
2) paperList should be returned instead of paperObj
Working code below make some enhancements in render function
Also codesandbox link
import React from "react";
import ReactDOM from "react-dom";
import rateLimit from "axios-rate-limit";
import axios from "axios";
import "./styles.css";
class App extends React.Component {
state = {
idlist: [],
papersList: ""
};
componentDidMount() {
console.log("incomponent");
axios
.get(
"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmode=json&retmax=1000&term=((Australia%5Bad%5D)%20AND%20(%222019%2F07%2F01%22%5BDate%20-%20Publication%5D%20%3A%20%223000%22%5BDate%20-%20Publication%5D))%20AND%20(%22nature%22%5BJournal%5D%20OR%20%22Nature%20cell%20biology%22%5BJournal%5D%20OR%20%22Nature%20structural%20%26%20molecular%20biology%22%5BJournal%5D)"
)
.then(response =>
this.setState({ idlist: response.data.esearchresult.idlist }, () => {
this.addPapers(this.state.idlist);
})
);
}
addPapers = idlist => {
if (idlist) {
const http = rateLimit(axios.create(), {
maxRequests: 6,
perMilliseconds: 1000
});
let paperList = [];
this.state.idlist.forEach(id => {
let paperObj = {};
http
.get(
`https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&retmode=json&rettype=abstract&id=${id}&api_key=9476810b14695bd14f228e63433facbf9c08`
)
.then(response2 => {
const title = response2.data.result[id].title;
const journal = response2.data.result[id].fulljournalname;
const authorList = [];
const authors = response2.data.result[id].authors;
authors.map((author, idx) =>
idx > 0
? authorList.push(" " + author.name)
: authorList.push(author.name)
);
paperObj.title = title;
paperObj.journal = journal;
paperObj.authors = authorList.toString();
paperList.push(paperObj);
})
.then(result => {
this.setState({ papersList: paperList });
});
});
}
};
render() {
return (
<div className="App">
<h1>Publications</h1>
{this.state.papersList.length &&
this.state.papersList.map(data => {
return <div>{data.title}</div>;
})}
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Hope it helps!!!
Do it like this:
render () {
let article;
if (this.state.papersList.length){
article = this.state.papersList.map(paper => <p>span>Title is {paper.title}</span></p> )
}
return (
<div className="App">
<h1>Publications</h1>
{article}
</div>
);
}
Related
I am creating an application that displays information in three languages without using any APIS. In the settings page , user can click change to spanish button which will be stored in the ASYNC storage .I am new to react native and would like to know whether this is best practice .
ACCOUNT SETTINGS
import { View, Text ,Button} from 'react-native'
import React, { useState , useEffect} from 'react'
import {AsyncStorage} from 'react-native';
const Account = () => {
const setspanish=()=> {
const lanugage = {
language:"spanish",
}
AsyncStorage.getItem('lang').then((datacart)=>{
if (datacart !== null) {
// We have data!!
const lang = JSON.parse(datacart)
lang.push(lanugage)
AsyncStorage.setItem('lang',JSON.stringify(lang));
}
else{
const lang = []
lang.push(lanugage)
AsyncStorage.setItem('lang',JSON.stringify(lang));
}
alert("ChangedLnag")
})
.catch((err)=>{
alert(err)
})
}
return (
<View>
<Button onPress={setspanish} title="spanish"/>
</View>
)
}
export default Account
I have create a state in different pages , but none update automatically . Once i navigate to other pages , i have used ternary operators to render out depending on the state which recieves async storage language value but none works .
You should set it on the context and save it, and for first time you should take and set it again to context
I write example about that:
interface IConfig{
lang: "en" | "lalala";
}
interface IContextConfig{
config: IConfig;
setConfig?: (val: any) => void;
}
export const ContextConfigApp = React.createContext<IContextConfig>({
config: {lang: "en"},
});
interface IPropsProvider{
init?: IConfig;
children: React.ReactNode;
}
const Provider = ({init = {lang: "en"}}) => {
const [config,setConfig] = useState<IConfig>(init);
useEfect(() => {
AsyncStorage.setItem('config',JSON.stringify(config));
},[config]);
useEfect(() => {
(async () => {
const tmp = await AsyncStorage.getItem('config');
if(!!tmp && tmp) setConfig({...config,...JSON.parse(tmp)});
})();
},[]);
return (
<ContextConfigApp.Provider value={{config,setConfig}}>
{children}
</ContextConfigApp.Provider>
)
}
const App = () => {
return (
<Provider>
<Header />
</Provider>
)
}
const Header = () => {
const {setConfig,config} = useContext(ContextConfigApp);
return (
<Button onPress={() => {
setConfig({...config,lang: "en"})
}}>EN</Button>
)
}
Usage
const Example = () => {
const {config} = useContext(ContextConfigApp)
return (
<Text>{config.lang}</Text>
)
}
this is just example I hope it help you
This question already has answers here:
Using async/await inside a React functional component
(4 answers)
Closed 7 months ago.
I was given a snippet of a class named GithubService. It has a method getProfile, returning a promise result, that apparently contains an object that I need to reach in my page component Github.
GithubService.ts
class GithubService {
getProfile(login: string): Promise<GithubProfile> {
return fetch(`https://api.github.com/users/${login}`)
.then(res => res.json())
.then(({ avatar_url, name, login }) => ({
avatar: avatar_url as string,
name: name as string,
login: login as string,
}));
}
export type GithubProfile = {
avatar: string;
name: string;
login: string;
};
export const githubSerive = new GithubService();
The page component should look something like this:
import { githubSerive } from '~/app/github/github.service';
export const Github = () => {
let name = 'Joshua';
const profile = Promise.resolve(githubSerive.getProfile(name));
return (
<div className={styles.github}>
<p>
{//something like {profile.name}}
</p>
</div>
);
};
I'm pretty sure the Promise.resolve() method is out of place, but I really can't understand how do I put a GithubProfile object from promise into the profile variable.
I've seen in many tutorials they explicitly declare promise methods and set the return for all outcomes of a promise, but I can't change the source code.
as you are using React, consider making use of the useState and useEffect hooks.
Your Code could then look like below, here's a working sandBox as well, I 'mocked' the GitHub service to return a profile after 1s.
export default function Github() {
const [profile, setProfile] = useState();
useEffect(() => {
let name = "Joshua";
const init = async () => {
const _profile = await githubService.getProfile(name);
setProfile(_profile);
};
init();
}, []);
return (
<>
{profile ? (
<div>
<p>{`Avatar: ${profile.avatar}`}</p>
<p>{`name: ${profile.name}`}</p>
<p>{`login: ${profile.login}`}</p>
</div>
) : (
<p>loading...</p>
)}
</>
);
}
You should wait for the promise to be resolved by either using async/await or .then after the Promise.resolve.
const profile = await githubSerive.getProfile(name);
const profile = githubSerive.getProfile(name).then(data => data);
A solution would be:
import { githubSerive } from '~/app/github/github.service';
export async function Github() {
let name = 'Joshua';
const profile = await githubSerive.getProfile(name);
return (
<div className={styles.github}>
<p>
{profile.name}
</p>
</div>
);
}
But if you are using react, things would be a little different (since you have tagged reactjs in the question):
import { githubSerive } from '~/app/github/github.service';
import * as React from "react";
export const Github = () => {
let name = 'Joshua';
const [profile, setProfile] = React.useState();
React.useEffect(() => {
(async () => {
const profileData = await githubSerive.getProfile(name);
setProfile(profileData);
})();
}, [])
return (
<div className={styles.github}>
<p>
{profile?.name}
</p>
</div>
);
}
I am trying to fetch images by their ids. The architecture of backend is as follows: DB stores images in binary and there is another table that stores images ids.
I am using apollo client on front end to prefetch images ids and then send another set of fetch requests.
Unfortunately I get Error: Too many re-renders. React limits the number of renders to prevent an infinite loop. Could anyone help me to
1) figure out why it happens. I see that there is bunch of pending promises in the stack.
and 2) how it can be refactored to better architecture.
import React, {useState} from 'react'
import {useQuery} from "#apollo/react-hooks";
import {gql} from 'apollo-boost';
const apiEndpoint = 'http://localhost:9211';
const getProductImage = function (id) {
return gql`
{
productById(id: "${id}") {
images {
imageId
}
}
}`
};
const fetchImage = (imageUrl, allImgsArr) => {
return fetch(imageUrl)
.then(res => res.blob())
.then(img => allImgsArr.push(URL.createObjectURL(img)))
};
const ItemPage = (props) => {
const [id] = useState(props.match.params.id);
const {data} = useQuery(getProductImage(id));
let imagesIds = [];
if (data) {
data.productById.images.forEach(image => {
imagesIds.push(image.imageId)
});
}
const [imagesUrls, setImagesUrl] = useState([]);
// MULTIPE FETCH RETRIEVALS START
for (let imId of imagesIds) {
setImagesUrl(imagesUrls => [...imagesUrls, fetchImage(`${apiEndpoint}/image/${imId}`, imagesUrls)]);
}
// MULTIPE FETCH RETRIEVALS END
return (
<>
<div>
<div>
<img src={imagesUrls[0] ? imagesUrls[0] : ''} alt="main item 1 photo"/>
</div>
<div>
<div>
<img src={imagesUrls[1] ? imagesUrls[1] : ''} alt="Additional item 1 photo"/>
</div>
</div>
</div>
</>
)
};
export default ItemPage;
your query should be a constant , not function.
const GET_PRODUCT_IMAGE = gql`
query getProduct($id:String!) {
productById(id: $id) {
images {
imageId
}
}
}
}`
// pass variables like this
const {data} = useQuery(GET_PRODUCT_IMAGE, { variables: { id },
});
More Info : https://www.apollographql.com/docs/react/data/queries/
I stuck in this moment creating store with different products, that I want to add to the basket. The problem occur when I wanted to pass the state of cardList into Basket component to change the information from "Basket is empty" to display information how many items are currently in basket.
Below I paste my main hooks component with basket component which include all functionality.
Basket component:
import React from 'react'
const Basket = (props) => {
return (
<div>
{props.cardItems.length === 0 ? "Basket is empty" : <div> You have {props.cardItems.length} products in basket!</div>}
</div>
)
}
export default Basket;
Main component:
function
const [cardItems, setCardItems] = useState([]);
const price = 2.50;
useEffect(() => {
fetch(URL, {
method: 'GET',
headers: {
Accept: "application/json",
}
}).then(res => res.json())
.then(json => (setBeers(json), setFilteredBeers(json))
);
}, [])
function handleMatches(toMatch) {...
}
const displayFilterBeers = event => {...
}
const handleRemoveCard = () => {...
}
const handleAddToBasket = (event, beer) => {
setCardItems(state => {
let beerAlreadyInBasket = false;
cardItems.forEach(item => {
if (item.id === beer.id) {
beerAlreadyInBasket = true;
item.count++;
};
});
if (!beerAlreadyInBasket) {
cardItems.push({ ...beer, count: 1 })
}
localStorage.setItem('baketItems', JSON.stringify(cardItems));
console.log('cardItems: ', cardItems, cardItems.length);
return cardItems;
})
}
return (
<div className="App">
<div className='search'>
<input type='text' placeholder='search beer...' onChange={displayFilterBeers} />
</div>
<BeerList BeersList={filteredBeers} price={price} handleAddToBasket={handleAddToBasket} />
<Basket cardItems={cardItems}/>
</div>
);
}
export default App;
I saw an example that without React hooks that in basket component someone used const {cartItems} = this.props; but I don't know how to achieve something similar using hooks.
I think what you are facing is related to this issue.
So when dealing with array or list as state, react doesn't re-render if you don't set state value to a new instance. It assumes from the high-level comparison that the state hasn't been changed. So it bails out from the re-rendering.
from the issue I found this solution is better than the others -
const handleAddToBasket = (event, beer) => {
const nextState = [...cardItems, beer] // this will create a new array, thus will ensure a re-render
// do other stuffs
setCardItems(nextState);
};
I wont to make an app which fetch some pics of NASA image of the day. I show the today's pic and some (4 for example) previous. I use datepicker to choose the image of the day of date by my choice. The problem is sometimes it work fine, sometimes shows only the today's photo, sometimes today's plus one or two previous. Can someone explain what's going on ?
I've reset the cookies, try it with Firefox and Chromium. I upload the code with DEMO_KEY but in my app use the key received after registration.
App.js:
import React, { Component } from "react";
import DateInput from "./components/DateInput.js";
import Photo from "./components/Photo.js";
import Axios from "axios";
class App extends Component {
state = {
date: new Date(),
currentPhoto: "",
photos:[]
};
componentDidMount(){
Axios
.get(`https://api.nasa.gov/planetary/apod?&api_key=DEMO_KEY`)
.then(response => this.setState({currentPhoto: response.data}));
this.getImages(5);
}
getImages = n => {
const daysBuffer = [];
for(let i=1; i<n; i++){
let today = new Date();
today.setDate(today.getDate()-i);
daysBuffer.push(today);
}
const picBuffer = [];
const datesBuffer = daysBuffer.map(day => this.getDate(day));
datesBuffer.map(date => {
Axios
.get(`https://api.nasa.gov/planetary/apod?date=${date}&api_key=DEMO_KEY`)
.then(response => picBuffer.push(response.data));
})
this.setState({photos: picBuffer});
}
getDate = time => {
let year = time.getFullYear();
let month = time.getMonth();
let day = time.getDate();
return (
`${year}-${month}-${day}`
)
};
getPhoto = a => {
let date = this.getDate(a);
Axios
.get(`https://api.nasa.gov/planetary/apod?date=${date}&api_key=DEMO_KEY`)
.then(response => this.setState({currentPhoto: response.data}))
}
changeDate = date => {
this.setState({
date
});
this.getPhoto(date);
}
render() {
const imageGrid = this.state.photos.map(pic => {
return (
<ul>
<Photo photo = {pic} key={pic.date} />
</ul>
)
})
return (
<div>
<h1>NASA's Astronomy Picture of the Day</h1>
<DateInput
changeDate = {this.changeDate}
date = {this.state.date}
/>
<Photo photo = {this.state.currentPhoto} />
{imageGrid}
</div>
);
}
}
export default App;
DateInput.js:
import React from "react";
import DatePicker from 'react-datepicker';
import "react-datepicker/dist/react-datepicker.css";
const DateInput = props => (
<div>
Select a Date:
<DatePicker
selected = {props.date}
onChange = {props.changeDate}
/>
</div>
);
export default DateInput;
Photo.js
import React from 'react';
const Photo = props => (
<div>
<h3>{props.photo.title}</h3>
<img src={props.photo.url} alt={props.photo.title} />
<p>{props.photo.explanation}</p>
</div>
)
export default Photo;
The most likely problem in your code, is that you are taking synchronous action while you are retrieving images asynchronously.
The main problem lies in your getImages function
getImages = n => {
const daysBuffer = [];
for(let i=1; i<n; i++){
let today = new Date();
today.setDate(today.getDate()-i);
daysBuffer.push(today);
}
const picBuffer = [];
const datesBuffer = daysBuffer.map(day => this.getDate(day));
datesBuffer.map(date => {
Axios
.get(`https://api.nasa.gov/planetary/apod?date=${date}&api_key=DEMO_KEY`)
.then(response => picBuffer.push(response.data));
})
this.setState({photos: picBuffer}); //this line runs before Axios finishes
}
To fix this, without moving to async/await (which is better but requires restructuring), you would have to change the last few lines to this:
datesBuffer.map(date => {
Axios
.get(`https://api.nasa.gov/planetary/apod?date=${date}&api_key=DEMO_KEY`)
.then(response => {
picBuffer.push(response.data);
this.setState({photos: picBuffer});
})
})
Notice that it is now setting the state many times, which is not ideal, but without knowing the capabalities of Axios regarding async/await this would be the most logical solution.