I am having an issue with calling an async function in React to an outside API where I'm looking to call the function from two separate events, an onSubmit of a form, and an onClick from the results of a separate search.
I have the following React components:
App.js:
import React, { useState, useEffect } from 'react'
import './App.css';
import Ticker from './compoments/Ticker'
import SearchTicker from './compoments/SearchTicker'
import TickerResults from './compoments/TickerResults'
import SearchCompanyProfile from './compoments/SearchCompanyProfile';
function App() {
const [company, setCompany] = useState([])
const [price, setPrice] = useState([])
const [symbol, setSymbol] = useState([])
async function fetchCompanyProfile(e) {
let company = '';
if(e.target.target.ticker.value){
company = e.target.ticker.value
} else {
company = e.target.innerHTML;
}
console.log(company)
e.preventDefault()
e.target.reset()
const companyData = await fetch(https://someapicompany.com/profile/${company}?apikey=xyz)
.then(res => res.json())
.then(data => data)
setCompany({data: companyData})
}
console.log(company.data)
return (
<div className="App">
<SearchTicker getTicker={fetchTicker}/>
<SearchCompanyProfile getCompanyProfile={fetchCompanyProfile}/>
<TickerResults ticker={symbol} getTicker={fetchCompanyProfile}/>
<CompanyProfile company={company}/>
</div>
);
}
export default App;
SearchCompanyProfile.js
import React from 'react'
const SearchCompanyProfile = (props) => {
return (
<div>
<form onSubmit={props.getCompanyProfile} >
<input type="text" name="ticker" placeholder="Enter Ticker..."/>
<button value="Search" type="submit">
Search
</button>
</form>
</div>
)
}
export default SearchCompanyProfile;
SearchTicker.js
import React from 'react'
const SearchTicker = (props) => {
return (
<div>
<form onSubmit={props.getTicker}>
<input type="text" name="symbol" placeholder="Enter Compamy Name..."/>
<button value="Search" type="submit">
Search
</button>
</form>
</div>
)
}
export default SearchTicker;
TickerResults.js
import React from 'react'
const TickerResults = (props) => {
console.log(props)
return (
<div>
{props.ticker.data && props.ticker.data.map(x => <p name="ticker" value={x.symbol} onClick={props.getTicker}>{x.symbol}</p>)}
</div>
)
}
export default TickerResults;
My goal is to call the async function fetchCompanyProfile from both submitting the SearchCompanyProfile form and clicking on the ticker from the tickerResults component.
I have tried both:
async function fetchCompanyProfile(e) {
let company = '';
if(e.target.ticker.value){
company = e.target.ticker.value;
} else {
company = e.target.innerHTML;
}
console.log(company)
e.preventDefault()
e.target.reset()
const companyData = await fetch(https://someapicompany.com/profile/${company}?apikey=xyz)
.then(res => res.json())
.then(data => data)
setCompany({data: companyData})
}
console.log(company.data)
-and-
async function fetchCompanyProfile(e) {
let company = '';
if(e.target.innerHTML){
company = e.target.innerHTML;
} else {
company = e.target.ticker.value;
}
console.log(company)
e.preventDefault()
e.target.reset()
const companyData = await fetch(https://someapicompany.com/profile/${company}?apikey=xyz)
.then(res => res.json())
.then(data => data)
setCompany({data: companyData})
}
console.log(company.data)
Under both instances it works if the correct event happens in the right order but in the first case, I run into "value cannot be run on undefined" if I use the onClick event through tickerResults component, and in the second case, the innerHTML is just the form inputs from the the searchCompanyProfile form so the fetch is not called with the correct value.
I realize this is a long winded question but any suggestions would be appreciated.
Thy this, you need to call setCompany({ data }) inside of then because it's promise
function App() {
const [company, setCompany] = useState([]);
const [price, setPrice] = useState([]);
const [symbol, setSymbol] = useState([]);
function fetchCompanyProfile(e) {
let company = '';
if(e.target.target.ticker.value){
company = e.target.ticker.value
} else {
company = e.target.innerHTML;
}
console.log(company);
e.preventDefault();
e.target.reset();
fetch('https://someapicompany.com/profile/${company}?apikey=xyz')
.then(res => res.json())
.then(data => {
setCompany({ data })
});
}
console.log(company.data)
return (
<div className="App">
<SearchTicker/>
<SearchCompanyProfile getCompanyProfile={fetchCompanyProfile}/>
<TickerResults ticker={symbol} getTicker={fetchCompanyProfile}/>
<CompanyProfile company={company}/>
</div>
);
}
Here's the async/await solution:
async function fetchCompanyProfile(e) {
let company = '';
if(e.target.innerHTML){
company = e.target.innerHTML;
} else {
company = e.target.ticker.value;
}
console.log(company)
e.preventDefault()
e.target.reset()
let companyData = await fetch(https://someapicompany.com/profile/${company}?apikey=xyz)
setCompany({data: companyData.json()})
}
Related
I was trying to set my value in the input value! but after that, I cannot write anything in the input field! I wanted to set values from the back end in value!
We are writing an admin channel to edit the article for that we need already existing article values to edit the article! What am I doing wrong! or Maybe you can suggest a better way to edit the article in the admin channel!
here is the code:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { useParams } from 'react-router';
const EditArticle = (props) => {
const [editValues, setEditValues] = useState([]);
const [changedValues, setChangedValues] = useState('');
console.log('values', editValues);
console.log('changed', changedValues);
const params = useParams();
console.log(params);
const resultsId = params.id;
console.log('string', resultsId);
const [authTokens, setAuthTokens] = useState(
localStorage.getItem('token') || ''
);
const setTokens = (data) => {
localStorage.setItem('token', JSON.stringify(data));
setAuthTokens(data);
// setToken(data['dataValues']['token']);
};
useEffect(() => {
const fetchData = async () => {
try {
const res = await axios.get(
`${process.env.REACT_APP_API_URL}/article/${resultsId}`
);
setEditValues(res.data);
} catch (err) {}
};
fetchData();
}, [resultsId]);
const inputValue = editValues;
const userToken = props.token;
return (
<div>
<form value={{ authTokens, setAuthTokens: setTokens }}>
<input
value={editValues.title || ''}
onChange={(input) => setChangedValues(input.target.value)}
type='text'
/>
<input
// ref={editValues.shortDesc}
value={editValues.shortDesc}
onChange={(input) => setChangedValues(input.target.value)}
type='text'
/>
<button type='submit'>send</button>
</form>
</div>
);
};
export default EditArticle;
your onChange handler is updating a different state property than what is being used as the value on the input (editValues vs changedValues).
Also you can pass a defaultValue to input that will get used as the default value only.
See more here https://reactjs.org/docs/uncontrolled-components.html
you can use just do it just using editValues. try this:
I just reproduced it without the api call to run the code.
import React, { useState, useEffect } from "react";
const EditArticle = (props) => {
const [editValues, setEditValues] = useState([]);
console.log("values", editValues);
const [authTokens, setAuthTokens] = useState(
localStorage.getItem("token") || ""
);
const setTokens = (data) => {
localStorage.setItem("token", JSON.stringify(data));
setAuthTokens(data);
// setToken(data['dataValues']['token']);
};
useEffect(() => {
const fetchData = async () => {
try {
//here get the data from api and setstate
setEditValues({ title: "title", shortDesc: "shortDesc" });
} catch (err) {}
};
fetchData();
}, []);
return (
<div>
<form value={{ authTokens, setAuthTokens: setTokens }}>
<input
value={editValues.title || ""}
onChange={(input) => setEditValues({title: input.target.value})}
type="text"
/>
<input
value={editValues.shortDesc}
onChange={(input) => setEditValues({shortDesc: input.target.value})}
type="text"
/>
<button type="submit">send</button>
</form>
</div>
);
};
export default EditArticle;
Hi guys trying to make an mini application in react JS , in that application i want to display data in the my body part when i do search on my textbar , so anyone tell me how can i do it or what should i do to display data?
App.js This is my main component
import './App.css';
import Star from './Star';
import People from './People';
import Planet from './Planet';
function App(props) {
const {people,planet} = props;
return (
<div className="App">
<Star />
<People data={people}/>
<Planet data={planet}/>
</div>
);
}
export default App;
Star.js
This is my star component where i fetch my all star war apis
import React, { useState, useEffect } from 'react';
import './Star.css';
const Star = () => {
const [search, setSearch] = useState('');
const [people, setPeople] = useState([]);
const [planet, setPlanet] = useState([]);
const onSubmit = (e) => {
e.preventDefault();
if (search === "") {
alert("please Enter some value");
}
}
useEffect(() => {
async function fetchPeople() {
let result = await fetch("https://swapi.dev/api/people/?format=json");
let data = await result.json();
setPeople(data.results);
}
async function fetchPlanet() {
let result = await fetch("https://swapi.dev/api/planets/?format=json");
let data = await result.json();
setPlanet(data.results);
}
fetchPeople();
fetchPlanet();
}, [])
console.log("people", people);
console.log("planet", planet);
return (
<div>
<div className='container'>
<h2>Star War</h2>
<div className='jumbotron'>
<input type="text"
className="form-control"
placeholder='Search...'
value={search}
onChange={(e) => setSearch(e.target.value)} />
<span><button className='btn btn-secondary' onClick={onSubmit}>Search</button></span>
</div>
</div>
</div>
)
}
export default Star;
people.js
This is my people that i want to display in the my body part
import React from 'react';
const People = (props) => {
const { data } = props;
return (
<div className="row">
{data && data.map((people, i) => {
return (
<div className="col-md-3" key={i}>
<div className="card">
<div className="card-body">
<h4>{people.name}</h4>
</div>
</div>
</div>
)
})}
</div>
);
};
export default People;
If I understand correctly your issue, you want to display the People component and the issue is how to pass it the data fetched in the Star component.
The solution is to move the state of the data in the parent component so that it can be passed easily to all its children.
function App(props) {
const [people, setPeople] = useState([]);
const [planet, setPlanet] = useState([]);
useEffect(() => {
async function fetchPeople() {
let result = await fetch("https://swapi.dev/api/people/?format=json");
let data = await result.json();
setPeople(data.results);
}
async function fetchPlanet() {
let result = await fetch("https://swapi.dev/api/planets/?format=json");
let data = await result.json();
setPlanet(data.results);
}
fetchPeople();
fetchPlanet();
}, [])
return (
<div className="App">
<Star
people={people} //if you need these in the Star component
planet={planet} //if you need these in the Star component
/>
<People data={people}/>
<Planet data={planet}/>
</div>
);
}
As the query will fetch the value that is provided in useState. But I want the search bar to search for recipes provided by me. Can anybody help me on how I can do that.
import React, { useEffect, useState } from 'react';
import Recipe from './Recipe';
import './App.css';
const App = ()=>{
const APP_ID= '2*****'
const APP_KEY= 'f******************'
const [recipes, setRecipes] = useState([]);
const [search, setSearch] = useState("");
const [query, setQuery] = useState('chicken');
useEffect(() =>{
const getRecipes = async()=>{
const response = await fetch(`https://api.edamam.com/search?q=${query}&app_id=${APP_ID}&app_key=${APP_KEY}`);
const data = await response.json();
setRecipes(data.hits)
};
getRecipes();
},[query]);
const updateSearch = e=>{
setSearch(e.target.value);
}
const getSearch = e =>{
setQuery(search);
setSearch('');
}
return(
<div className="App">
<form onSubmit={getSearch} className="search-form">
<input className="search-bar" type="text" value={search} onChange={updateSearch} />
<button className="search-button" type="submit">Search</button>
</form>
{recipes.map(recipe =>(
<Recipe
key={recipe.recipe.label}
title={recipe.recipe.label}
calories={recipe.recipe.calories}
image={recipe.recipe.image} />
))}
</div>
)
}
export default App;
don't create multiple states for the same thing.
pass search string to the fetch API
import React, { useState, useEffect } from "react";
import "./styles.css";
const App = () => {
const APP_ID = "2***********";
const APP_KEY = "f*********";
const [recipes, setRecipes] = useState([]);
const [search, setSearch] = useState("");
useEffect(() => {
try {
const getRecipes = async () => {
const response = await fetch(
`https://api.edamam.com/search?q=${search}&app_id=${APP_ID}&app_key=${APP_KEY}`
);
const data = await response.json();
setRecipes(data.hits);
};
getRecipes();
} catch (error) {
// handle error here
console.error(error);
}
}, [search]);
const updateSearch = (e) => {
setSearch(e.target.value);
};
const getSearch = (e) => {
setSearch("");
};
return (
<div className="App">
<form onSubmit={getSearch} className="search-form">
<input
className="search-bar"
type="text"
value={search}
onChange={updateSearch}
/>
<button className="search-button" type="submit">
Search
</button>
</form>
{recipes.map((recipe) => (
<Recipe
key={recipe.recipe.label}
title={recipe.recipe.label}
calories={recipe.recipe.calories}
image={recipe.recipe.image}
/>
))}
</div>
);
};
export default App;
edit:
If initially, you want to have chicken results from API, change the response variable to this:
const response = await fetch(
`https://api.edamam.com/search?q=${search || "chicken"}&app_id=${APP_ID}&app_key=${APP_KEY}`
);
I have this React component that I use to update business entities. It basically fetches by ID on componentDidMount and sends a put request when the form is submitted. I would like to refactor this to a hook based component.
Here is the code before
import React from "react";
import axios from "axios";
//Api Helper Methods
const API_HOST = "https://api.example.com";
const get = (endPoint) =>
axios
.get(`${API_HOST}/${endPoint}`)
.then((response) => response.data);
export const put = (endPoint, payload, id) =>
axios
.put(`${API_HOST}/${endPoint}/${id}`, payload)
.then((response) => response.data);
//React route (uses React Router)
const END_POINT = `users`;
class Entity extends React.Component {
state = { entity: {}, fetching: true };
getEntity = async () => {
const { id } = this.props.match.params;
this.setState({ fetching: true });
const entity = await get(`${END_POINT}/${id}`);
this.setState({ entity, fetching: false });
};
onChange = (key, value) =>
this.setState({ entity: { ...this.state.entity, [key]: value } });
componentDidMount() {
this.getEntity();
}
onSubmit = async (e) => {
e.preventDefault();
let { entity } = this.state;
let { match } = this.props;
await put(END_POINT, entity, match.params.id);
};
render() {
const { entity, fetching } = this.state;
if (fetching) {
return <p>loading...</p>;
}
return (
<form onSubmit={this.onSubmit}>
<label htmlFor="name">name</label>
<input
value={entity["name"]}
onChange={(e) => this.onChange("name", e.target.value)}
/>
<button type="submit">submit</button>
</form>
);
}
}
export default Entity;
This is what I have so far for the code after. Next step would be to extract custom hook.
const END_POINT = `users`;
export default function Entity({ match }) {
const [entity, setEntity] = useState({ name: "" });
const [fetching, setFetching] = useState( true );
const { id } = match.params;
const onChange = (key, value) => setEntity({ ...entity, [key]: value });
useEffect(() => {
const fetchEntity = async () => {
const entity = await get(`${END_POINT}/${id}`);
setEntity(entity);
setFetching(false);
};
fetchEntity();
}, [id]);
const onSubmit = async (e) => {
e.preventDefault();
await put(END_POINT, entity, id);
};
if (fetching) {
return <p>loading...</p>;
}
return (
<form onSubmit={onSubmit}>
<label htmlFor="name">name</label>
<input
value={entity["name"]}
onChange={(e) => onChange("name", e.target.value)}
/>
<button type="submit">submit</button>
</form>
);
}
I haven't tested this but this should be close to what you want with a custom hook for your entity function.
import React, { useEffect, useState } from 'react';
const API_HOST = "https://api.example.com";
const END_POINT = `users`;
function useEntity(entityID) {
const [entity, setEntity] = useState({})
useEffect(() => {
(async () => {
await fetch(`${API_HOST}/${END_POINT}/${props.match.params}`)
.then(async res => await res.json())
.then(result => setEntity(result));
})();
}, [])
return entity
}
export default function Entity(props) {
const { id } = props.match;
const entity = useEntity(id);
const onSubmit = async () => await fetch(`${API_HOST}/${END_POINT}/${id}`, {method: 'PUT', body: entity})
if (!entity) {
return <p>loading...</p>;
}
return (
<form onSubmit={onSubmit}>
<label htmlFor="name">name</label>
<input
value={entity["name"]}
onChange={(e) => setEntity({ ...entity, name: e.target.value})}
/>
<button type="submit">submit</button>
</form>
)
}
Thanks for the help Harben, I got it working like this.
import React, {useEffect, useState} from "react";
import axios from "axios";
//Api Helper Methods
const API_HOST = "https://api.example.com";
const get = (endPoint) =>
axios.get(`${API_HOST}/${endPoint}`).then((response) => response.data);
export const put = (endPoint, payload, id) =>
axios
.put(`${API_HOST}/${endPoint}/${id}`, payload)
.then((response) => response.data);
const END_POINT = `users`;
const useEntity = (entityId) => {
const [entity, setEntity] = useState({ name: "" });
const [fetching, setFetching] = useState(true);
useEffect(() => {
(async () => {
const entity = await get(`${END_POINT}/${entityId}`);
setEntity(entity);
setFetching(false);
})();
}, [entityId]);
return [entity, fetching, setEntity];
};
//React route (uses React Router)
export default function Entity({ match }) {
const { id } = match.params;
const [entity, fetching, setEntity] = useEntity(id);
const onChange = (key, value) => setEntity({ ...entity, [key]: value });
const onSubmit = async (e) => {
e.preventDefault();
await put(END_POINT, entity, id);
};
if (fetching) {
return <p>loading...</p>;
}
return (
<form onSubmit={onSubmit}>
<label htmlFor="name">name</label>
<input
value={entity["name"]}
onChange={(e) => onChange("name", e.target.value)}
/>
<button type="submit">submit</button>
</form>
);
}
I'm using global giving API to make a charity finder app.
I have two dropdowns and a search button in the CharityFinderPage.js component. Now on clicking the search button, I want to fetch the charities using the themeId. The endpoint is https://api.globalgiving.org/api/public/projectservice/themes/{themeId}/projects
I know that on handleClick I should fetch the charities, but how do I get the value of themeId in the handleClick of CharityFinderPage.js component.
What I want is to show a new card component when the button clicks like showing a charity card with the fields populated on it from the data of the API, but first I need to be able to get the data from the API, then I can render a new component.
Here's the code:
CharityFinderPage.js
const CharityFinderPage = () => {
const handleClick = () => {
console.log("inside handleclick")
}
return (
<div style={containerStyle}>
<h1>Charity Finder ❤️</h1>
<h3>Search for charity</h3>
<h4>
Filter charities by personal search conditions. Use the dropdown below
to see charities matching your criteria.
</h4>
<Themes />
<Regions />
<button onClick={handleClick}>Search</button>
</div>
)
}
export default CharityFinderPage
Themes.js
import React, { useEffect, useState } from "react"
import axios from "axios"
const url = `https://api.globalgiving.org/api/public/projectservice/themes.json?api_key=${process.env.REACT_APP_api_key}`
const Themes = () => {
const [isLoading, setIsLoading] = useState(false)
const [selectValue, setSelectValue] = useState("")
const [themes, setThemes] = useState([])
useEffect(() => {
const fetchThemes = async () => {
try {
setIsLoading(true)
const result = await axios.get(url)
setThemes(result.data.themes.theme)
setIsLoading(false)
} catch (err) {
console.log(err)
}
}
fetchThemes()
}, [])
const handleChange = (event) => {
console.log("inside handleChange", event.target.value)
setSelectValue(event.target.value)
}
return (
<div>
{isLoading ? (
<h4>Loading......</h4>
) : (
<div>
<label>Select theme: </label>
<select onChange={handleChange} value={selectValue}>
{themes.map((theme, id) => {
return <option key={id}>{theme.name}</option> //{id} is the `themeId`
})}
</select>
</div>
)}
</div>
)
}
export default Themes
Regions component is exactly similar to Themes.
So the thing that you need to do here is called lifting the state up.
You need to move your states of theme component to CharityFinder component
I am lifting only selectedValue because that is all that you need
CharityFinderPage.js
const CharityFinderPage = () => {
const [selectValue, setSelectValue] = useState("")
const handleClick = () => {
console.log(`inside handleclick with ${selectValue}`)
}
return (
<div style={containerStyle}>
<h1>Charity Finder ❤️</h1>
<h3>Search for charity</h3>
<h4>
Filter charities by personal search conditions. Use the dropdown below
to see charities matching your criteria.
</h4>
// you can pass the setSelectValue as prop to Themes component
<Themes setSelectValue={setSelectValue} selectValue={selectValue} />
<Regions />
<button onClick={handleClick}>Search</button>
</div>
)
}
export default CharityFinderPage
Theme.js
import React, { useEffect, useState } from "react"
import axios from "axios"
const url = `https://api.globalgiving.org/api/public/projectservice/themes.json?api_key=${process.env.REACT_APP_api_key}`
const Themes = ({ selectValue, setSelectValue }) => {
const [isLoading, setIsLoading] = useState(false)
const [themes, setThemes] = useState([])
useEffect(() => {
const fetchThemes = async () => {
try {
setIsLoading(true)
const result = await axios.get(url)
setThemes(result.data.themes.theme)
setIsLoading(false)
} catch (err) {
console.log(err)
}
}
fetchThemes()
}, [])
const handleChange = (event) => {
console.log("inside handleChange", event.target.value)
setSelectValue(event.target.value)
}
return (
<div>
{isLoading ? (
<h4>Loading......</h4>
) : (
<div>
<label>Select theme: </label>
<select onChange={handleChange} value={selectValue}>
{themes.map((theme, id) => {
return <option key={id}>{theme.name}</option> //{id} is the `themeId`
})}
</select>
</div>
)}
</div>
)
}
export default Themes
You can do this.
const CharityFinderPage = () => {
const [themeId, setThemeId] = useState();
const handleClick = () => {
console.log("inside handleclick")
// make call to endpoint with themeId
}
return (
<div style={containerStyle}>
<h1>Charity Finder ❤️</h1>
<h3>Search for charity</h3>
<h4>
Filter charities by personal search conditions. Use the dropdown below
to see charities matching your criteria.
</h4>
<Themes setThemeId={setThemeId} />
<Regions />
<button onClick={handleClick}>Search</button>
</div>
)
}
export default CharityFinderPage
Then in Themes.js:
...
const handleChange = (event) => {
console.log("inside handleChange", event.target.value)
props.setThemeId(event.target.value);
setSelectValue(event.target.value)
}
...