Multiple concurrent axios requests on React - reactjs

I have a situation that I can not solve for some reason.
Let me explain what I'm trying to do:
I am trying to get number of crypto tokens received by entering a contract and an address (which helps me to reach out the API results).
For the moment, it works like this: https://thecryptoguetter.netlify.app/pages/HowMuchGlobal
So as you can see, I ask the user to chose a network (because API endpoints are different but they are all structured the same way)
My goal: Allowing users to simply enter a contract and an address, and I'll manage to get the right answer on my side.
What do I need:
I need to make multiple requests and get the answer of the one which works. For that, this is quite easy on the paper, the message in the API is "OK" if it's ok, and "NOTOK" if not. Simple ahah
But for some reason, I'm drowning with the logic, and tried multiple things but my brain is bugging hard on this one.
Here is what I did:
import axios from "axios";
const HowMuchTest = () => {
const [tokenContract, setTokenContract] = useState("");
const [address, setAddress] = useState("");
const [answerAPI, setAnswerAPI] = useState([]);
const [total, setTotal] = useState([]);
const [decimals, setDecimals] = useState(18);
const [txDatasArray, setTxDatasArray] = useState();
const handleTokenContract = (event) => {
const searchTokenContract = event.target.value;
setTokenContract(searchTokenContract);
};
const handleAddress = (event) => {
const searchAddress = event.target.value;
setAddress(searchAddress);
};
const clearToken = () => {
setTokenContract("");
setAddress("");
setTotal([]);
};
useEffect(() => {
axios
.all([
axios.get(
`https://api.bscscan.com/api?module=account&action=tokentx&contractaddress=${tokenContract}&address=${address}&page=1&offset=100&startblock=0&endblock=27025780&sort=asc&apikey=[KEY]`
),
axios.get(
`https://api.etherscan.io/api?module=account&action=tokentx&contractaddress=${tokenContract}&address=${address}&page=1&offset=100&startblock=0&endblock=27025780&sort=asc&apikey=[KEY]`
),
])
.then(
axios.spread((...res) => {
setAnswerAPI(res);
})
);
}, [address, tokenContract]);
console.log(answerAPI[0].data);
useEffect(
(answerAPI) => {
if (answerAPI[0].data.message === "OK") {
setTxDatasArray(answerAPI[0]);
} else if (answerAPI[1].data.message === "OK") {
setTxDatasArray(answerAPI[1]);
}
},
[answerAPI]
);
console.log(txDatasArray);
useEffect(() => {
let dataArray = [];
for (let i = 0; i < answerAPI.length; i++) {
let toLower = answerAPI[i]?.to?.toLowerCase();
let addressLower = address?.toLowerCase();
if (toLower === addressLower) {
dataArray.push({
ticker: answerAPI[0].tokenSymbol,
value: Number(answerAPI[i].value),
});
}
}
setDecimals(Number(answerAPI[0]?.tokenDecimal));
setTotal(dataArray);
}, [address, answerAPI, decimals]);
const totaltotal = total.reduce((a, v) => (a = a + v.value), 0);
const FinalTotal = () => {
if (decimals === 6) {
return (
<div>
{(totaltotal / 1000000).toFixed(2).toLocaleString()} {total[0].ticker}
</div>
);
} else if (decimals === 18) {
return (
<div>
{(totaltotal / 1000000000000000000).toFixed(2).toLocaleString()}{" "}
{total[0].ticker}
</div>
);
}
};
return (
<div className="how-much">
<div className="received-intro">
<h1>Find out how much did you receive on a particular token</h1>
</div>
<div className="token-contract">
<input
type="text"
placeholder="Token Contract Address"
value={tokenContract}
onChange={handleTokenContract}
/>
<input
type="text"
placeholder="ERC20 Address"
value={address}
onChange={handleAddress}
/>
</div>
{totaltotal !== 0 && (
<div className="show-total-received">
<div className="total-received">
<FinalTotal />
</div>
<div>{total.ticker}</div>
</div>
)}
<button className="btn btn-info" onClick={clearToken}>
Clear
</button>
</div>
);
};
export default HowMuchTest;
The part to look is the two first useEffect.
I get the following answer from the console: Cannot read properties of undefined (reading 'data')
Which is weird because I can reach answerAPI[0].data in the first console.log
So yes, I'm totally lost and can't figure it out. I tried a for loop in a useEffect too but it ended in an infinite loop...
Thank you in advance for the ones who will read until there!
Have a good day

The problem is in your useEffect implementation. The function passed to useEffect does not take any arguments, so answerAPI as written will always be undefined, because inside this function it is the non-existing argument, and not your state as you might have expected.
You can change it to:
useEffect(() => {
if (answerAPI[0]?.data?.message === "OK") {
setTxDatasArray(answerAPI[0]);
} else if (answerAPI[1]?.data?.message === "OK") {
setTxDatasArray(answerAPI[1]);
}
}, [answerAPI]);

Related

How to prepending before icon for text area for React component

I'm trying to implement React version of TextArea which appends "$" every-time I press Return/Enter.
I'm having hard time prepending a sign # or % or dollar every time someone presses enter. How can I go about this?
This is my basic attempt but I'm kind of lost:
const MyComponent = () => {
const [name, setName] = React.useState('');
return (
<div>
<textarea value={name} onChange={(e) => { setName(e.target.value) }} />
</div>
);
}
ReactDOM.render(<MyComponent />, document.getElementById('root'));
Ok so I had a bit of time on my hands and thought this could be a good learning experience. So I present to you: MoneyInputList
import './css/MoneyInputList.css'
import { useState, useEffect } from 'react';
let latestAdded = 'moneyInputList-input-0';
let lastSize = 0;
const MoneyInputList = () => {
const [recordList, setRecordList] = useState({data: ['']});
const handleFormSubmit = (e) => {
e.preventDefault();
if(recordList.data[recordList.data.length-1] !== ''){
setRecordList({data: [...recordList.data, '']})
}
};
useEffect(() => {
if(lastSize !== recordList.data.length)
document.getElementById(latestAdded).focus();
lastSize = recordList.data.length;
}, [recordList]);
return (
<form autoComplete='off' onSubmit={handleFormSubmit}>
<div className="main-container">
{recordList.data.length > 0 &&
recordList.data.map((record, iter) => {
latestAdded = "moneyInputList-input-"+iter;
return (
<div key={"moneyInputList-field-"+iter} className="record-field">
<div className="record-sign">$</div>
<input className="record-input" id={"moneyInputList-input-"+iter} value={recordList.data[iter]} onChange={(e) => {
if(e.target.value === '' && iter !== recordList.data.length-1){
let modifiedData = [];
recordList.data.forEach((e,i) => {
if(i !== iter)
modifiedData.push(e);
});
setRecordList({data: modifiedData});
return;
}
const filteredValue = e.target.value.split('').filter(e=>(e.charCodeAt() >= '0'.charCodeAt() && e.charCodeAt() <= '9'.charCodeAt()));
let formattedValue = [];
filteredValue.forEach((elem, i) => {
if((filteredValue.length - i) % 3 === 0 && i !== 0)
formattedValue.push(',');
formattedValue.push(elem);
});
formattedValue = formattedValue.join('');
e.target.value = formattedValue;
let myData = recordList.data;
myData[iter] = e.target.value;
setRecordList({data: myData});
}} type="text"/>
</div>
)})
}
</div>
<input style={{flex: 0, visibility: 'collapse', height: 0, width: 0, padding: 0, margin: 0}} type="submit"/>
</form>
)
}
export default MoneyInputList;
This component should do what you need it to do. It is not the best code but it works. You can see it working here. Of course you might still need to change some stuff in order for it to fit in your codebase and maybe implement redux, but the general idea is here. You use it by typing whatever number you want pressing enter will create a new line and deleting the content of a line will remove it.
I hope I understood correctly what you are trying to do, here is a super scuffed version of it. You might need to change the code a bit to fit your use case.
import { useState, useEffect } from "react";
export default function App() {
const [textValue, setTextValue] = useState("");
const [displayedTextValue, setDisplayedTextValue] = useState("");
useEffect(() => {
let splitTextValue = textValue.split("\n");
splitTextValue = splitTextValue.map((line, iter) => {
if (iter === splitTextValue.length - 1) return line;
if (line[0] !== "$") return "$ " + line;
return line;
});
setDisplayedTextValue(splitTextValue.join("\n"));
}, [textValue]);
return (
<div>
<textarea
value={displayedTextValue}
onChange={(e) => {
setTextValue(e.target.value);
}}
/>
</div>
);
}
Here is a version working with key event that I think is cleaner when handling thing depending on keys.
Here is the repro on Stackblitz and here is the code :
import React from 'react';
import { render } from 'react-dom';
const App = () => {
const enterKeyCode = 'Enter';
const backspaceKeyCode = 'Backspace';
const [val, setVal] = React.useState('$ ');
const [keyCodeEvent, setKeyCodeEvent] = React.useState();
React.useEffect(() => {
if (keyCodeEvent) {
// Handle numpad 'enter' key
if (keyCodeEvent[0].includes(enterKeyCode)) {
setVal(`${val}\n$ `);
} else if (keyCodeEvent[0] === backspaceKeyCode) {
setVal(`${val.slice(0, -1)}`);
} else {
setVal(`${val}${keyCodeEvent[1]}`);
}
}
}, [keyCodeEvent]);
return (
<div>
{/* Empty onChange to prevent warning in console */}
<textarea onKeyDown={e => setKeyCodeEvent([e.code, e.key])} value={val} onChange={() => {}} />
</div>
);
};
render(<App />, document.getElementById('root'));
I read your comments on Marko Borković 's answer so I handled the backspace but he is totally right when saying you should build a special component for this. It will be way easier to improve and cleaner. You are not safe from some others bugs if you want to add features to your component.

not able to render api results in react

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

Fetch data before component render, Reactjs

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();

Why my hook state doesn´t update correctly?

i don't know how make this guys, i can't update my state with the api array, and if i put it in useEffect i have an error cause i am not sending any data, help me please is my first time using stackoverflow
import React, { useEffect, useState } from "react";
import getTeam from "../Helpers/getTeam";
const selectTeams = [
"Barcelona",
"Real Madrid",
"Juventus",
"Milan",
"Liverpool",
"Arsenal",
];
const Select = () => {
const [team, setTeam] = useState(null);
const [loading, setLoading] = useState(null);
const handleOption = async (e) => {
setLoading(true);
let teamsJson = await getTeam(e.target.value);
let arr = [];
Object.keys(teamsJson).map((teamjs, i) => {
return arr.push(teamsJson[teamjs]);
});
console.log(arr);
console.log(team);
setTeam(arr);
setLoading(false);
};
return (
<div
style={{ background: "skyblue", textAlign: "center", padding: "20px" }}
>
<h1>Equipos Disponibles</h1>
<div>
<select onChange={handleOption}>
<option>Elige tu equipo</option>
{selectTeams.map((selectTeam, i) => {
return <option key={i}>{selectTeam}</option>;
})}
</select>
</div>
{loading ? <h1>suave</h1> : (
team !== null ? (
team.map((newTeam, i) => {
return (
<div>
the items are here
</div>
)
})
) : null
)}
</div>
);
};
export default Select;
i let you my api file down
const getTeam = async (teamName) => {
const url = `https://www.thesportsdb.com/api/v1/json/1/searchteams.php?t=${teamName}`;
const res = await fetch(url);
const team = await res.json();
return team;
};
export default getTeam;
i wanna update my const team with the response of my api call, but it doesn't update it, i dont know what do, please help me
The teamsJson value is an object with a single key and value of some array
{ teams: [...] }
So you are updating your state with a nested array when you push the value into another array.
let arr = [];
Object.keys(teamsJson).map((teamjs, i) => {
return arr.push(teamsJson[teamjs]);
});
Based upon how you want to map your team state array I assume you just want the raw inner array from teamJson.
const { teams } = await getTeam(e.target.value);
setTeam(teams);
Then when you are mapping you can access any of the properties you need.
team.map((newTeam, i) => {
return <div key={i}>{newTeam.idTeam}</div>;
})
I've just tested it & it seems to works just fine.
The only 2 issues seem to be that:
You don't use team anywhere (apart from a console.log statement).
At the moment when you console.log(team); the constant team will (yet) be null for the first time (because it still keeps the initial state).
Here's what I see in React dev tools after picking a random team in the <select>:

My code is showing error when search is not found with React

Please am new to React working with API, when ever the search object is not found, it returned error! Please help me.
I have tried using techniques that I know but it's not working.
What did i need to do or add to make it work and return non-error when search is not returned.
function Search() {
const[searchQuery, setSearchQuery] = useState('');
const[advices, setAdvices] = useState({ slips: [] });
const text = 'Tweet';
const shareText = 'Check https://adviser.surg.sh for more...';
const onInputChange = (e) => {
setSearchQuery(e.target.value);
}
let API_URL = `https://api.adviceslip.com/advice/search/`;
const fetchAdvices = async () => {
const result = await axios.get(`${API_URL}${searchQuery}`);
console.log(result.data);
setAdvices(result.data);
}
const onSubmitHandler = (e) => {
e.preventDefault();
fetchAdvices();
}
return (
<div>
<section>
<form className='search-box' onSubmit={onSubmitHandler}>
<input
type='search'
placeholder='Search for advice e.g love'
value={searchQuery}
onChange={onInputChange}
/>
<button className='search-btn' type='submit'>Search</button>
</form>
<div className='adviceList'>
{
advices.slips.map((advice, id) => {
return (
<div key={id}>
<div className='advice-card'>
<h2>{advice.advice}</h2>
<p> <Twitter text={text}
url={advice.advice}
shareText={shareText} />
</p>
</div>
</div>
);
})
}
</div>
</section>
</div>
)
}
export default Search
How can i solve this, please help me with what to do.
The most optimal solution is to have the backend to return some sort of data even when the result is not found (ex. a found property). Then, use that data to check if there is a result or not. Otherwise, you'd have to do error handling:
const fetchAdvices = async () => {
try {
const result = await axios.get(`${API_URL}${searchQuery}`);
console.log(result.data);
setAdvices(result.data);
} catch(e) {
setAdvices({slips: []}); //or any other value you want to set it to when there's no result returned
}
}
Rather than using await, try this;
const fetchAdvices = axios.get(`${API_URL}${searchQuery}`)
.then(result => {
console.log(result.data);
setAdvices(result.data);
}).catch(error => {
console.log(error);
// handleError(error);
});

Resources