How to make state of each react component as independent? - javascript

I am creating a card that shows questions and it makes a GET request to my server and shows the answer to the asked question
The CODE Below of the component shows how the API request is made
const ChatBoxIItem = ({ message }) => {
const [ans, setAns] = useState(null);
getAns(message);
async function getAns(message) {
if (!message) {
return;
}
st_chat_asked_question = message;
let param = {
question: message,
};
param = JSON.stringify(param);
let url;
let res_data;
try {
console.log(param);
let curr_data = new Date();
url = `${process.env.REACT_APP_API_URL}/student/question_answer`;
res_data = await send_xhr.post(param, url, "application/json");
res_data = JSON.parse(res_data);
curr_data = new Date();
let time = curr_data.toLocaleTimeString();
if (
res_data.answers &&
res_data.answers[0] &&
res_data.answers[0].score > 0
) {
st_chat_response_data = res_data;
console.log(res_data);
setAns(res_data.answers[0].answer);
} else {
}
} catch (e) {
display_mess("something went wrong", "error", 5000);
window.scrollTo(0, 100);
console.log(e);
return;
}
}
return (
<div className='st-cs-chat-item'>
<div className='st-cs-chat-item-header'>
<p className='st-cs-chat-item-question'>{message}</p>
<p>{ans ? ans : <p>Searching For Answer</p>}</p>
</div>
</div>
);
};
It's working fine but only for the first component when 2 or more request is made it changes the value of the 1st state also to "Searching for ans"
When 1st request is made
After 1st Request
When 2nd request is made (States of both components changes)
How to fix this issue I don't want to change the states of previous components Need each of them as independent

Related

fetch request with different parameters on every click

Im using an API and I fetch 9 pictures every time from said API. the API can display "pages", meaning if a "page=1" parameter is passed in the request I get the first 9 pictures from the database. "page=2" gives me the next 9 pictures and so on.
I want to have 2 buttons so on every click I get the next 9 pictures. I tried to do so using the API parameter change but haven't had much luck.
I ran across this thread:
Setting query string using Fetch GET request
but either it doesnt help me or I dont understand it enough.
Thanks!
my code is below:
const Home = ()=>{
let pageNumber = 1;
const [photosData, SetPhotosData] = useState([])
const url = `https://pixabay.com/api/?key=25540812-faf2b76d586c1787d2dd02736&per_page=9&page=`
const getImages = async()=>{
try {
const response = await fetch (url+pageNumber)
if (!response.ok) {
throw new Error();
}
const responseObj = await response.json();
const allPhotosData = responseObj.hits
SetPhotosData(allPhotosData)
}
catch (error) {
console.log(error);
}
}
useEffect(()=>{
getImages();
},[])
const getNextPage = ()=> {
pageNumber++;
console.log(pageNumber);
getImages();
}
const getPrevPage =()=>{
pageNumber--;
console.log(pageNumber);
getImages();
}
return(
<div>
<button onClick={getPrevPage}>prev</button>
<button onClick={getNextPage}>next</button>
<br/>
{photosData.map(singlePhotoData=>
<img
key={singlePhotoData.id}
src={singlePhotoData.largeImageURL}
/>)}
</div>
)
}
export default Home;
Your pageNumber will reset to 1 everytime you set a new state with SetPhotosData. You could fix this by using useRef.
const pageNumber = useRef(1);
...
const response = await fetch(url + pageNumber.current);
...
const getNextPage = () => {
pageNumber.current++;
console.log(pageNumber.current);
getImages();
};
const getPrevPage = () => {
if (pageNumber.current < 2) return;
pageNumber.current--;
console.log(pageNumber.current);
getImages();
};
Note that we also check if the pageNumber is not lower than 2 before decreasing it and calling the api to prevent the api from throwing a error, saying it has no page 0

Google Apps Script Working on backend but not on sheets

I am trying to create a script that pulls from the coin market cap API and displays the current price. The script is working fine on the back end when I assign the variable a value. However, when I try to run the function on sheets the returned value is null.
function marketview(ticker) {
var url = "https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?CMC_PRO_API_KEY=XXX&symbol=" + ticker;
var data = UrlFetchApp.fetch(url);
const jsondata = JSON.parse(data);
Logger.log(jsondata.data[ticker].quote['USD'].price)
}
My execution logs show that the scripts are running, but when when I use the function and try and quote ETH for example, the script is running for BTC.
When I do this on the backend and assign ETH the script works fine and returns the right quote. Any ideas on what I'm missing?
I did the same with coingecko API and add an issue having all my requests being rejected with quota exceeded error.
I understood that Google sheets servers IPs address were already spamming coingecko server. (I was obviously not the only one to try this).
This is why I used an external service like apify.com to pull the data and re-expose data over their API.
This is my AppScripts coingecko.gs:
/**
* get latest coingecko market prices dataset
*/
async function GET_COINGECKO_PRICES(key, actor) {
const coinGeckoUrl = `https://api.apify.com/v2/acts/${actor}/runs/last/dataset/items?token=${key}&status=SUCCEEDED`
return ImportJSON(coinGeckoUrl);
}
You need ImportJSON function, available here: https://github.com/bradjasper/ImportJSON/blob/master/ImportJSON.gs
Then in a cell I write: =GET_COINGECKO_PRICES(APIFY_API_KEY,APIFY_COINGECKO_MARKET_PRICES), you will have to create two field named APIFY_API_KEY and APIFY_COINGECKO_MARKET_PRICES in order for this to work.
Then register on apify.com, then you'll have to create an actor by forking apify-webscraper actor.
I set the StartURLs with https://api.coingecko.com/api/v3/coins/list, this will give me the total number of existing crypto (approx 11000 as of today), and number of page so I can run the request concurrently (rate limit is 10 concurrent requests on coingecko), then I just replace /list with /market and set the proper limit to get all the pages I need.
I use the following for the tasks page function:
async function pageFunction(context) {
let marketPrices = [];
const ENABLE_CONCURRENCY_BATCH = true;
const PRICE_CHANGE_PERCENTAGE = ['1h', '24h', '7d'];
const MAX_PAGE_TO_SCRAP = 10;
const MAX_PER_PAGE = 250;
const MAX_CONCURRENCY_BATCH_LIMIT = 10;
await context.WaitFor(5000);
const cryptoList = readJson();
const totalPage = Math.ceil(cryptoList.length / MAX_PER_PAGE);
context.log.info(`[Coingecko total cryptos count: ${cryptoList.length} (${totalPage} pages)]`)
function readJson() {
try {
const preEl = document.querySelector('body > pre');
return JSON.parse(preEl.innerText);
} catch (error) {
throw Error(`Failed to read JSON: ${error.message}`)
}
}
async function loadPage($page) {
try {
const params = {
vs_currency: 'usd',
page: $page,
per_page: MAX_PER_PAGE,
price_change_percentage: PRICE_CHANGE_PERCENTAGE.join(','),
sparkline: true,
}
let pageUrl = `${context.request.url.replace(/\/list$/, '/markets')}?`;
pageUrl += [
`vs_currency=${params.vs_currency}`,
`page=${params.page}`,
`per_page=${params.per_page}`,
`price_change_percentage=${params.price_change_percentage}`,
].join('&');
context.log.info(`GET page ${params.page} URL: ${pageUrl}`);
const page = await fetch(pageUrl).then((response) => response.json());
context.log.info(`Done GET page ${params.page} size ${page.length}`);
marketPrices = [...marketPrices, ...page];
return page
} catch (error) {
throw Error(`Fail to load page ${$page}: ${error.message}`)
}
}
try {
if (ENABLE_CONCURRENCY_BATCH) {
const fetchers = Array.from({ length: totalPage }).map((_, i) => {
const pageIndex = i + 1;
if (pageIndex > MAX_PAGE_TO_SCRAP) {
return null;
}
return () => loadPage(pageIndex);
}).filter(Boolean);
while (fetchers.length) {
await Promise.all(
fetchers.splice(0, MAX_CONCURRENCY_BATCH_LIMIT).map((f) => f())
);
}
} else {
let pageIndex = 1
let page = await loadPage(pageIndex)
while (page.length !== 0 && page <= MAX_PAGE_TO_SCRAP) {
pageIndex += 1
page = await loadPage(pageIndex)
}
}
} catch (error) {
context.log.info(`Fetchers failed: ${error.message}`);
}
context.log.info(`End: Updated ${marketPrices.length} prices for ${cryptoList.length} cryptos`);
const data = marketPrices.sort((a, b) => a.id.toLowerCase() > b.id.toLowerCase() ? 1 : -1);
context.log.info(JSON.stringify(data.find((item) => item.id.toLowerCase() === 'bitcoin')));
function sanitizer(item) {
item.symbol = item.symbol.toUpperCase()
return item;
}
return data.map(sanitizer)
}
I presume you are hiting the same issue I had with coinmarketcap, and that you could do the same with it.
You're not return ing anything to the sheet, but just logging it. Return it:
return jsondata.data[ticker].quote['USD'].price

React Child Component Is Not Rerendering When Props Are Updated

My parent component takes input from a form and the state changes when the value goes out of focus via onBlur.
useEffect(() => {
let duplicate = false;
const findHierarchy = () => {
duplicationSearchParam
.filter(
(object, index) =>
index ===
duplicationSearchParam.findIndex(
(obj) => JSON.stringify(obj.name) === JSON.stringify(object.name)
)
)
.map((element) => {
DuplicateChecker(element.name).then((data) => {
if (data.status > 200) {
element.hierarchy = [];
} else {
element.hierarchy = data;
}
});
if (duplicate) {
} else {
duplicate = element?.hierarchy?.length !== 0;
}
});
return duplicate;
};
let dupe = findHierarchy();
if (dupe) {
setConfirmationProps({
retrievedData: formData,
duplicate: true,
responseHierarchy: [...duplicationSearchParam],
});
} else {
setConfirmationProps({
retrievedData: formData,
duplicate: false,
responseHierarchy: [],
});
}
}, [duplicationSearchParam]);
I have a child component also uses a useeffect hook to check for any state changes of the confirmationProps prop.
the issue is that the event gets triggered onblur, and if the user clicks on the next button. this function gets processes
const next = (data) => {
if (inProgress === true) {
return;
}
inProgress = true;
let countryLabels = [];
formData.addresses?.map((address) => {
fetch(`/api/ref/country/${address?.country}`)
.then((data) => {
countryLabels.push(data.label);
return countryLabels;
})
.then((countries) => {
let clean = MapCleanse(data, countries);
fetch("/api/v1/organization/cleanse", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(clean),
})
.then((data) => {
if (data.status > 200) {
console.log(data.message);
message.error(getErrorCode(data.message.toString()));
} else {
Promise.all([confirmationProps, duplicationSearchParam]).then(
(values) => {
console.log(values);
console.log(data);
setCleansed(data);
**setCurrent(current + 1);**
inProgress = false;
}
);
}
})
.catch((err) => {
console.log(err);
inProgress = false;
});
})
.catch((err) => {
console.log(err);
inProgress = false;
});
});
console.log(confirmationProps);
};
The important part in the above code snippet is the setCurrent(current + 1) as this is what directs our code to render the child component
in the child component, i have a use effect hook that is watching [props.duplicateData.responseHierarchy]
I do output the values of props.duplicateData.responsehierarchy to the console to see if the updated information gets passed to the child component and it does. the values are present.
I have a conditional render statement that looks like this
{cleansedTree?.length > 0 || treeDuplicate ? (...)}
so although the data is present and is processed and massaged in the child component. it still will not re render or display properly. unless the user goes back to the previous screen and proceeds to the next screen again... which forces a re-render of the child component.
I have boiled it down and am assuming that the conditional rendering of the HTML is to blame. Or maybe when the promise resolves and the state gets set for the confirmation props that the data somehow gets lost or the useefect doesn't pick it up.
I have tried the useefect dependency array to contain the props object itself and other properties that arent directly related
UPDATE: this is a code snippet of the processing that gets done in the childs useeffect
useEffect(() => {
console.log(props.duplicate);
console.log(props.duplicateData);
console.log(props.confirmationProps);
let newArray = props.duplicateData.filter((value) => value);
let duplicateCheck = newArray.map((checker) =>
checker?.hierarchy?.find((Qstring) =>
Qstring?.highlightedId?.includes(UUIDToString(props?.rawEdit?.id))
)
);
duplicateCheck = duplicateCheck.filter((value) => value);
console.log(newArray, "new array");
console.log(duplicateCheck, "duplicate check");
if (newArray?.length > 0 && duplicateCheck?.length === 0) {
let list = [];
newArray.map((dupeData) => {
if (dupeData !== []) {
let clean = dupeData.hierarchy?.filter(
(hierarchy) => !hierarchy.queryString
);
let queryParam = dupeData.hierarchy?.filter(
(hierarchy) => hierarchy.queryString
);
setSelectedKeys([queryParam?.[0]?.highlightedId]);
let treeNode = {};
if (clean?.length > 0) {
console.log("clean", clean);
Object.keys(clean).map(function (key) {
treeNode = buildDuplicate(clean[key]);
list.push(treeNode);
return list;
});
setCleansedTree([...list]);
setTreeDuplicate(true);
} else {
setTreeDuplicate(false);
}
}
});
}
}, [props.duplicateData.responseHierarchy]);
This is a decently complex bit of code to noodle through, but you did say that **setCurrent(current + 1);** is quite important. This pattern isn't effectively handling state the way you think it is...
setCurrent(prevCurrent => prevCurrent + 1)
if you did this
(count === 3)
setCount(count + 1) 4
setCount(count + 1) 4
setCount(count + 1) 4
You'd think you'd be manipulating count 3 times, but you wouldn't.
Not saying this is your answer, but this is a quick test to see if anything changes.
The issue with this problem was that the state was getting set before the promise was resolved. to solve this issue I added a promise.all function inside of my map and then proceeded to set the state.
What was confusing me was that in the console it was displaying the data as it should. but in fact, as I learned, the console isn't as reliable as you think. if someone runs into a similar issue make sure to console the object by getting the keys. this will return the true state of the object, and solve a lot of headache

Get output from Alteryx API Query

I have been playing with trying to set up a javascrip app able to run and display the outputs of an Alteryx analytic app hosted on my server. The idea is then to embed it in Tableau server.
I have got something set up which was able to run an app from a user input and show its status.
But fetching the output of the job is a bit more tricky and I can't get my head around it.
It seems I should use getOutputFileURL() function to get what I want but I am really unsure about how I should set that up.
I tried building a function getOutputs() line 54 but it is not giving me anything.
Anyone who had a similar experience would mind giving me a hand here?
Thank you
'''`// Importing the modules & the keys
// polyfilling the extensions browser
import "babel-polyfill";
import keys from "./keys";
import Gallery from "alteryx-subscription-api";
import tableauJS from "./tableau";
// Grab the Run Button
const btn = document.getElementById("btn");
// Grab the input box
const input = document.getElementById("peopleSearch");
// Grab the message box
const inputValue = document.getElementById("message");
// Grab the spinner gif
const loading = document.getElementById("spinner");
// Grab the status paragraph
const statusInfo = document.getElementById("status");
// Grab the output div
const dataPrint = document.getElementById("dataOutput");
// load into the gallery based on the values specified in the keys file
const createGallery = () => {
const gallery = new Gallery(keys.apilocation, keys.apikey, keys.apisecret);
return gallery;
};
async function jobs(jobId) {
const response = await createGallery().getJob(jobId);
const data = await response.json();
if (data.status !== "Completed") {
// if workflow hasn't been completed, keep checking the jobID for its status
jobs(jobId);
if (inputValue.style.display == "block") {
inputValue.style.display = "none";
}
loading.style.display = "inline";
// if error please report
} else if (data.status === "Error") {
console.error(data.status);
const status = data.status;
statusInfo.innerHTML = status;
} else {
// if finished display completed message
loading.style.display = "none";
const status = data.status;
statusInfo.innerHTML = status;
statusInfo.style.display = "inline";
// after 4 seconds remove the input and refresh the DS
removeElement();
}
}
// gets output from the job
async function getOutputs(jobId) {
const response = await createGallery().getJob(jobId);
const data = await response.json();
console.log(data);
for (var i = 0; i < data.length; i++) {
var url = galleryOutput.getOutputFileURL(jobId, data.id, "Raw");
dataPrint.innerHTML == url;
dataPrint.style.display = "inline";
}
}
// on click of buton run the workflow/app specified in the keys file
async function runWorkflow(appId, dataArray) {
const response = await createGallery().executeWorkflow(appId, dataArray);
const data = await response.json();
console.log(data);
// check if the job is running and if finished
jobs(data.id);
getOutputs(data.id);
}
// If you click run, first check if search input has been specified, then it grabs the appID from the keys file
btn.addEventListener("click", () => {
const peopleSearch = input.value;
const appId = keys.appId;
if (!peopleSearch) {
inputValue.style.display = "block";
} else {
inputValue.style.display = "none";
statusInfo.style.display = "none";
const dataArray = [
{
name: "Text Box (414)",
value: peopleSearch
}
];
runWorkflow(appId, dataArray);
}
});
//removes the messages and the search input after 4 seconds and refresh the data source
function removeElement() {
setTimeout(() => {
if ((statusInfo.style.display = "inline")) {
statusInfo.style.display = "none";
input.value = "";
tableauJS.refreshDS();
}
}, 6000);
}
`
A more efficient process would be use Output tool in Alteryx to write to a database or .TDE tableau data file, then ingest into Tableau properly..

Do not understand Uncaught Typerror Cannot Read Property of 0 Undefinded?

Creating my first ReactJS Website and using Node in the back-end, currently in the code that follows I fetch data that I then print on the page. I manage to print the names of the people in a project, their picture and their email from the server BUT the description of the project i get the error :
TypeError: Cannot read property '0' of undefined
Which I do not understand.
Here is the code :
class ProjectPage extends Component {
constructor(props) {
super(props);
this.state = {
user: [],
description: [],
mail: [],
name: [],
};
this.getNames = this.getNames.bind(this);
this.getPictures = this.getPictures.bind(this);
this.getMails = this.getMails.bind(this);
this.getDetails = this.getDetails.bind(this);
}
I create the class and all the elements that are required
componentDidMount() {
console.log("BEGIN componentDidMount");
this.fetchDetails();
this.fetchNames();
this.fetchMails();
console.log("END componentDidMount");
}
Call all the function in my componentDidMount()
fetchDetails() {
console.log("BEGIN fetchDetails()");
let url = 'http://192.168.1.33:8080/getprojectdetails/Aprite';
console.log("url details = " + url);
fetch(url)
.then(results => {
var json = results.json();
return json;
})
.then(data => {
this.setState({ description: data });
})
console.log("END fetchData()");
}
Here is the fetch of the project description
getDetails = () => {
let lines = [];
let nbr = this.state.description.length;
console.log("nbr = " + nbr);
if (nbr){
console.log("lines = " + this.state.description[0].P_Description);
for (let i = 0; i < nbr; i++)
lines.push(<div key={this.state.descripton[i].P_Description}></div>);
}
return (lines);
}
And the function to print the data in the Render() function
But when i try to print this data, the value of nbr passes from 0 to 1 then to 0 again... in the console log I can see the description but it doesn't appear on the website and I don't get it.
Please help me ?
There is a typo in the inner loop inside the getDetails function
You should write this.state.description not this.state.descripton
Hope this solves your problem :)
So with the React render lifecycle system, the componentDidMount will actually happen after the first render. During that first render, you're trying to access the first element of an empty array, which is the error you are seeing.
In order to solve this problem, in your render method, you should have a fallback something to render while we wait for the fetchDetails to return a value from the server. If you want it to render nothing, you can just return null.
ie.
const { description = [] } = this.state;
if (description.length === 0) {
return null;
}
return this.getDetails();
As a side note, in order to avoid having all of those (which gets pretty unmaintainable):
this.getNames = this.getNames.bind(this);
this.getPictures = this.getPictures.bind(this);
this.getMails = this.getMails.bind(this);
this.getDetails = this.getDetails.bind(this);
You can just define them as class properties like so:
getNames = () => {
// do stuff
}

Categories