As a quick summary, im trying to fetch from a URL and do so with 2 parameters.
I have no experience with javascript so i was trying this:
componentDidMount() {
$input = array("team" => {teamName}, "name" => {userPrincipalName});
fetch("http://localhost/openims/json.php?function=getDocuments&input=".urlencode(json_encode($input)))
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
files: result.files
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
This however does not seem to work. So my question would be: how do i succesfully pass the teamName and userPrincipalName from the context to the json_encode.
There is however 1 more problem with my code. I currently have two componentDidMounts, which are both using setState. The problem seems to be that whatever setState happens last, is the one that is being worked with, while the first setState is being completely overwritten. But i do need to do both the context setState AND the fetch to achieve my goal.
Here is my full code to give as clear an image as possible of what im doing:
import React from 'react';
import './App.css';
import * as microsoftTeams from "#microsoft/teams-js";
class Tab extends React.Component {
constructor(props){
super(props)
this.state = {
context: {}
}
}
componentDidMount(){
microsoftTeams.getContext((context, error) => {
this.setState({
context: context
});
});
}
componentDidMount() {
$input = array("team" => {teamName}, "name" => {userPrincipalName});
fetch("http://localhost/openims/json.php?function=getDocuments&input=".urlencode(json_encode($input)))
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
files: result.files
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { teamName, userPrincipalName } = this.state.context;
const { error, isLoaded, files } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
{files.map(file => (
<li key={file.id}>
{file.name} {file.type}
<span id="user">Team: {teamName}, userPrincipalName: {userPrincipalName }</span>
</li>
))}
</ul>
);
}
}
}
export default Tab;
TL;DR
How do i use setState two times without problems? and how do i work the parameters teamName and userPrincipalName into my fetch?
Thank you!
If I understand correctly, what you need is backticks:
`http://localhost/openims/json.php?function=getDocuments&input=${userPrincipalName}`
Related
I have a react JSX component that makes an api call to the backend and fetches the data I require. This data is currently inside a react-select tag which allows me to display all the options inside a dropdown. I'm trying now to use this dropdown to render the selection using charts, specifically react-chartjs-2. I've been trying for quite a while but I honestly am at my wits end.
Here's what the api call with the select looks like:
export default class ------ extends component{
constructor(props, context) {
super(props, context);
this.state = {
selectedOption: {},
};
}
fetchData = (inputValue, callback) => {
setTimeout(() => {
fetch(
"api" +
inputValue,
{
method: "GET",
}
)
.then((resp) => {
return resp.json();
})
.then((data) => {
const tempArray = [];
if (data) {
if (data.length) {
data.forEach((element) => {
tempArray.push({
label: `${element.campo}`,
value: element.valor,
});
});
} else {
tempArray.push({
label: `${data.campo}`,
value: data.valor,
});
}
}
callback(tempArray);
})
.catch((error) => {
console.log(error, "error");
});
}, 1000);
};
onSearchChange = (selectedOption) => {
if (selectedOption) {
this.setState({
selectedOption,
});
}
};
Here's the actual select element and the chart inside the return section
render(){
return(
<AsyncSelect
id="valx"
value={this.state.selectedOption}
loadOptions={this.fetchData}
placeholder="placehold"
onChange={(e) => {
this.onSearchChange(e);
}}
defaultOptions={true}
/>
<div class="charts">
<Bar
id="MyBarChart"
data={{}}
options={{}}
/>
<Pie
id="MyPieChart"
data={{}}
options={{}}
/>
</div>
I have a really long way to go when it comes to React, I'm sure the solution will turn out to be rather simple. Any and all help is welcome, and thanks in advance.
In the below compoenent, the function is neverending. Can someone tell me what to fix so that in the end the beers array in the state has 5 names?
export default class GetBeers extends React.Component {
constructor() {
super();
this.state = {
beers: [],
didError: false
};
this.getBeerInfo = this.getBeerInfo.bind(this);
}
render() {
return (
...
}
getBeerInfo() {
let beerArr = [1,2,3,4,5];
this.props.beerArr.map(id => {
fetch(`https://api.punkapi.com/v2/beers/${id}`)
.then(res => res.json())
.then(json => {
this.setState(state => {
const beers = state.beers.concat(json[0].name);
return {
beers
};
});
})
.catch(err => {
this.setState({
didError : true
});
});
})
}
}
Well your code should be somethings like this ..
import React from 'react';
export default class GetBeers extends React.Component {
constructor() {
super();
this.state = {
beers: [],
didError: false
};
this.getBeerInfo = this.getBeerInfo.bind(this);
}
render() {
return (
<div>{this.state.beers}</div>
)
}
componentDidMount() {
this.getBeerInfo()
}
getBeerInfo() {
let beerArr = [1,2,3,4,5];
beerArr.map(id => {
fetch(`https://api.punkapi.com/v2/beers/${id}`)
.then(res => res.json())
.then(json => {
this.setState({
//const beers = state.beers.concat(json[0].name);
//return {
//beers
//};
beers: this.state.beers.concat(json[0].name)
});
console.log('well at least this works')
})
.catch(err => {
this.setState({
didError : true
});
});
})
}
}
It is advised that you use the componentDidMount() lifecycle method for the fetch api and add what #atahnksy said.
When you are using setState, you can try this:
this.setState({ beers: [...this.state.beers, json[0].name])
This might fix your problem.
You can improve the render method using a combination of ternary operator(to display appropriate message when it cannot reach the server), format with map and ordered list to get something like this :
render() {
return (
<div><ol>{this.state.beers.length!==0 ? this.state.beers.map((beer)=><li>{beer}</li>) :"Could not retrieve any bears. Try again/ensure you can access the server/networtk"}</ol></div>
)
}
Components
const Pcards = ({ projects }) => {
return (
<div>
<CardColumns>
{projects.map((projects) => (
<Card>
<Card.Img variant="top" src={"http://localhost:8000" + projects.images[0].file_path + projects.images[0].file_name + projects.images[0].file_type} />
Pages
class Projects extends Component {
state = {
projects:[]
}
componentDidMount() {
fetch('http://localhost:5000/api/projects')
.then(res => res.json())
.then((data) => {
this.setState({ projects: data })
})
.catch(console.log)
}
render () {
return (
<Pcards projects = {this.state.projects} />
);
}
}
New to react and this code returns
TypeError: projects.map is not a function
This appears to be compiling just fine on my partner's end since he written this code and I'm trying to expand on his work.
I've seen other similar posts but unable to find a fix. Any idea what's going on here?
You have two mistakes in your Projects class.
1- .catch error handling syntax was wrong
2- you were not checking the fetched data
class Projects extends Component {
state = {
projects: []
}
componentDidMount() {
fetch('http://localhost:5000/api/projects')
.then(res => res.json())
.then((data) => {
if (data && data.length) { // checking the data
this.setState({ projects: data })
} else {
console.log("Projects fetch failed, check your api code!")
}
})
.catch(e => console.log(e)); // corrected error catch
}
render() {
return (
<Pcards projects={this.state.projects} />
);
}
}
You can also edit your Pcards component code. You are already using a property called projects and you are mapping it, calling the argument projects too. That is not a good practice. If you are mapping projects name the item as project or projectItem.
projects.map((project) => ...
Try
class Projects extends Component {
constructor(props){
super(props)
this.state = {
projects:[]
}
}
I am attempting to pull data from Open Data to put together a quick heat map. In the process, I want to add some stats. Almost everything runs well in that I have the data and am able to render the map, but I am unsure how to deal with calculations once I get the data since it takes time for data to come in. How do I set things up so that I can run a function on a state variable if it hasn't necessarily received data yet? Currently I am getting a null as the number that is passed as props to StatCard.
Below are my attempts:
App.js
import React, { Component } from 'react';
import Leaf from './Leaf';
import Dates from './Dates';
import StatCard from './StatCard';
import classes from './app.module.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
data:[],
cleanData:[],
dateInput: '2019-10-01',
loading: false,
totalInspections: null,
calculate: false
};
}
componentDidMount() {
try {
this.fetchData();
} catch (err) {
console.log(err);
this.setState({
loading: false
})
}
}
fetchData=()=>{
const requestData = async () => {
await fetch(`https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=latitude > 39 AND latitude< 45 AND inspection_date >= '${this.state.dateInput}'&$limit=50000`)
.then(res => res.json())
.then(res =>
//console.log(res)
this.setState({ data: res, loading: true})
)
}
const calculateInspections = () => {
this.setState({totalInspections: this.state.data.length})
}
//call the function
requestData();
if(this.state.data) {
calculateInspections();
}
}
handleDateInput = (e) => {
console.log(e.target.value);
this.setState({dateInput:e.target.value, loading: false}) //update state with the new date value
this.updateData();
//this.processGraph(e.target.value)
}
updateData =() => {
this.fetchData();
}
LoadingMessage=()=> {
return (
<div className={classes.splash_screen}>
<div className={classes.loader}></div>
</div>
);
}
//inspection_date >= '${this.state.dateInput}'&
// https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=inspection_date >= '2019-10-10T12:00:00'
render() {
return (
<div>
<div>{!this.state.loading ?
this.LoadingMessage() :
<div></div>}
</div>
{this.state.totalInspections && <StatCard totalInspections={this.state.totalInspections} /> }
<Dates handleDateInput={this.handleDateInput}/>
<Leaf data={this.state.data} />
</div>
);
}
}
export default App;
StatCard.js
import React from 'react';
const StatCard = ( props ) => {
return (
<div >
{ `Total Inspections: ${props.totalInspections}`}
</div>
)
};
export default StatCard;
Attempt Repair
componentDidMount() {
try {
this.fetchData();
} catch (err) {
console.log(err);
this.setState({
loading: false
})
}
}
componentDidUpdate () {
if(this.state.data) {
this.setState({totalInspections: this.state.data.length})
}
}
fetchData= async ()=>{
const requestData = () => {
fetch(`https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=latitude > 39 AND latitude< 45 AND inspection_date >= '${this.state.dateInput}'&$limit=50000`)
.then(res => res.json())
.then(res =>
//console.log(res)
this.setState({ data: res, loading: true})
)
}
//call the function
await requestData();
}
So your problem is that isLoading state needs to be set synchronously before any async calls.
So in your componentDidMount:
componentDidMount() {
try {
this.setState({ loading: true }); // YOU NEED TO SET TRUE HERE
this.fetchData();
} catch (err) {
console.log(err);
this.setState({
loading: false
})
}
}
This ensures loading as soon as you make the call.
Then your call is made and that part is asynchronous.
As soon as data comes through, the loading is done:
.then(data => {
this.setState({
data: data,
loading: false, // THIS NEEDS TO BE FALSE
totalInspections: this.state.data.length
})
})
Furthermore, your render method can have multiple return statements. Instead of having conditional JSX, return your loading layout:
render() {
if (this.state.loading) {
return <div> I am loading </div>
}
return <div> Proper Content </div>;
}
Only render <StatCard /> if you have the data you need:
{this.state.totalInspections && <StatCard totalInspections={this.state.totalInspections} /> }
First of all, I don't think you need a separate function calculateInspections(). You can put that logic in the then callback.
fetchData = () => {
fetch(`https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=latitude > 39 AND latitude< 45 AND inspection_date >= '${this.state.dateInput}'&$limit=50000`)
.then(res => res.json())
.then(data => {
this.setState({
data: data,
loading: true,
totalInspections: this.state.data.length
})
})
}
Secondly, setting this.state.totalInspections is effectively redundant, since you can simple do:
{this.state.data && <StatCard totalInspections={this.state.data.length} /> }
Lastly, avoid using componentDidUpdate() hook when you're new to react. Most of the time you end up shooting yourself in the foot.
Currently your Attempt Repair just got you into an infinite render loop. This happens because whenever you call setState(), it'll call componentDidUpdate() lifecycle hook after rendering. But within componentDidUpdate() you call again setState(), which induces a follow-up call to the same lifecycle hook, and thus the loop goes on and on.
If you must use componentDidUpdate() and call setState() inside, rule of thumbs, always put a stop-condition ahead of it. In you case, it'll be:
componentDidUpdate () {
if (this.state.data) {
if (this.state.totalInspections !== this.state.data.length) {
this.setState({ totalInspections: this.state.data.length })
}
}
}
Here is my solution.
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
dateInput: '2019-10-01',
loading: false,
error: false
};
}
async componentDidMount() {
try {
await this.fetchData(this.state.dateInput);
} catch (err) {
this.setState({ loading: false, error: true });
}
}
fetchData = (date) => new Promise(resolve => {
this.setState({ loading: true });
fetch(`https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=latitude > 39 AND latitude< 45 AND inspection_date >= '${date}'&$limit=50000`)
.then(res => res.json())
.then(res => {
this.setState({ data: res, loading: false, error: false });
resolve(res.data);
});
})
handleDateInput = e => {
this.setState({ dateInput: e.target.value }) //update state with the new date value
this.fetchData(e.target.value);
}
render() {
const { loading, data } = this.state;
return (
<div>
{loading && (
<div className={classes.splash_screen}>
<div className={classes.loader}></div>
</div>
)}
{data && <StatCard totalInspections={data.length} />}
<Dates handleDateInput={this.handleDateInput} />
<Leaf data={data} />
</div>
);
}
}
There are two ways of achieving this:
You can put calculator in componentDidUpdate() and write a condition to just calculate once
componentDidUpdate(prevProps, prevState) {
const data = this.state.data;
// this line check if we have data or we have new data,
// calculate length once
if (data.length || !isEqual(data, prevState.data)) {
calculateInspections()
}
}
// isEqual() is a lodash function to compare two object or array
You can stop your rendering until data is fetched
async componentDidMount() {
await fetchData()
}
fetchData = () => {
const requestData = async() => {
await fetch(`https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=latitude > 39 AND latitude< 45 AND inspection_date >= '${this.state.dateInput}'&$limit=50000`)
.then(res => res.json())
.then(res =>
//console.log(res)
this.setState({
data: res,
loading: true,
totalInspections: res.length
})
)
}
// in above situation you just setState when you are sure
// that data has come
//call the function
requestData();
}
I actually face a small problem in React when calling the API, since ComponentWillMount is deprecated.
I did this:
class MyClass extends Component {
constructor() {
super();
this.state = {
questionsAnswers: [[{ answers: [{ text: '', id: 0 }], title: '', id: 0 }]],
},
};
}
componentDidMount() {
this.getQuestions();
}
getQuestions = async () => {
let questionsAnswers = await Subscription.getQuestionsAndAnswers();
questionsAnswers = questionsAnswers.data;
this.setState({ questionsAnswers });
};
So the page is rendered a first time without questionsAsnwers, when I get the questionAnswers the page is re-rendered
Is there a better solution to avoid a re-render?
The best way to handle API call is in the componentDidMount method react lifeCycle according to react documentation. At this moment all you can do is to add a spinner to make your component more user-friendly.
Hopefully, in the next React releases. React will introduce a new way to solve this kind of problem using the suspense approach https://medium.com/#baphemot/understanding-react-suspense-1c73b4b0b1e6
Using a class with React.Component it is componentDidMount:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
fetch("https://api.example.com/items")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.items
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
{items.map(item => (
<li key={item.id}>
{item.name} {item.price}
</li>
))}
</ul>
);
}
}
}
If you use a function component with Hooks you should do it like this:
function MyComponent() {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);
// Note: the empty deps array [] means
// this useEffect will run once
// similar to componentDidMount()
useEffect(() => {
fetch("https://api.example.com/items")
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setItems(result);
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
setIsLoaded(true);
setError(error);
}
)
}, [])
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
{items.map(item => (
<li key={item.id}>
{item.name} {item.price}
</li>
))}
</ul>
);
}
}
Example response:
{
"items": [
{ "id": 1, "name": "Apples", "price": "$2" },
{ "id": 2, "name": "Peaches", "price": "$5" }
]
}
Source:
https://reactjs.org/docs/faq-ajax.html
I think, it's okay to show spinner in that situation.
And you should also check that ajax did not fail.