After I execute a query in the console I can see: {data:{action: [...]}. How can I assign that data to a variable in a React component?
Trying this but it is not working:
class MyGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
nodes: this.data
Have some falsy or empty initial value for state.node. Then fetch the data in componentDidMount - on success, update state.node with your actual data. That data can also be passed down to child components. Example:
class MyGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
nodes: null,
}
}
componentDidMount() {
// w/e your fetch function is. I'm assuming your method uses promises.
fetchDataSomehow().then((response) => {
this.setState({nodes: response.data}) // or whatever property has the nodes
})
}
render() {
const nodes = this.state.nodes;
return (
<LoadingIcon isVisible={!nodes}>
{(nodes || []).map((node) => (
<MyCircle key={node.someUniqueId} node={node} /> // access `node` inside the `<MyCircle />` component using `this.props.node`
))}
</LoadingIcon>
)
}
}
You'll need to handle what happens/renders while the data is still loading (e.g the made-up <LoadingIcon /> component).
Related
I have this class component where I'd like to fetch the response from server to an state array so I can pass further the elements to another component as props, so far I have this:
export default class MainApp extends Component {
state = {
posts: [],
}
constructor(props){
super(props);
const request = new FetchRequest();
request.setAmount(this.props.amount);
request.setUserid(this.props.token);
request.setSeenpostsList(this.props.seenPosts);
var stream = client.fetchPosts(request, {});
stream.on('data', function(response) {
this.setState({
posts: [...this.state.posts, response.array]
})
});
}
render(){
return(
<div className="main-app">
<Navbar />
<Postbox token = {this.props.token}/>
{this.state.posts.map(element =>
<Postcard username = {element[0]}/>
)}
</div>
)
}
With this code I get TypeError: Cannot read properties of undefined (reading 'posts').
What is the correct way to do it?
Your state needs to be initialised within the constructor() {}
constructor(props) {
super(props);
this.state = {
posts: []
}
}
Here's info you might also want to read
I'm encountering some counterintuitive behavior in react (at least to me). I have an API call that returns some JSON to be displayed. I wanted the result of this call to be in the state of a Parent Component('Problem Container'), and passed down as props to its child component ('Problem').
I was getting an undefined result in the child component so I did some console logging in 4 different places to see what was going on: once in each constructor, and once in each components "this.state" using my "getProblemsFromAPI()" function.
The Parent Component:
class ProblemContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
problem_set: getProblemsFromAPI()
};
{console.log(this.state.problem_set, 'parent constructor')}
}
render() {
return(
<div>
<Problem problem_set={this.problem_set} />
</div>
)
}
}
function getProblemsFromAPI(problem_set = {}) {
fetch("http://localhost:5000/api/problem_set/5")
.then(response => response.json())
.then((data) => {console.log(data, 'set state of parent'); return data});
}
The Child Component:
class Problem extends React.Component {
constructor(props){
super(props);
this.state = {
current_problem: '',
problem_set: getProblemsFromAPI()
}
console.log(this.props, 'child constructor')
}
render() {
return (
<div>
</div>
);
}
}
function getProblemsFromAPI(problem_set = {}) {
fetch("http://localhost:5000/api/problem_set/5")
.then(response => response.json())
.then((data) => {console.log(data, 'set state of child'); return data});
}
What I'm seeing in the console is:
"Parent Constructor"
"Child Constructor"
"Set State of Parent"
"Set State of Child"
This is weird to me because the "this.state" constructor of the Parent Component should be the first thing sequentially called. I can put the API call in the componentDidMount of the child component to move forward, that's not an issue. I just don't understand this behavior.
Is this happening because it's a function call and not a value? I'm new to both javascript and react. Any advice will help!
Thanks
Any side-effects like fetching data from an external API or any state variable update should be done after the component has mounted in componentDidMount life cycle method if using class components or in useEffect hook if using functional components. Since you said you want the ProblemContainer to pass the data to it's children. The ProblemContainer should be responsible for fetching the data.
For example,
class ProblemContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
problem_set: null,
error: undefined
};
}
componentDidMount() {
fetch("http://localhost:5000/api/problem_set/5")
.then(response => response.json())
.then((data) => {this.setState({problem_set: data})});
// never leave a promise with out catching the error
.catch(error => {
// you can console.log the error to see what is going wrong
this.setState({problem_set: null, error: error})
})
}
render() {
return(
<div>
<Problem problem_set={this.state.problem_set} /> // you need to pass the prop like this
</div>
)
}
}
In your problem component,
class Problem extends React.Component {
// you have access to the problem_set via this.props.problem_set
render() {
console.log(this.props.problem_set) // should show the expected result
return (
<div>
// component logic
</div>
);
}
}
I have two components, one parent one child. I am using the fetch method in componentDidMount() callback. Once I do this, I set the state with key items to that data that is pulled from the api. Once I do this it should be able to be console logged in the child component as a prop. However this is not working. What am I doing wrong here?
Parent Component:
import React, {Component} from 'react';
import Map from './maps/Map';
class Main extends Component {
constructor(props){
super(props);
this.state = {
name: "John",
items: []
}
}
componentDidMount() {
fetch('https://hn.algolia.com/api/v1/search?query=')
.then(dat => dat.json())
.then(dat => {
this.setState({
items: dat.hits
})
})
}
render() {
return (
<div>
<Map list={this.state.name} items={this.state.items}></Map>
</div>
)
}
}
export default Main;
Child Component:
import React, {Component} from 'react';
class Map extends Component {
constructor(props) {
super(props);
console.log(props.items)
}
render () {
return (
<h1>{this.props.name}</h1>
)
}
}
export default Map;
First, fetch is asynchronous. So, the fetch statement might be pending by the time you try to console.log the result inside the child constructor.
Putting the console.log inside the render method would work, because the component will be rerendered, if the state items changes.
The constructor for a component only runs one time during a lifecycle. When it does, props.items is undefined because your ajax request is in-flight, so console.log(props.items) doesn't show anything.
If you change your constructor to console.log("constructed");, you'll see one-time output (stack snippets may not show this--look in your browser console). Henceforth, componentDidUpdate() can be used to see the new props that were set when your ajax request finishes.
You could also log the props inside the render method, which will run once before the ajax request resolves and again afterwards when props.items changes.
As a side point, you have <Map list=... but the component tries to render this.props.name, which is undefined.
Also, if you aren't doing anything in the constructor (initializing state or binding functions) as here, you don't need it.
class Map_ /* _ added to avoid name clash */ extends React.Component {
constructor(props) {
super(props);
console.log("constructed");
}
componentDidUpdate(prevProps, prevState) {
const props = JSON.stringify(this.props, null, 2);
console.log("I got new props", props);
}
render() {
return (
<div>
<h1>{this.props.name}</h1>
<pre>
<ul>
{this.props.items.map((e, i) =>
<li key={i}>{JSON.stringify(e, null, 2)}</li>)}
</ul>
</pre>
</div>
);
}
}
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {name: "John", items: []};
}
componentDidMount() {
fetch('https://hn.algolia.com/api/v1/search?query=')
.then(dat => dat.json())
.then(dat => {
this.setState({items: dat.hits})
});
}
render() {
return (
<div>
<Map_
name={this.state.name}
items={this.state.items}
/>
</div>
);
}
}
ReactDOM.createRoot(document.querySelector("#app"))
.render(<Main />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
The only problem you have is that you are trying to use this.props.name and your Map component props are called list and items, so it will return undefined.
If you log your props in the constructor you will get the initial state of Main because the fetch hasn't returned anything yet. Remember that the constructor only runs once. So you are probably getting an empty array when you log props.items in the constructor because that's what you have in your initial state.
{
name: "John",
items: []
}
If you log the props in your render method you will see your array filled with the data you fetched, as you can see here:
https://codesandbox.io/s/stoic-cache-m7d43
If you don't want to show the component until the data is fetched you can include a boolean property in your state that you set to true once you the fetch returns a response and pass it as a prop to your component. Your component can you use that variable to show, for example, a spinner while you are fetching the data. Here's an example:
https://codesandbox.io/s/reverent-edison-in9w4
import CircularProgress from "#material-ui/core/CircularProgress"
class Main extends Component {
constructor(props) {
super(props);
this.state = {
name: "John",
items: [],
fecthed: false
};
}
componentDidMount() {
fetch("https://hn.algolia.com/api/v1/search?query=")
.then(dat => dat.json())
.then(dat => {
this.setState({
items: dat.hits,
fecthed: true
});
});
}
render() {
return (
<Map
fetched={this.state.fecthed}
list={this.state.name}
items={this.state.items}
/>
);
}
}
class Map extends Component {
render() {
return (
<div>
{this.props.fetched ? (
<div>
<h1>{this.props.list}</h1>
{this.props.items.map((item, indx) => (
<div key={indx}>Author: {item.author}</div>
))}
</div>
) : (
<CircularProgress />
)}
</div>
);
}
}
Hope this helps. Cheers!
I have this code:
export default class FinancesPage extends React.Component {
constructor(props) {
super(props);
this.state = {users: []};
}
componentWillMount() {
firebase.database().ref('Users').orderByChild('transactions').startAt(1).on('value', snap => {
const users = arrayFromObject(snap.val());
this.setState({users: users});
});
}
render() {
return (
<div>
<NumberOfPurchasesComponent users={this.state.users}/>
</div>
)
}
}
And this code:
export default class NumberOfPurchasesComponent extends React.Component {
constructor(props) {
super(props);
this.state = {users: this.props.users};
}
componentWillMount() {
const users = this.state.users;
// Do stuff here
}
render() {
return (
{/*And render stuff here*/}
);
}
}
What's happening right now: The parent element FinancesPage passes an empty array of users to the child NumberOfPurchasesComponent. I need it to pass a new value of the array every time there is an update.
And i want to pass the users from FinancesPage to NumberOfPurchasesComponent, but users data is obtained async. How can I make the NumberOfPurchasesComponent refresh when the variable value is obtained?
Have you tried to use componentWillReceiveProps? I mean something like:
export default class NumberOfPurchasesComponent extends React.Component {
constructor(props) {
super(props);
this.state={users: []}
}
componentWillReceiveProps(nextProps) {
if(nextProps.users && nextProps.users!==this.state.users){
this.setState({
users: nextProps.users
})
}
}
render() {
return (
{/*And render stuff here*/}
);
}
}
This way the component knows when it has to re-render.
The FinancesPage implementation looks good. The problem lies in NumberOfPurchasesComponent in this particular piece of code :
constructor(props) {
super(props);
this.state = {users: this.props.users};
}
Am assuming in the render method of NumberOfPurchasesComponent you are using this.state.users instead of this.props.users.
constructor runs only once. Now as you mentioned data is fetched async, which means NumberOfPurchasesComponent is initially rendered even before the the response is obtained. Hence its constructor method which runs only once sets the users state to []. Even if the props gets updated from FinancesPage, as the render in NumberOfPurchasesComponent uses state, no re-render happens.
Try using this.props.users directly in NumberOfPurchasesComponent render and see if it works.
As per FinancesPage page it is well and good with codebase, but problem is why you are making setstate if there is no any manipulation of user's data as you got from API call.
So without making setState just pass it as direct
render() {
return (
<div>
<NumberOfPurchasesComponent users={this.props.users}/>
</div>
)
}
so whenever the API calls to fetch the response, here update value get in passed to NumberOfPurchasesComponent class.
Since upgrading to using Gatsby V2 I have been struggling to pass this.state to child components to be used as this.props.
For Example I have a Container that has data1 and data2 added to this.state as
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
data1: '',
data2: ''
};
}
componentDidMount() {
// Loading database
.then(doc =>
this.setState({
data1: doc.data().data1,
data2: doc.data().data2
})
);
}
render() {
const children = this.props;
const stateAsProps = React.Children.map(children, child =>
React.cloneElement(child, {
data1: this.state.data1,
data2: this.state.data2
})
);
return (
<div>{stateAsProps}</div>
);
}
}
and a child component as
class Child extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<h1>{this.props.data1}</h1>
<p>{this.props.data2}</p>
);
}
}
and finally this is brought into the page by
const Page = () => (
<Parent authUserID="01234" campaignID="56789">
<Child />
</Parent>
);
In Gatsby V1 this was working but now with the migration I am receiving an error Uncaught Error: Objects are not valid as a React child (found: object with keys {authUserID, campaignID, children}). If you meant to render a collection of children, use an array instead.
Can anyone advise to why and how this issue can be rectified?
You are using the entire props object as children in your Parent component. Make sure you destructure out the children object from the props instead and it will work as expected.
const { children } = this.props;