This is code and I am trying to access the result from the api but I am not able to show it on my page.
The results are visible in console but not in page.
I have tried few things but it hasn't worked for me.
I am stuck on this only for the last few days.
import React, { useState } from 'react'
const SearchArea = () => {
const [input,setInput] = useState("");
const [results,setResults] = useState(null)
const onInputChange = (ev) => {
setInput(ev.target.value)
}
const onSearch = () => {
fetch(`https://api.shrtco.de/v2/shorten?url=${input}`)
.then(r=>r.json())
.then(result=>{
setResults(result) ;
console.log(result.result.full_short_link)})
}
const onKeyDown= (ev) =>{
if(ev.keyCode === 13){
onSearch();
}
}
const renderResult = () => {
if(results && results.ok === 0){
return <div>No link to convert.</div>
}
if(results && results.length > 0){
return <div>{results.map((item)=><div>{item.results.result.full_short_link}</div>)}</div>
}
return null
}
return (
<div>
<div className="search-bar">
<input type="text" onKeyDown={onKeyDown} onChange={onInputChange} value={input} className="searching" placeholder="Shorten a link here..."></input>
<button className="short-butt" onClick={onSearch}>Shorten It!</button>
<div>{renderResult()}</div>
</div>
</div>
)
}
export default SearchArea
From what I can see, it seems that you are trying to show MULTIPLE results from the API. So you must start with an array instead of null in the state.
const [results, setResults] = useState([]);
Then for every response from the API, you could either push into the results with results.push() (not recommended) or you could do spread operator like below (more recommended):
fetch(`https://api.shrtco.de/v2/shorten?url=${input}`)
.then((r) => {
return r.json();
})
.then((result) => {
console.log(result.result.full_short_link);
setResults([...results, result.result.full_short_link]); //spread operator
})
.catch((e) => {
console.error(e);
});
Later you can use map on showing the results.
if(results && results.length > 0){
return <div>{results.map((item)=><div>{item.results.result.full_short_link}</div>)}</div>
}
Result:
You can see the code in action: https://codesandbox.io/s/react-link-shorter-7ro9o?file=/index.js
Related
I'm trying to make an autocomplete search for Alpha Vantage API, but the array which should contain the matches (suggestion) always returns empty when I type in the input field and I don't know what could be the reason, I'm stuck for a while on this and would appreciate if someone could help me with this.
The related code here is mostly in useEffect and the inputHandler:
const Search = () => {
useEffect(() => {
const getSymbols = async () => {
const searchURL = `https://www.alphavantage.co/query?function=SYMBOL_SEARCH&keywords=${textInput}&apikey=${process.env.REACT_APP_ALPHA_VANTAGE_API_KEY}`
const res = await axios.get(searchURL);
if(res) {
setSecurity(res.data.bestMatches);
if(security !== undefined && security.length > 0) {
let symbols = security.map(sec => sec['1. symbol'])
setAllSymbol(symbols);
}
}
}
getSymbols();
}, [])
console.log(allSymbol);
const inputHandler = (e) => {
setTextInput(e.target.value);
let matches = [];
if(textInput.length > 0) {
matches = allSymbol.filter(sym => {
const regex = new RegExp(`${textInput}`, "gi");
return sym.match(regex);
})
setSuggestion(matches);
}
console.log(suggestion);
setTextInput(e.target.value);
}
const showData = async (e) => {
e.preventDefault();
const searchURL = `https://www.alphavantage.co/query?function=SYMBOL_SEARCH&keywords=${textInput}&apikey=${process.env.REACT_APP_ALPHA_VANTAGE_API_KEY}`
const monthlyURL = `https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=${textInput}&apikey=${process.env.REACT_APP_ALPHA_VANTAGE_API_KEY}`
try {
const res = await axios.get(searchURL);
const data = await axios.get(monthlyURL);
if(res) {
setTickers(res.data.bestMatches[0]);
setSymbol(res.data.bestMatches[0]['1. symbol']);
setSecurity(res.data.bestMatches);
if(data) {
const monthlyTimeSeries = Object.values(data.data['Monthly Time Series']);
const result = [monthlyTimeSeries[1]];
const resultValues = Object.keys(result[0]).map(key => {
return Math.floor(result[0][key]);
})
setPrices(resultValues);
}
}
} catch(err) {
console.log(err)
}
setDailyPrices([]);
setWeeklyPrices([]);
setIntraPrices([]);
}
return (
<StyledSearch>
<div className="wrapper">
<h1>Security Price Monitor App </h1>
<form onSubmit={showData} className="search-form">
<input type="text" value={textInput} onChange={inputHandler} placeholder='Enter Stock Symbol (GOOG, MSFT)'/>
<button type="submit">Search</button>
</form>
</div>
{prices.length > 0 && (
<>
<Table prices={prices} symbol={symbol}/>
<TimeFrames symbol={symbol} textInput={textInput} weeklyPrices={weeklyPrices} setWeeklyPrices={setWeeklyPrices} dailyPrices={dailyPrices} setDailyPrices={setDailyPrices} intraPrices={intraPrices} setIntraPrices={setIntraPrices} />
</>
)}
</StyledSearch>
)
}
I have a list and this list has several elements and I iterate over the list. For each list I display two buttons and an input field.
Now I have the following problem: as soon as I write something in a text field, the same value is also entered in the other text fields. However, I only want to change a value in one text field, so the others should not receive this value.
How can I make it so that one text field is for one element and when I write something in this text field, it is not for all the other elements as well?
import React, { useState, useEffect } from 'react'
import axios from 'axios'
function Training({ teamid }) {
const [isTrainingExisting, setIsTrainingExisting] = useState(false);
const [trainingData, setTrainingData] = useState([]);
const [addTraining, setAddTraining] = useState(false);
const [day, setDay] = useState('');
const [from, setFrom] = useState('');
const [until, setUntil] = useState('');
const getTrainingData = () => {
axios
.get(`${process.env.REACT_APP_API_URL}/team/team_training-${teamid}`,
)
.then((res) => {
if (res.status === 200) {
if (typeof res.data !== 'undefined' && res.data.length > 0) {
// the array is defined and has at least one element
setIsTrainingExisting(true)
setTrainingData(res.data)
}
else {
setIsTrainingExisting(false)
}
}
})
.catch((error) => {
//console.log(error);
});
}
useEffect(() => {
getTrainingData();
}, []);
const deleteTraining = (id) => {
axios
.delete(`${process.env.REACT_APP_API_URL}/team/delete/team_training-${teamid}`,
{ data: { trainingsid: `${id}` } })
.then((res) => {
if (res.status === 200) {
var myArray = trainingData.filter(function (obj) {
return obj.trainingsid !== id;
});
//console.log(myArray)
setTrainingData(() => [...myArray]);
}
})
.catch((error) => {
console.log(error);
});
}
const addNewTraining = () => {
setAddTraining(true);
}
const addTrainingNew = () => {
axios
.post(`${process.env.REACT_APP_API_URL}/team/add/team_training-${teamid}`,
{ von: `${from}`, bis: `${until}`, tag: `${day}` })
.then((res) => {
if (res.status === 200) {
setAddTraining(false)
const newTraining = {
trainingsid: res.data,
mannschaftsid: teamid,
von: `${from}`,
bis: `${until}`,
tag: `${day}`
}
setTrainingData(() => [...trainingData, newTraining]);
//console.log(trainingData)
}
})
.catch((error) => {
console.log(error);
});
}
const [editing, setEditing] = useState(null);
const editingTraining = (id) => {
//console.log(id)
setEditing(id);
};
const updateTraining = (trainingsid) => {
}
return (
<div>
{trainingData.map((d, i) => (
<div key={i}>
Trainingszeiten
<input class="input is-normal" type="text" key={ d.trainingsid } value={day} placeholder="Wochentag" onChange={event => setDay(event.target.value)} readOnly={false}></input>
{d.tag} - {d.von} bis {d.bis} Uhr
<button className="button is-danger" onClick={() => deleteTraining(d.trainingsid)}>Löschen</button>
{editing === d.trainingsid ? (
<button className="button is-success" onClick={() => { editingTraining(null); updateTraining(d.trainingsid); }}>Save</button>
) : (
<button className="button is-info" onClick={() => editingTraining(d.trainingsid)}>Edit</button>
)}
<br />
</div>
))}
)
}
export default Training
The reason you see all fields changing is because when you build the input elements while using .map you are probably assigning the same onChange event and using the same state value to provide the value for the input element.
You should correctly manage this information and isolate the elements from their handlers. There are several ways to efficiently manage this with help of either useReducer or some other paradigm of your choice. I will provide a simple example showing the issue vs no issue with a controlled approach,
This is what I suspect you are doing, and this will show the issue. AS you can see, here I use the val to set the value of <input/> and that happens repeatedly for both the items for which we are building the elements,
const dataSource = [{id: '1', value: 'val1'}, {id: '2', value: 'val2'}]
export default function App() {
const [val, setVal]= useState('');
const onTextChange = (event) => {
setVal(event.target.value);
}
return (
<div className="App">
{dataSource.map(x => {
return (
<div key={x.id}>
<input type="text" value={val} onChange={onTextChange}/>
</div>
)
})}
</div>
);
}
This is how you would go about it.
export default function App() {
const [data, setData]= useState(dataSource);
const onTextChange = (event) => {
const id = String(event.target.dataset.id);
const val = String(event.target.value);
const match = data.find(x => x.id === id);
const updatedItem = {...match, value: val};
if(match && val){
const updatedArrayData = [...data.filter(x => x.id !== id), updatedItem];
const sortedData = updatedArrayData.sort((a, b) => Number(a.id) - Number(b.id));
console.log(sortedData);
setData(sortedData); // sorting to retain order of elements or else they will jump around
}
}
return (
<div className="App">
{data.map(x => {
return (
<div key={x.id}>
<input data-id={x.id} type="text" value={x.value} onChange={onTextChange}/>
</div>
)
})}
</div>
);
}
What im doing here is, finding a way to map an element to its own with the help of an identifier. I have used the data-id attribute for it. I use this value again in the callback to identify the match, update it correctly and update the state again so the re render shows correct values.
I have an Autocomplete component:
Autocomplete
function Autocomplete() {
const [ matches, setMatches ] = useState([ 'game' ]);
const [ query, setQuery ] = useState('');
const [ menuState, setMenuState ] = useState(false);
useEffect(() => {
if(query !== ""){
updateQuery()
}
}, [query])
const updateQuery = async () => {
const data = await props.searchQuery(query);
if(data.length > 1 ){
setMenuState(true);
setMatches(data);
} else if (data.length < 1){
setMenuState(false);
setMatches([]);
}
}
return (
<div className={rootClassName(null, [props.className], classStates)} style={styles}>
<div className='Autocomplete__Trigger'>
<input
className='Input'
type='text'
placeholder={props.placeholder}
value={query}
onChange={ e => setQuery(e.target.value) }
onKeyDown={ handleKeyPress }
/>
</div>
<div className='Autocomplete__Menu' role='menu'>
{
//Custom if function
If(matches.length > 0, () => (
<div className='Autocomplete__Content'>
{
//Custom loop
For(matches, (item, index) => (
//loops through matches
))
}
</div>
)).EndIf()
}
</div>
</div>
);
}
export default Autocomplete;
Basically how it works is as follows:
When something is typed into the input a query string is set:
<input
onChange={ e => setQuery(e.target.value) }
/>
The query state is set immediately with useEffect and updateQuery() is called:
useEffect(() => {
//If input is empty
if(query !== ""){
updateQuery()
}
}, [query])
In updateQuery() all of the menu items in the autocomplete are requested from an API and the matches are set to be looped through and dictate whether a menu is open:
const updateQuery = async () => {
const data = await props.searchQuery(query);
if(data.length > 1 ){
setMenuState(true);
setMatches(data);
} else if (data.length < 1){
setMenuState(false);
setMatches([]);
}
}
The problem I'm having is the matches state lags behind, but if I add it to useEffect I get an infinite loop because updateQuery is always being called:
useEffect(() => {
//If input is empty
if(query !== ""){
updateQuery()
}
}, [query, matches])
How can I make it so matches and the query state are updated at the same time without an infinite loop?
You can put the dependent code INSIDE the useEffect with the call to it.
That way, the function isn't re-declared every render. It only changes when the effect is re-run.
This might be missing an await or async modifier somewhere, but to give the idea.
useEffect(() => {
const updateQuery = async () => {
const data = await props.searchQuery(query);
if(data.length > 1 ){
setMenuState(true);
setMatches(data);
} else if (data.length < 1){
setMenuState(false);
setMatches([]);
}
}
//If input is empty
if(query !== ""){
updateQuery()
}
}, [query])
I am having a slight issue with my react code. I want the data to be completely fetched before rendering. However, I have tried various ways such as 'setGroupLoaded to true' after the async call, but it is still not working. When the component first mounts, 'groupLoaded == false and myGroup == [],', then it goes to the next conditional statement where 'groupLoaded == true' but the myGroup [] is still empty. I was expecting the myGroup [] to be filled with data since groupLoaded is true. Please I need help with it.
function CreateGroup({ currentUser }) {
const [name, setName] = useState("");
const [myGroup, setMyGroup] = useState([]);
const [groupAdded, setGroupAdded] = useState(false);
const [groupLoaded, setGroupLoaded] = useState(false);
const handleChange = (e) => {
const { value, name } = e.target;
setName({
[name]: value,
});
};
useEffect(() => {
const fetchGroupCreated = async () => {
let groupArr = await fetchMyGroup(currentUser);
setMyGroup(groupArr);
};
fetchGroupCreated();
setGroupLoaded(true);
return () => {
setMyGroup([]);
};
}, [currentUser.id, groupAdded, deleteGroupStatus]);
useEffect(() => {
let groupId = myGroup.length ? myGroup[0].id : "";
let groupName = myGroup.length ? myGroup[0].groupName : "";
if (myGroup.length) {
JoinGroup(currentUser, groupId, groupName);
setTimeout(() => fetchGroupMembers(), 3000);
}
}, [myGroup]);
let itemsToRender;
if (groupLoaded && myGroup.length) {
itemsToRender = myGroup.map((item) => {
return <Group key={item.id} item={item} deleteGroup={deleteGroup} />;
});
} else if (groupLoaded && myGroup.length === 0) {
itemsToRender = (
<div>
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="exampleInputTitle">Group Name</label>
<input
type="text"
className="form-control"
name="name"
id="name"
aria-describedby="TitleHelp"
onChange={handleChange}
/>
</div>
<button type="submit" className="btn btn-primary">
Add group{" "}
</button>
</form>
</div>
);
}
return (
<div>
<h3>{currentUser ? currentUser.displayName : ""}</h3>
{itemsToRender}
</div>
);
}
The problem is here:
useEffect(() => {
const fetchGroupCreated = async () => {
let groupArr = await fetchMyGroup(currentUser);
setMyGroup(groupArr);
};
fetchGroupCreated();
setGroupLoaded(true);
return () => {
setMyGroup([]);
};
}, [currentUser.id, groupAdded, deleteGroupStatus]);
When you call setGroupLoaded(true), the first call to setMyGroup(groupArr) hasn't happened yet because fetchMyGroup(currentUser) is asynchronous. If you've never done this before, I highly recommend putting in some logging statements, to see the order in which is executes:
useEffect(() => {
const fetchGroupCreated = async () => {
console.log("Got data")
let groupArr = await fetchMyGroup(currentUser);
setMyGroup(groupArr);
};
console.log("Before starting to load")
fetchGroupCreated();
setGroupLoaded(true);
console.log("After starting to load")
return () => {
setMyGroup([]);
};
}, [currentUser.id, groupAdded, deleteGroupStatus]);
The output will be:
Before starting to load
After starting to load
Got data
This is probably not the order you expected, but explains exactly why you get groupLoaded == true, but the myGroup is still empty.
The solution (as Nick commented) is to move the setGroupLoaded(true) into the callback, so that it runs after the data is retrieved:
const fetchGroupCreated = async () => {
let groupArr = await fetchMyGroup(currentUser);
setMyGroup(groupArr);
setGroupLoaded(true);
};
fetchGroupCreated();
An alternative approach may be to await the call to fetchGroupCreated(). I'm not sure if it'll work, but if it does it'll be simpler:
await fetchGroupCreated();
I have the following React functional component, but the content in curly braces doesn't render, where I am trying to show the user their past actions from the historyStack.
The logic within these curly braces doesn't get executed and I don't understand why.
I haven't used separate components, since I am just testing so far.
Thanks in advance.
import React, {useState, useEffect} from 'react'
//import { getData } from '../shared/api'
const App = () => {
useEffect(() => {
let isMounted = true
fetch('api/config.json')
.then((response) => response.json())
.then((config) => {
isMounted && setInitData(config)
})
return () => { isMounted = false }
}, [])
const [historyStack, setHistoryStack] = useState([])
const [initData, setInitData] = useState({})
// Adds item to the history stack
const handleClick = (item) => {
setHistoryStack(historyStack.concat(item))
}
// Handles back action and removes the last item from the history stack
const handleBack = () => {
const updatedHistory = [...historyStack]
updatedHistory.pop()
setHistoryStack(updatedHistory)
}
// Load the inital data received from the API call
const loadData = () => {
setHistoryStack(historyStack.concat(initData))
}
const latestItem = historyStack[historyStack.length -1]
return (
<div>
<h1>Produktfinder</h1>
{
!latestItem && <button onClick={() => loadData()}>Start</button>
}
{
historyStack && historyStack.map((item, key) => {
<p key={key}>{`${item.label} -->`}</p>
})}
{
latestItem &&
<div>
<h2>{latestItem.label}</h2>
<p>{latestItem.question}</p>
</div>
}
{
latestItem && latestItem.children.map((item, key) =>
<div key={key}>
<button onClick={() => handleClick(item)}>{item.label}</button>
</div>
)
}
{
latestItem && <button onClick={() => handleBack()}>Zurück</button>
}
</div>
)
}
export default App
You're missing return statement in historyStack map. The following should work (given your conditions are right) :-
historyStack && historyStack.map((item, key) => {
return <p key={key}>{`${item.label} -->`}</p>
})
The following will also work (Implicit return):
historyStack && historyStack.map((item, key) =>
<p key={key}>{`${item.label} -->`}</p>
)