How to extract data from axios GET request object in React? - reactjs

Currently I've been pulling data from an API using axios and the useEffect Hook in React. This is done by having the user enter a value into the code field which will be used to make a request to the API. However, when it comes to extracting the data and getting it to display on the page I've been having issues. Normally, I would use data.map() to map the array data to different fields, but when pulling from this API I am getting a project object which I've been having issues mapping to fields for displaying on the application. In addition, I've also noticed that the useEffect hook is constantly being called over and over in the console, and I dunno how to make it be called only once as well. If anyone could help with either of these issues that would be greatly appreciated!
home.js
import React, { useState, useEffect } from 'react';
import axios from "axios";
import './Home.css'; // Import styling
function Home() {
const [data, setData] = useState([])
const [state, setState] = useState({
code: ""
})
const handleChange = e => setState(prevState => ({ ...prevState, [e.target.name]: e.target.value }))
useEffect(() => {
axios.get(baseURL)
.then((response) => {
setData(response.data);
console.log(data)
});
});
return (
<div className="form-container">
<h1>axios-test</h1>
<div className="form-group">
<label>Code</label>
<input type="text" className="form-control" name="code" value={state.code} onChange={handleChange}/>
</div>
<div className="form-group">
<label>Lead</label>
<input type="text" className="form-control" name="Lead" value={data.map(data => data.project.primaryLeaderName)} onChange={handleChange} disabled/>
</div>
</div>
)
}
export default Home;
console output

I would suggest adding the dependency array [] in the useEffect and also use useEffect hook to call a function =>
const fetchData = async ()=>{
const {data} = await axios.get('url')
// we await the response and destruct it to get only the data
setState(data)
}
useEffect(()=>{
fetchData()
},[])

There are two issues with your useEffect call.
You are missing the dependency array. Check out the documentation . You should add [] as the second argument to useEffect if you only want this to run the first time the component mounts.
console.log(data). You are expecting data to have the new value, but this is not guaranteed by React. The call to setData updates data, but you won't be able to access the new value inside of the same useEffect call. In this case, I don't think you need to though. It's worth pointing out as that's a common gotcha with learning useEffect
Finally, there's a slight issue in this section, based on the screenshot of the data:
value={data.map(data => data.project.primaryLeaderName)}
Check out the documentation on Array.prototype.map. data does not have a project key. In fact, you're mapping projects! The best naming convention is to name the element in the function after what it represents. Each element in data is a project. So:
value={data.map(project => project.primaryLeaderName)}
EDIT:
Seems I misread the console output. Looks like your response looks like this:
{project: [//...Array of projects]
So try this:
value={data.project.map(proj => proj.primaryLeaderName)}

Related

React hooks not showing correct data when component is rendered

I have created a hook in a component as below.
useEffect(() => {
axios
.get("http://127.0.0.1:5000/v1/matches")
.then((response) => {
getStatusCode(response.data.code);
console.log("responseCode",responseCode);
getMatchdata(response.data.result);
setInfo(<MatchData responseCode={responseCode} matchdata={matchdata} />);
})
.catch((error) => console.log(error));
},[]);
This is a state function used in the above effect
const [info, setInfo] = useState();
I expected the above useEffect should return me some data in the below block
<div> {info} </div>
but it is showing wrong data, whereas I have created another function to trigger on Refresh button as
function refresh() {
setInfo(<MatchData responseCode={responseCode} matchdata={matchdata} />);
}
this function is returning me correct data. I want to create a functionality that will dynamically update the div element with change in state of {info}, by default when the page is loaded first, it should fetch data from the endpoint used here only. I'm new to React. Where I'm going wrong and how do I achieve it?
I don't want to say this is wrong, but this seems like an atypical approach from what I've seen in the wild. Specifically I am talking about storing a JS/JSX or TS/TSX element in a state object. I have more commonly seen a value stored in that type of variable and that value changing when necessary via the set dispatch function. Then the state object is passed to the component who needs it to do something. In react, when the value of that state object changes, it will cause the component who uses it to re-render. If I were coding this, this is what my code would look like.
const [info, setInfo] = useState();
const getData = () => {
axios
.get("http://127.0.0.1:5000/v1/matches")
.then((response) => {
setInfo(response.json())
})
.catch((error) => console.log(error));
}
const divComponent = ({info}) => (
<div>
<p>{info.data.code}</p>
<p>{info.data.result}</p>
</div>
)
const refreshButton = () => (
<button onClick(()=>getData())>Refresh</button>
)
Unless you only specifically want something to happen once at component mount, you would not use useEffect() like you did in your code. If the decision to refresh were coming from an external object with state instead of the refresh button, you could add that object whose state changes to the dependency array of the useEffect function. This would cause the refresh to run any time that object's state value changes. In the code above, getData() (which might need to be async) will only run when called. Then you have a component called divComponent which is expecting info to have value. When rendering this component you would want a null check like I coded below. Finally the refreshButton component will call getData() when it is clicked.
Then in your code that renders this, I would have something like this:
<>
{info ? <divComponent info={info} /> : <p>There is no info</p>}
<refreshButton />
</>
The code above will check if the state object info has value, and if it does it will render the divComponent with your data values. If it does not, instead it will show the p tag explaining that there is no data. Either way it will render the refreshButton, which would run the getData() function again when clicked.
** EDIT **
Based on your comment, here is another approach so you can have a value on page load and update when necessary:
import {useState, useEffect} from "react";
const [info, setInfo] = useState();
const getData = () => {
axios
.get("http://127.0.0.1:5000/v1/matches")
.then((response) => {
setInfo(response.json())
})
.catch((error) => console.log(error));
}
useEffect(()=> {
getData();
}, [])
const divComponent = ({info}) => (
<div>
<p>{info.data.code}</p>
<p>{info.data.result}</p>
</div>
)
const refreshButton = () => (
<button onClick(()=>getData())>Refresh</button>
)
export const Page = () => (
<>
{info ? <divComponent info={info} /> : <p>There is no info</p>}
<refreshButton />
</>
);
your method is quite complex. I believe you need to add your MatchData Component Inside the div in this way.Also Don't Need To Call State Method setInfo() in useEffect hook.Only responseCode and matchdata Needed that is already adjusted by you in useEffect Hook.

React DropdownMultiselect component not updating with new options

import React, {useState, useEffect} from 'react';
import DropdownMultiselect from "react-multiselect-dropdown-bootstrap";
function App() {
const [data, setData] = useState(['test'])
const fetchApi = () => {
fetch("../api/" + ticket)
.then((response) => response.json())
.then((json) => {
let names = [];
for(var entry in json){
names.push(json[entry].print_name);
}
setData(names);
})
}
useEffect( () => {
fetchApi();},
[])
console.log('I ran')
return (
<div>
<button> {data[0]} </button>
<DropdownMultiselect options={data} />
</div>
);
}
export default App;
When I run the code above, the button updates with the name that was fetched via the fetch call, but the DropdownMultiSelect does not update its options. Am I doing it wrong, or is there something strange about the DropdownMultiSelect component that is somehow breaking things?
edit: brought the names declaration and the setData call into the second .then statement, still no joy.
I tested your code here. Looks like the problem lies within react-multiselect-dropdown-bootstrap itself and not your code.
I would suggest using a different library instead like react-select. In general it's always wiser to use third-party libraries which are being maintained frequently and have many weekly downloads.
If yet you still want to stick with react-multiselect-dropdown-bootstrap I would suggest creating an issue on their github repository.
You can try with react-multiselect-dropdown-bootstrapv1.0.4
Higher versions have some issues with the data population.
codesandbox - https://codesandbox.io/s/pensive-curran-zryxj?file=/src/App.js

Rendering search results in react

In my webapp, I have a Navbar with a searchbar. I want to be able to enter a search query, make an API call, and render the search results in a ResultsPage component. Think just a typical website with a searchbar at the top as an example.
I am having trouble with what approach is best for this. Should I get the query string and call the api in the Navbar component, and then pass the result object in the ResultsPage for displaying the elements? Or should instead take in the search query in the Navbar, and then pass the string to the ResultsPage component where I call the api and display the elements there?
Some guidance would be appreciated, thank you.
This is the simpliest example. No loading, and error processing.
const App = () => {
const [query, setQuery] = React.useState("");
const [result, setResult] = React.useState("");
React.useEffect(() => {
Api.invokeQuery(query)
.then(result => setResult(result));
}, [query]);
return (
<div>
<Navbar value={query} onChange={setQuery} />
<ResultsPage> { result } </ResultsPage>
</div>
)
}
As a conclusion, you should handle the state at the parent levels of Navbar and ResultPage so that the state can be shared by both components.
You can call the API with a GET request in the body of the function onChange() in react and then render a carousel combobox just under the searchbar in a SearchForm JSX class <input type="text" value={this.state.value} onChange={this.handleChange} />
You might use Redux to share state between all components which are not inherited as the following.
-- NavBar.js --
...
const dispatch = useDispatch();
const onValueChanged = (e) => {
fetchDataFromAPI(e.target.value)
.then((res) => {
dispatch('SEARCH_RESULT_CHANGED', res);
})
.catch(e=>{
console.error(e);
}
}
-- reducer --
...
swicth(action.type){
case 'SEARCH_RESULT_CHANGED':
return {...state, value: action.value}
...
guess the above reducer was combined as "state.search"
-- ResultsPage.js --
...
const searchResult = useSelector(state=>state.search)
You can use searchResult.value in every components.
Of course, it will be better to use Redux-saga or Thunk than to call fetchDataFromAPI function directly in the NavBar component.

React - API call with Axios, how to bind an onClick event with an API Call

I have one component, which is handle input component
import React, {useState} from 'react';
import FacebookApiCall from './Api'
export default function FacebookInterest () {
const [interest, setInterest] = useState('');
function HandleChange(event) {
setInterest(event.target.value);
}
return (
<div className="maincontainer">
<input className="searchbar" type="text" placeholder="Type an interest ..." value={interest} onChange={HandleChange}/>
<button onClick={() => FacebookApiCall(interest)} className="searchbutton">Search</button>
</div>
);
}
When a user click, i want to make a call to the API thanks to FacebookApiCall which is
import React, {useEffect} from 'react'
import axios from 'axios'
export default function FacebookApiCall(props) {
const url = `https://graph.facebook.com/search?type=adinterest&q=${props}&limit=10000&locale=en_US&access_token=EA`
useEffect(() => {
axios.get(url)
.then(res => {
console.log(url);
console.log(res);
//console.log(res.data.data[62].audience_size);
//console.log(res.data.data[62].name);
//console.log(res.data.data[62].description);
//console.log(res.data.data[62].path);
})
});
return (null);
}
the error returned by react is :
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
So I can't use a hooks inside my Facebook function i understand that now how can i make an api call without using useEffect and if I have to use useEffect no matter what, what i'm supposed to do ? I have to admit that im lost here.
Thanks a lot guys
So it looks like you're breaking one of the rules of hooks:
Only Call Hooks from React Functions
In your FacebookApiCall you're calling the useEffect hook from a non-react function.
The proper way to do this would be to do your api call from a function in your component.
import React, { useState, useEffect } from 'react';
function FacebookInterest () {
const url = `https://graph.facebook.com/search?type=adinterest&q=${props}&limit=10000&locale=en_US&access_token=EA`
const [interest, setInterest] = useState(null);
const [response, setResponse] = useState(null);
useEffect(() => {
// If you want do do some other action after
// the response is set do it here. This useEffect will only fire
// when response changes.
}, [response]); // Makes the useEffect dependent on response.
function callYourAPI() {
axios.get(url).then(res => {
// Handle Your response here.
// Likely you may want to set some state
setResponse(res);
});
};
function HandleChange(event) {
setInterest(event.target.value);
};
return (
<div className="maincontainer">
<input
className="searchbar"
type="text"
placeholder="Type an interest ..."
value={interest}
onChange={HandleChange}
/>
<button
onClick={() => callYourAPI(interest)}
className="searchbutton"
// You may want to disable your button until interest is set
disabled={interest === null}
>
Search
</button>
</div>
);
};

Using React hooks to fire events with side effects, like onClick() calling fetch()

I use React with hooks, using useState() for the internal state and useEffect() for external side effects like calling a web service, as it is described in React's documentation.
In this example I have a button, and a click on the button should call a web service. This works with useEffect() the first time on initialization but further button clicks will not call the web service.
Now my hacky solution is to introduce an additional state called hackIndex which is incremented each time the button is clicked, but that does not seem particular elegant:
import React, { useState, useEffect } from "react";
function App() {
const [answers, setAnswers] = useState([]);
// very ugly hack
const [hackIndex, setHackIndex] = useState(0);
// when hackIndex changes, gets data from web service and adds it to answers array
// also called when the page is initialized, but we can ignore that for this question
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then(response => response.json())
.then(json => setAnswers([...answers, json.title]));
}, [hackIndex]);
// returning React JSX
return (
<div>
<div>
<button
onClick={e => {
setHackIndex(hackIndex + 1);
}}
>
Call web service
</button>
</div>
<div>Collected answers:</div>
{answers.map(answer => (
<div>{answer}</div>
))}
</div>
);
}
export default App;
I haven't used reducers yet, as coming from Java and Angular they strike me a bit odd. But useReducer() should only use pure functions, so they don't seem to fit.
This is somewhat basic functionality, but I don't find any articles about this problem or anyone asking it. Or I'm missing something completely.
There is a similar question, but it involves Apollo for GraphQL queries using useQuery(), whereas I'm using a simple VanillaJS fetch() call:
How to fire React Hooks after event
setX functions returned from useState have a functional update ability that takes a function as an argument and calls that function with the previous state value. That enables your fetchAnswers to not need access to the previous value of answers.
import React, { useState, useEffect } from "react";
function App() {
const [answers, setAnswers] = useState([]);
function fetchAnswers() {
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then(response => response.json())
.then(json => setAnswers(prevAnswers => [...prevAnswers, json.title]));
}
useEffect(() => {
fetchAnswers();
}, []);
// returning React JSX
return (
<div>
<div>
<button
onClick={fetchAnswers}
>
Call web service
</button>
</div>
<div>Collected answers:</div>
{answers.map(answer => (
<div>{answer}</div>
))}
</div>
);
}
export default App;

Resources