I'm trying to navigate between a master detail data format in React. The source page does this:
{myDataList.map(myData =>
<tr key={myData.dataId}>
<td>{myData.dataId}</td>
<td>{myData.dataDescription}</td>
<td>
<NavLink to={'/details/' + myData.dataId}>
Details
</NavLink>
</td>
Clicking the edit link does navigate to the details page with the correct URL; which does this (mainly taken from the example template in VS):
interface DetailsState {
details?: DataDetails;
loading: boolean;
}
export class Details extends React.Component<RouteComponentProps<number>, DetailsState> {
constructor() {
super();
this.state = { details: undefined, loading: true };
console.log("match params: " + this.props.match.params);
fetch('api/Data/Details/' + this.props.match.params)
.then(response => response.json() as Promise<DataDetails>)
.then(data => {
this.setState({ details: data, loading: false });
});
}
My problem is that the console.log like above shows:
TypeError: _this.props is undefined
And so I can't access the dataId that I'm passing through.
Clearly I'm passing (or receiving) this parameter incorrectly; so my question is: what is the correct way to pass the parameter? It's worth noting that my project is based on the VS Template, and I am using tsx, rather than jsx files.
You cannot access this.props in a component's constructor. However, it is available as the first parameter. You can do this:
export class Details extends React.Component<RouteComponentProps<number>, DetailsState> {
constructor(props) {
super(props);
this.state = { details: undefined, loading: true };
console.log("match params: " + props.match.params);
fetch('api/Data/Details/' + props.match.params)
.then(response => response.json() as Promise<DataDetails>)
.then(data => {
this.setState({ details: data, loading: false });
});
}
}
Make sure that you call super(props) as the first thing in the constructor, or else React will yell at you.
EDIT:
As #Dan notes, it's generally not avisable to perform asynchronous actions in the constructor. Better to do that in componentDidMount():
export class Details extends React.Component<RouteComponentProps<number>, DetailsState> {
constructor(props) {
super(props);
this.state = { details: undefined, loading: true };
console.log("match params: " + props.match.params);
}
componentDidMount() {
fetch('api/Data/Details/' + this.props.match.params)
.then(response => response.json() as Promise<DataDetails>)
.then(data => {
this.setState({ details: data, loading: false });
});
}
}
If you want to access props in your constructor, you need to pass them in:
constructor(props) {
super(props);
}
Related
I have React class component called SearchLocationForm.js which is a child of App.js. Inside of the SearchLocationForm.js I have this code below
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
error: null,
isLoaded: false,
coordinates: []
}
this.appid = this.props.appid;
}
handleSubmit(location) {
fetch(
`http://api.openweathermap.org/geo/1.0/direct?q=${location}&appid=${this.props.appid}`
)
.then(res => res.json())
.then(resp => this.setState({coordinates: [resp.lat, resp.lon]}))
.catch((error) => {
console.error("there has been an issue with te GeoCode API Call", error)
})
}
render() {
return (
<LocationInput className="searchLocationForm" handleSubmit={this.handleSubmit}/>
)
}
I am trying to figure out how I can use the setState() method to update the component state with the API response, and afterwards lift that up to the App.js component in order to make subsequent calls.
I have lookup many answers regarding this problem and have tried implementing many solutions but nothing seems to work as of now. What do you see going on within my code that seems to be causing the issue? Thanks!
If yiu want only the coordinate will update and other states will remains same you have to use spread operator like this way
handleSubmit(location) {
fetch(`http://api.openweathermap.org/geo/1.0/direct?q=${location}&appid=${this.props.appid}`)
.then(res => res.json())
.then(resp => this.setState({...thia.state, coordinates: [resp.lat, resp.lon]}))
.catch((error) => {
console.error("there has been an issue with te GeoCode API Call", error)
})
}
And if you want to lift the state up to the app.js, you have to define the state on app.js and set the state from this component. Read more about lifting state up from here
Give you the full example. Do it like this way.
class App extends Component {
state = {
error: null,
isLoaded: false,
coordinates: []
}
handleSubmit(location) {
fetch(`http://api.openweathermap.org/geo/1.0/direct?q=${location}&appid=${this.props.appid}`)
.then(res => res.json())
.then(resp => this.setState({...thia.state, coordinates: [resp.lat, resp.lon]}))
.catch((error) => {
console.error("there has been an issue with te GeoCode API Call", error)
})
}
render() {
return (
<div>
<Hello name={this.state.name} />
<p>
Start editing to see some magic happen :)
</p>
<SearchLocationForm state={this.state} handleSubmit={this.handleSubmit} />
</div>
);
}
}
class SearchLocationForm extends Component {
constructor(props) {
super(props);
}
render() {
return (
<LocationInput className="searchLocationForm" handleSubmit={this.props.handleSubmit}/>
)
}
}
I have functions.js file and it export one function that I want to use in many files.
functions.js
import { API_URL } from "./index";
export const getData = (skip = 0, params = "") => {
this.setState({
loading: true
});
fetch(`${API_URL}items?limit=5&skip=${skip}${params}`, {
method: "GET",
credentials: "include"
})
.then(res => res.json())
.then(res => {
if (res.result.length > 0) {
let array = [];
res.result.map(item => {
let obj = item.data;
obj = Object.assign({ id: item._id }, obj);
array.push(obj);
});
this.setState({
records: array,
loading: false
});
} else {
this.setState({
next: true,
loading: false,
records: []
});
}
})
.catch(err => {
this.setState({
loading: false
});
});
};
hear this is function.js file that gets data from API and set in the state,
now, I want to use this function in items.js
items.js
import { getData } from "./../../config/functions";
import React from "react";
class Customers extends React.Component {
constructor(props) {
super(props);
this.getData = getData.bind(this);
}
componentDidMount() {
this.getData();
}
...
}
Error
TypeError: Cannot read property 'setState' of undefined
I fount this answer How to use state of one component in another file in reactjs? but it did not work for me so help me to change app.js file state from my functions.js file.
You're trying to re-bind this on an arrow function, which you cannot do. Check out this other SO question/answer for more details, but that's your problem. I'm going to edit this post with a suggestion of a more idiomatic way to write this in React.
Edit: OK I wanted to get you an answer quickly so you could unblock yourself and learn a bit more about arrow functions and this binding.
But more than just fixing this, you could improve this code significantly if you separate your api requests from your component. Right now you're mixing them up by trying to set state in your function that fetches data.
import { API_URL } from "./index";
export const getData = (skip = 0, params = "") => {
this.setState({
loading: true
});
fetch(`${API_URL}items?limit=5&skip=${skip}${params}`, {
method: "GET",
credentials: "include"
})
.then(res => res.json())
.then(res => {
// no need to declare an array and then push to it,
// that's what map is for. It will return a new array.
return res.result.map(item => {
// can also be written as return { ...item, id: item._id }
return Object.assign({ id: item._id }, obj)
});
});
// no need to catch here, you can do error handling in your component
};
import { getData } from "./../../config/functions";
import React from "react";
class Customers extends React.Component {
constructor(props) {
super(props);
this.fetchData = this.fetchData.bind(this);
}
componentDidMount() {
this.fetchData();
}
fetchData() {
getData()
.then((results) => {
this.setState({
next: results.length === 0,
records: results,
loading: false
});
})
.catch((err) => {
this.setState({ loading: false })
});
}
...
}
I'm trying to access the first object from data[]. Then, grab the keys using Object.keys() but it gives me this error:
"TypeError: Cannot convert undefined or null to object".
I need the output to be an array of the keys.
import React, { Component } from 'react';
class CodecChart extends Component {
constructor(props) {
super(props);
this.state = {
post: [],
isLoaded: false,
}
}
componentDidMount() {
const url = 'https://jsonplaceholder.typicode.com/users';
fetch(url)
.then(result => result.json())
.then(post => {this.setState({ post: post })
})
}
render() {
const data = this.state.post;
// cannot reach the first object of data[]
var keys = Object.keys(data[0]);
return (
<div>
//output should be an array of the keys
<h5>{keys}</h5>
</div>
)
}
}
export default CodecChart;
The first time you try to access data[0], it's still empty:
this.state = {
post: [],
isLoaded: false,
}
and const data = this.state.post; means that data[0] is undefined.
it's only after the component is mounted, and the state is set correctly that data[0] is defined (or not, depending on what the API returns).
I found a way for it to work by adding another "then" so it can set the "keys" state right after the "posts" state was set. But I wonder if there is another way to make it more elegant. Thank you for trying to help.
constructor(props) {
super(props);
this.state = {
posts: [],
isLoaded: false,
keys: []
}
}
componentDidMount() {
const url = 'https://jsonplaceholder.typicode.com/users';
fetch(url)
.then(result => result.json())
.then(posts => {
this.setState({ posts: posts })
})
.then(_ => { this.setState({ keys: Object.keys(this.state.posts[0]) }) })
}
render() {
const keys = this.state.keys;
return (
<div>
<h5>{keys}</h5>
</div>
)
}
I seem to have a lifecycle hook issue that I can't seem to solve.
export default class EditRevision extends Component {
state = {
data: [],
customColumns: []
}
componentWillMount = () => {
axios.get('http://localhost:8080/lagbevakning/revision/subscriptions?id=' + (this.props.match.params.id)).then(response => {
this.setState({
data: response.data,
loading: false
})
})
}
render() {
/* THIS IS THE CONSOLE.LOG() I AM REFERRING TO */
console.log(this.state.data.subscriptionRevisionDTOS)
return (
<div></div>
)
}
}
And this is my log upon rendering the component
https://i.gyazo.com/9dcf4d13b96cdd2c3527e36224df0004.png
It is undefined, then retrieves the data as i desire it to, then it gets undefined again.
Any suggestions on what causes this issue is much appreciated, thank you.
Replace this:
componentWillMount = () => {
axios.get('http://localhost:8080/lagbevakning/revision/subscriptions?id=' + (this.props.match.params.id)).then(response => {
this.setState({
data: response.data,
loading: false
})
})
with:
constructor(props){
super(props)
this.state = {
data: [],
customColumns: []
}
axios.get('http://localhost:8080/lagbevakning/revision/subscriptions?id=' + (this.props.match.params.id)).then(response => {
this.setState({
data: response.data,
loading: false
})
})
}
try to call axios in constructor or componentDidMount() (componentWillMount should not be used). the undefined result is caused by the async call. Looks like you have a lot of uncontrolled renders. try to add a shouldComponentUpdate function or convert your component in a PureComponent
Take a look at https://reactjs.org/docs/react-component.html
You have init the state with
state = {
data: [],
customColumns: []
}
Here this.state.data is empty array which did not have definition of
subscriptionRevisionDTOS that is why you are getting this.state.data.subscriptionRevisionDTOS undefined.
Meanwhile, your asyncaxios.get call is completed and this.state.data is updated with subscriptionRevisionDTOS.
As soon as state is updated render() called again and you are getting the proper value of this.state.data.subscriptionRevisionDTOS.
So below line will surely work.
state = {
data:{subscriptionRevisionDTOS:[]},
customColumns: []
}
export default class EditRevision extends Component {
state = {
data:{subscriptionRevisionDTOS:[]},
customColumns: []
}
componentDidMount = () => {
axios.get('http://localhost:8080/lagbevakning/revision/subscriptions?id=' +
(this.props.match.params.id)).then(response => {
this.setState({
data: response.data,
loading: false
})
})
render() {
/* THIS IS THE CONSOLE.LOG() I AM REFERRING TO */
console.log(this.state.data.subscriptionRevisionDTOS)
return (
<div></div>
)
}
see this it should be like this
I have a very simple react component that needs to connect to an API and retrieve some JSON data, which will then be used for displaying some information.
In the following class/component, I have mounted and state as a property. I normally use a constructor to hold my states but in this case, if I move the state to a constructor, I cannot seem to access the data (projectInfo) inside the renderer. When inside the renderer (line containinig {projectInfo.name}), I get the error: TypeError: Cannot read property 'name' of null
How can I use the constructor in this class to hold the state? Why does the following class work but not when I use a constructor? What is the convention for handling something like this?
class MyReportSummary extends Component {
mounted = true;
state = {
projectInfo: null,
isLoading: true,
error: null
};
componentDidMount() {
fetch(`/api/projects/${this.props.projectId}`)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error("Encountered problem fetching project info");
}
})
.then(data => {
if (this.mounted) {
this.setState({
projectInfo: data
});
}
})
.catch(fetchError => {
if (this.mounted) {
this.setState({
isLoading: false,
error: fetchError
});
}
});
}
componentWillUnmount() {
this.mounted = false;
}
render() {
const { isLoading, error, projectInfo } = this.state;
if (error) {
return <p>{error.message}</p>;
}
if (isLoading) {
return <p>Loading...</p>;
}
return (
<div className="myReportSummary">
Summary of Project name: {projectInfo.name}
Number of events: {this.props.data.length}
</div>
);
}
}
UPDATE: Just for clarity, the above sample code works just fine. What I'm trying to understand is if my class look like this that has a constructor initializing state, then I get that TypeError.
class MyReportSummary extends Component {
mounted = true;
constructor(props) {
super(props);
this.state = {
projectInfo: null,
isLoading: false,
error: null
};
}
componentDidMount() {
// same as the previous sample code
}
componentWillUnmount() {
this.mounted = false;
}
render() {
//same as the previous sample code
}
}
What is the correct convention for states? Is constructor not the proper way of doing this?
Initializing state in the constructor like your second example is perfectly valid, but your two examples are not the same. You are setting isLoading to true in the class property version, but isLoading to false in the constructor.
If error in null and isLoading is false you will hit the last part of your render method. Since projectInfo is null before your request is complete, you will try to access name on null and get your error.
Set isLoading to true and it should work as expected, or even projectInfo to an empty object {}, but then you will not get the loading indicator.
class MyReportSummary extends Component {
constructor(props) {
super(props);
this.mounted = true;
this.state = {
projectInfo: {},
isLoading: true,
error: null
};
}
// ...
}