i wanna render state after i updated state with another response from api
on component did mount i make a request from random user and i store it in state, and i made a button which on click makes another request and stores another user from api in state , my problem is i cant seem to render the new state ( updated after click on button ) on console.log i see the updated state , but when i try to render , i get only the initial value , the rest appear as undefined
state = {
persons : []
}
async componentDidMount( ){
let response = await axios(`https://randomuser.me/api/?results=1`)
this.setState({
persons: response.data.results
})
}
update = async () => {
const response = await axios(`https://randomuser.me/api/?results=1`)
this.setState(prevState => ({
persons: [...prevState.persons, response.data.results]
}))
}
render(){
const test = this.state.persons.map( i => i.cell)
return(
<div>
{test}
<button onClick={this.update}>update</button>
</div>
)
}
You need to set the correct state. the response is an array, so you need to merge both arrays
update = async () => {
const response = await axios(`https://randomuser.me/api/?results=1`)
this.setState(prevState => ({
persons: [...prevState.persons, ...response.data.results]
}))
}
This should work
render(){
const test = this.state.persons.map( i => {
return(
<div>{i.cell}</div>
)
})
return(
<div>
{test}
<button onClick={this.update}>update</button>
</div>
)
}
Related
Is it possible to have an api constantly being called (in a loop until asked to stop) or have react automatically change state if the api changes?
I currently have a backend (/logs) that will send out logs in an array ex: [{log:"test"}, {log:"test1"}].
I have react able to pick that up and update state to display those logs on the front end when a button is clicked
axios.get("/logs").then(response =>{
let templog = response.data
let newLogs = [...logs]
for (let i = 0; i < templog.length; i++) {
if (templog[i].log !== "") {
newLogs.push({log: templog[i].log, id: uuidv4()})
}
}
setLogs(newLogs)
})
Right now, if I update the backend, I would have to reclick the button for the state to update rather than the state automatically updating based on the api
try setInterval in useEffect, also return clearInterval at end of useEffect
import React from 'react'
const ScheduleUpdate = (props) => {
const [logs, setLogs] = React.useState([])
const [run, setRun] = React.useState(true)
React.useEffect(() => {
const getLogs = () => {
fetch("/logs")
.then(r => r.json())
.then(d => { setLogs(d) })
}
if (run){
const handle = setInterval(getLogs, 1000);
return () => clearInterval(handle);
}
}, [run])
return (
<div>
<h1>ScheduleUpdate</h1>
{
logs.map(l => <div>{l}</div>)
}
<button onClick={() => setRun(false)}>Stop</button>
</div>
)
}
export default ScheduleUpdate
I have two Components which are functional. The first one is Parent and the second one is Child and on Parent I have some states and a function which retrieve some data from server and base on it, setData is called then this state is passed to Child to be shown.
The problem is when I first click on a button to trigger that function, state does not change but on second click state is changed and data is shown on Child component.
My Parent is like:
const Parent= () => {
const [data, setData] = useState();
const handleShowDetails = (id) => {
const mainData = data
MyServices.GetData(
command,
(res) => {
mainData = res.name;
}
);
setData(mainData);
}
}
return (
<Child
data={data}
handleShowDetails={handleShowDetails}
/>
);
And my Child is like:
const Child= ({data, handleShowDetails}) => {
return(
<div>
<button onClick={() => handleShowDetails()}/>
{typeof data!== "undefined" ? (
<div className="col-1">
{data}
</div>
) : ("")
</div>
)
}
What can I do to fix this issue?
Note: I implemented MyServices for handling API calls by Axios.
You are setting state even before your GetData call is completed, which must be asynchronous. So setData is called with the old value of data.
Try this:
const handleShowDetails = (id) => {
const mainData = data
MyServices.GetData(
command,
(res) => {
mainData = res.name;
setData(mainData);
}
);
}
Or use await pattern:
const handleShowDetails = async (id) => {
const mainData = data
const ans = await MyServices.GetData(command);
setData(ans.name);
);
}
Note: No need to use mainData i guess.
I am new to react and firebase/firestore.
I am trying to map into what I believe to be a nested firestore value. I am able to pull each value individually
function Pull() {
const [blogs,setBlogs]=useState([])
const fetchBlogs=async()=>{
const response=firestore.collection('customer');
const data= await response.get();
data.docs.forEach(item=>{
setBlogs(data.docs.map(d => d.data()))
console.log(data)
})
}
useEffect(() => {
fetchBlogs();
}, [])
return (
<div className="App">
{
blogs.map((items)=>(
<div>
<p>{items[1].name}</p>
</div>
))
}
</div>
);
}
I have been trying to map twice to get into the string inside the collection, yet I have had no luck.
My FireStore collection
https://drive.google.com/file/d/1Erfi2CVrBSbWocQXGR5PB_ozgg9KEu12/view?usp=sharing
Thank you for your time!
If you are iterating a data.docs array and enqueueing multiple state updates then you will want to use a functional state update to correctly enqueue, and update from the previous state.
const fetchBlogs = async ( )=> {
const response = firestore.collection('customer');
const data = await response.get();
data.docs.forEach(item => {
setBlogs(blogs => blogs.concat(item.data()))
});
}
or you can map the data.docs to an array of items and update state once.
const fetchBlogs = async ( )=> {
const response = firestore.collection('customer');
const data = await response.get();
setBlogs(blogs => blogs.concat(data.docs.map(item => item.data())));
}
try changing the foreach to a snapshot like this:
data.docs.onSnapshot(snapshot=>{
setBlogs(snapshot.docs.map(d => d.data()))
console.log(data)
})
ive used it like this in the past multiple times
and it has worked. If it doesnt work, the instagram clone tutorial on youtube by Clever Programmer goes over this well.
I'm using React Hooks. I set the state property questions after an axios fetch call. Now when I click a button, in its function questions state is still empty
const [questions, setQuestions] = useState([]);
const [customComponent, setCustomComponent] = useState(<div />);
useEffect(() => {
axios.get("urlhere").then(res => {
console.log(12, res.data);
setQuestions(res.data);
res.data.map(q => {
if (q.qualifyingQuestionId == 1) {
setCustomComponent(renderSteps(q, q.qualifyingQuestionId));
}
});
});
}, []);
const handleNext = i => {
console.log(32, questions); //questions is still an empty array here
};
const renderSteps = (step, i) => {
switch (step.controlTypeName) {
case "textbox":
return (
<div key={i}>
<input type="text" placeholder={step.content} />
<button onClick={() => handleNext(i)}>Next</button>
</div>
);
}
};
return <>{customComponent}</>;
Do I need to use reducers here and put the custom component in another "file"?
setQuestions does not update state immediately, you should use the prevState instead to access the new value.
Here's a sandbox to match your codes with some explanation on why it was empty > https://codesandbox.io/s/axios-useeffect-kdgnw
You can also read about it here: Why calling react setState method doesn't mutate the state immediately?
Finally I have my own solution
I passed down the data from the fetch function to another component as props
useEffect(() => {
axios.get('url')
.then((data) => {
setCustomComponent(<Questions questions={data} />)
})
}, [])
I'm trying to learn React Hooks by building a simple Domain Availability checker. Specifically, I'm playing with useState()
The aim is just to have an input field where the user types a keyword, hits Enter, and then the app will run a fetch request for that keyword with a number of different domain endings.
Here is my App component (or check codesandbox here: https://codesandbox.io/s/5410rkrq1p)
const App = () => {
const domainEndings = [
".ws",
".ga",
".cf",
".tk",
".ml",
".gq",
".kz",
".st",
".fm",
".je"
];
const [domainString, setDomainString] = useState("");
const [domainsArray, setDomainsArray] = useState([]);
const [lookedUpDomainsArray, setLookedUpDomainsArray] = useState([]);
const handleDomainChange = event => {
setDomainString(event.target.value);
setDomainsArray(
domainEndings.map(ending => event.target.value.trim() + ending)
);
};
let testArray = [];
const runDomainLookup = url => {
return fetch(
`https://domainr.p.rapidapi.com/v2/status?domain=${url}&mashape-key=${myAPIKEY}`
)
.then(res => res.json())
.then(data => {
testArray.push({
url: data.status[0].domain,
status: data.status[0].status
});
setLookedUpDomainsArray(testArray);
});
};
const handleSubmit = e => {
e.preventDefault();
setLookedUpDomainsArray([]);
testArray = [];
domainsArray.map(eachDomain => runDomainLookup(eachDomain));
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
value={domainString}
placeholder="type keyword then hit enter"
onChange={e => handleDomainChange(e)}
/>
</form>
{lookedUpDomainsArray &&
lookedUpDomainsArray.map((domain, index) => {
return (
<div key={index}>
{domain.url} is {domain.status}
</div>
);
})}
</div>
);
};
The bug that I'm experiencing is:
the state seems to be being set correctly (checked in React Dev Tools).
the first response from the mapped fetch requests is rendered correctly to the DOM
a re-render for the newly added state (from fetch request) is not triggered until the user presses a key in the input field
Here is a video demonstrating it : https://streamable.com/klshu
You will notice that the rest of the results don't appear until I start typing other characters into the input
Thanks in advance 🙏
Thanks! Yes I had tried Promise.all() however I wanted each fetch request to run separately (for example, if one fetch request times out, it would hold up ALL of the others).
Found a solution on Reddit
Essentially, when setting the state via Hooks, I should have passed in the previous state as so:
const runDomainLookup = url => {
return fetch(
`https://domainr.p.rapidapi.com/v2/status?domain=${url}&mashape-key=${myAPIKEY}`
)
.then(res => res.json())
.then(data => {
setLookedUpDomainsArray(prevArray => [
...prevArray,
{
url: data.status[0].domain,
status: data.status[0].status
}
]);
});
};
Working solution on CodeSandbox: https://codesandbox.io/s/51ky3r63x