I’m having trouble passing the state data from a parent component to a child component. I’m not sure why this is happening, so any feedback is greatly appreciated.
My fetch request is returning the correct data when I console.log ‘this.state.episodeData’ in the componentDidMount() method in the parent component, but it is not showing in the console.log in the componentDidMount() method in the child component. What am I doing wrong?
I’ve simplified the example to show only the relevant fetch request and data handling:
Parent component
import React, { Component, useState, Fragment } from 'react';
import TempComp from './tempComp';
export default class PostContent extends Component {
constructor(props) {
super(props);
this.state = {
id: '',
episodeData: [],
}
}
async componentDidMount() {
const { id } = this.props.match.params;
const response = await fetch(`http://localhost:5000/episode/${id}/playlist`);
const jsonData = await response.json();
this.setState({
episodeData: jsonData, //this is working!
id: id
});
console.log('parent fetched data', this.state.episodeData)
}
render() {
return (
<Fragment>
<TempComp playlist={this.state.episodeData} />
</Fragment>
)
}
}
Child component
import React, { Component } from 'react'
class TempComp extends Component {
constructor(props) {
super(props);
this.state = {
}
}
componentDidMount() {
console.log(‘props in child component’, this.props.playlist)
}
render() {
return (
<div>
</div>
)
}
}
export default TempComp
componentDidMount is called just the first time after the initial rendering of your component. This means that your child component is rendered before you finish your fetch request. In the parent component you are not seeing it, because setState works asynchronously and it is not done saving your state, when you try to print it out. If you want to see it, pass callback to setState:
async componentDidMount() {
const { id } = this.props.match.params;
const response = await fetch(`http://localhost:5000/episode/${id}/playlist`);
const jsonData = await response.json();
this.setState({
episodeData: jsonData, //this is working!
id: id
}, () => {
console.log('parent fetched data', this.state.episodeData)
);
}
In order to see the updated data in your child component, consider using componentDidUpdate(prevProps, prevState, snapshot):
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('props in child component', this.props.playlist) ;
}
Related
I am learning react and I would still consider myself to be a beginner. My goal is to click a button on my child component so that I could re render the parent component. This is the code I have.
Parent Component
import React, { Component } from 'react';
import Activity from './Components/Activity'
class App extends Component {
state = {
activity: ''
}
handleClick = () => {
// I have read that forceUpdate is discouraged but this is just an example
this.forceUpdate()
}
async componentDidMount() {
const url = 'http://www.boredapi.com/api/activity/'
const response = await fetch(url);
const data = await response.json();
this.setState({
activity: data.activity
})
}
render() {
return (
<div>
<Activity act={this.state.activity} click={this.handleClick}/>
</div>
);
}
}
export default App;
Child Component
import React, { Component } from 'react';
class Activity extends Component {
render() {
return (
<div>
<h1>Bored? Here is something to do</h1>
<p>{this.props.act}</p>
<button onClick={this.props.click}>Something Else</button>
</div>
);
}
}
export default Activity;
As you can see I am trying to click a button so that I could get another fetch and a different activity renders on my child component. I am trying to keep my child component stateless but if keeping it stateless doesn't make sense or is just plain wrong I would love to know.
You can try to move fetching function outside componentDidMount
for the example:
handleClick = () => {
this.fetchdata();
}
async fetchdata(){
const url = 'http://www.boredapi.com/api/activity/'
const response = await fetch(url);
const data = await response.json();
this.setState({
activity: data.activity
})
}
componentDidMount() {
this.fetchdata();
}
You can make a class method for fetching the new activity,
Call it after the app first mounted with componentDidMount() and again when you call it from the child component Activity.
You should mentioned in the your question that the response body is different in each request you make.
import React, { Component } from 'react';
import Activity from './Activity'
class App extends Component {
state = {
activity: ''
}
handleClick = () => {
this.getActivity()
}
componentDidMount() {
this.getActivity();
}
async getActivity() {
const url = 'https://www.boredapi.com/api/activity/'
const response = await fetch(url);
const data = await response.json();
this.setState({
activity: data.activity
})
}
render() {
console.log(this.state);
return (
<div>
<Activity act={this.state.activity} click={this.handleClick}/>
</div>
);
}
}
export default App;
Here is also a sandbox:
https://codesandbox.io/s/dreamy-noether-q98rf?fontsize=14&hidenavigation=1&theme=dark
I'm making a get() request to firestore to get data. I am able to store the data in my component state but each time I try to access the data, it shows as duplicated. How can I prevent it? Thanks.
``
import React from 'react';
import fire from '../../config/firebase';
class ViewLists extends React.Component {
constructor(props){
super(props)
this.state = {
listData: []
};
}
componentDidMount(){
fire.firestore().collection('restaurantList')
.get()
.then(querySnapshot => {
const lists = querySnapshot.docs.map(doc => doc.data());
this.setState({ listData: lists });
});
}
render() {
const { listData } = this.state
return (
<div>
{listData.map(list => console.log(list.user))}
</div>
);
}
}
export default ViewLists;
``
This was my code
import React, { Component } from "react";
import axios from "axios";
class App extends Component {
state = {
invites: [],
};
constructor() {
super();
axios.get(`http://localhost:8080/security/allUser`).then((res) => {
console.log(res.data);
this.setState({ invites: res.data });
});
}
render() {
return (
<div>
{this.state.invites.map((invite) => (
<h2 key={invite.id}>{invite.name}</h2>
))}
<h1>Welcome</h1>
</div>
);
}
}
export default App;
state and setState have worked for me alright for more complex codes before. This one keeps showing the same error
This is the error:
index.js:1 Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the App component.
Add a componentDidMount() and write your request call inside it. When the component first loads the componentDidMount function will run.
Actually you can make request in constructor (React allows it but you shouldnt) but you have to make the request only after the component has been mounted or just before it is about to be mounted.
So it is wise to make your requests in componentDidMount().
import React, { Component } from "react";
import axios from "axios";
class App extends Component {
constructor(props) {
super(props);
this.state = {
invites: [],
};
}
componentDidMount() {
axios.get(`http://localhost:8080/security/allUser`).then((res) => {
console.log(res.data);
this.setState({ invites: res.data });
});
}
render() {
return (
<div>
{this.state.invites.map((invite) => (
<h2 key={invite.id}>{invite.name}</h2>
))}
<h1>Welcome</h1>
</div>
);
}
}
export default App;
I have a component that contains a state, and I will pass the state data into another component, I use a static contextType to throw the state data but the data does not reach the intended component, what do you think this is wrong? thank you
this is my parent component
export const MyContext = React.createContext();
export class MerchantByPromo extends Component {
constructor(props) {
super(props);
this.state = {
dataPromo: [],
loading: true
};
}
async componentDidMount() {
const merchant_id = this.props.match.params.id_merchant
await Api.post('language/promo-voucher-by-merchant', { MERCHANT_ID: merchant_id })
.then((response) => {
if (response.data.STATUS_CODE === '200') {
this.setState({
dataPromo: response.data.DATA,
loading: false
});
}
})
}
this is my child component
import React, { Component } from 'react'
import { MyContext } from './MerchantByPromo'
export class MerchantByPromoDetail extends Component {
constructor(props){
super(props)
this.state = {
detailPromo:[],
}
}
UNSAFE_componentWillMount(){
let value = this.context
console.log(value)
}
componentDidMount(){
}
render() {
return (
<MyContext.Consumer>
<p>tes</p>
</MyContext.Consumer>
)
}
}
I always get an error message like this "TypeError: render is not a function", what's the solution?
<MyContext.Consumer>
{() => <p>tes</p>}
</MyContext.Consumer>
change to this and Check
I am basically trying to set the state based on the response I got from an API.
api.js
const baseURL = "http://localhost:8000";
export const getStatus = (list) => {
fetch(`${baseURL}/api/status`).then(res => {
return res.json();
}).then(status => {
list.setState({status: status});
});
};
And this is how I call it from a component
import React, {PropTypes} from 'react';
import {getStatus} from '../../../api';
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
status: [],
};
}
componentDidMount() {
getStatus(this);
}
I feel like it is not a good practice to pass this down and modify the state from the downstream api file. Is there a more "react" way to do this?
I also tried another way, which is to wait for the callback to send back the response and then modify the state based on the response, but the setState function never gets executed in componentDidMount. If someone can direct me, that would be great!
api.js
const baseURL = "http://localhost:8000";
export const getStatus = () => {
fetch(`${baseURL}/api/status`).then(res => {
return res.json();
}).then(status => {
return status;
});
};
import React, {PropTypes} from 'react';
import {getStatus} from '../../../api';
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
status: [],
};
}
componentDidMount() {
getStatus((status) => {
this.setState({status: status});
})
}
Better way is to use .then() in componentDidMount
api.js
export const getStatus = () => {
return fetch(`${baseURL}/api/status`).then(res => {
return res.json();
});
};
yourComponent.jsx
import React, {PropTypes} from 'react';
import {getStatus} from '../../../api';
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
status: [],
};
}
componentDidMount() {
getStatus()
.then(status => {
this.setState({status: status});
})
}
Making an API call and setting the state of a component based on what the call returns is normal practice in react. You don't need to pass a reference to this down to getStatus and for that matter you don't need to pass anything to getStatus. Instead, chain then off of what is returned from getStatus.
componentDidMount() {
getStatus()
.then(status => {
this.setState({status});
})
}
it is also unnecessary to call constructor or super in your component. Simply write:
class List extends React.Component {
state = {
status: []
}
}
If you are using ES6, try the async function syntax to increase readability.
api.js
export const getStatus = () => fetch(`${baseURL}/api/status`);
yourComponent.jsx
import React, {PropTypes} from 'react';
import {getStatus} from '../../../api';
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
status: [],
};
}
async componentDidMount() {
const res = await getStatus()
this.setState({status: res.json()});
}
Also, you might not need to initialize the state, and you could remove the constructor if so.
I have a working code:
fetch(serviceUrl)
.then(result => result.json())
.then(newStatus => this.setState({status: newStatus}))
.catch(error => {
console.log(error);
this.setState({status: 'error'});
})