I have problem with sending information from one component child to parent and from parent to other component. There is 3 components main (App.js), and two child (MoviePoster.js and MovieDescription.js). In App.js we have state named ID. I send function with setState to MoviePoster where I use it in onClick. Function seting id to our state. App.js send props with id to MovieDescription. In MovieDescription I use API to download information about film based on his ID. Clicking on MoviePoster should change ID and send to MovieDescription, after that MovieDescription should render information about this film. I must click three times on MoviePoster to render new information about film and i can't understand why it's working after third click and doesn't work after first click. Here is my code:
App.js
First I tried to save all information from API to couple of states. But in the end it causes inifinite loop with rendering the elements. So I change state to global variables.
Second I tried to put downloading from API to Component Life Circle Methods like in code.
class App extends Component {
constructor(props) {
super(props)
this.updateId = this.updateId.bind(this)
}
state = {
response: '',
post: '',
responseToPost: '',
id: 438808
};
updateId = (id) => {
this.setState({id: id})
}
render() {
return (
<div className="App">
<MoviePoster updateId = {this.updateId} />
<MovieDescription id = {this.state.id} />
</div>
);
}
}
MoviePoster.js
class MoviePoster extends React.Component{
state = {
images: [],
title: []
}
handleImgClick = (id) => {
this.props.updateId(id);
}
componentDidMount(){
this.getMovie();
}
getMovie = ()=>{
axios.get('https://api.themoviedb.org/3/movie/upcoming?api_key=332654c71ccbb479020bcc047eaa43e8&language=en-US&page=1®ion=pl')
.then(res=>{
this.setState({
images: res.data.results.slice(0,4)
})
})
}
render(){
const { images } = this.state;
const posters = images.map( image => {
return(
<div onClick={() => this.handleImgClick(image.id)} className='poster-con'key={image.id}>
<div className='poster-s-con' >
<img className="poster responsive-img" src ={`http://image.tmdb.org/t/p/w300${image.poster_path}`} alt='img'/>
<h5 className='movie-title'>{image.title}</h5>
</div>
</div>
)
})
return(
<div className='movie-big-con white-text'>
<div className='movie-small-con'>
<div className='movie-con'>{posters}</div>
</div>
</div>
)}
};
export default MoviePoster;
MovieDescription.js
let data = {};
let data1 = {};
class MovieDescription extends React.Component {
componentWillMount() {
fetch(`https://api.themoviedb.org/3/movie/${this.props.id}?api_key=${api_key}&language=en-US`)
.then(response => response.json())
.then(json => {
data = json;
});
fetch(`https://api.themoviedb.org/3/movie/${this.props.id}/credits?api_key=${api_key}`)
.then(response => response.json())
.then(json => {
data1 = json;
});
}
componentWillUpdate() {
fetch(`https://api.themoviedb.org/3/movie/${this.props.id}?api_key=${api_key}&language=en-US`)
.then(response => response.json())
.then(json => {
data = json;
});
fetch(`https://api.themoviedb.org/3/movie/${this.props.id}/credits?api_key=${api_key}`)
.then(response => response.json())
.then(json => {
data1 = json;
});
}
render() {
return(
<div>{data.overview}</div>
<div>{data.runtime}</div>
);
}
}
I want to click once on poster and have rerendered component MovieDescription with new content download from API. I dont have idea what is wrong.
The best part in react is state which you are not utilizing it in MovieDescription component so move data and data1 variables under component state so that whenever you do setState the component will re render with updated data. With your current MovieDescription component code though you do fetch calls assign data to variables won’t re render the component because you are overriding a variable so it won’t trigger re render.
Also use componentDidMount instead of componentWillMount method. As you know componentWillMount is deprecated in react v16 version.
Here is the updated MovieDescription component code
class MovieDescription extends React.Component {
constructor(props){
super(props);
this.state = {
data:{},
data1: {}
}
}
componentDidMount() {
fetch(`https://api.themoviedb.org/3/movie/${this.props.id}?api_key=${api_key}&language=en-US`)
.then(response => response.json())
.then(json => {
this.setState({data : json});
});
fetch(`https://api.themoviedb.org/3/movie/${this.props.id}/credits?api_key=${api_key}`)
.then(response => response.json())
.then(json => {
this.setState({data1: json});
});
}
componentWillUpdate() {
fetch(`https://api.themoviedb.org/3/movie/${this.props.id}?api_key=${api_key}&language=en-US`)
.then(response => response.json())
.then(json => {
this.setState({data : json});
});
fetch(`https://api.themoviedb.org/3/movie/${this.props.id}/credits?api_key=${api_key}`)
.then(response => response.json())
.then(json => {
this.setState({data1 :json});
});
}
render() {
return(
<div>{data.overview}</div>
<div>{data.runtime}</div>
);
}
}
in MovieDescription, put the data objects in the state. when you get the json back from the fetches uses setState({}) to update the data. I think that should fix your problem. Ideally you would do all of this in the main component and pass the data as props to the child components. That way you don't have to worry about the states in three objects, just one.
Related
I have an api which i want to filter the data and place the filterd into a state
export default class ModifyPage_Single extends React.Component {
constructor(props) {
super(props)
this.state = {data:[],idd:""}
}
componentWillMount() {
fetch("removed api")
.then(response => response.json())
.then((data) =>{
this.setState({data:data})
})
}
render() {
const test = this.state.data.map((e) => { if(e.ID === this.props.location.productdetailProps.productdetail) {this.setState({idd:e.PP})} })
But i keep getting this error Unhandled Rejection (Error): Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
How can i solve so that the fitered out api goes into the state?
Thanks in advance
you should update in componentMount, not in render():
export default class ModifyPage_Single extends React.Component {
constructor(props) {
super(props);
this.state = { data: [], idd: "" };
}
componentWillMount() {
fetch("removed api")
.then((response) => response.json())
.then((data) => {
this.setState({ data: data });
data.forEach((e) => {
if (e.ID === this.props.location.productdetailProps.productdetail) {
this.setState({ idd: e.PP });
}
});
});
}
render() {
return null;
}
}
You can update the state in lifecycle methods, updating it in render is anti pattern
componentDidMount() {
fetch("removed api")
.then(response => response.json())
.then((data) =>{
this.setState({data:data})
const iddObj = data.find((el) => el.ID === this.props.location.productdetailProps.productdetail)
if(iddObj ){
this.setState({idd:iddObj.PP})
}
})
}
I want to create an app with comments feature. I am trying with the code like this:
response.data.forEach((el, idx, arr) => {
const newMessage = <CommentMessage username={el.username} message={el.message}/>
ReactDOM.render(newMessage, this.commentListRef.current)
})
I am using MySQL. Axios for HTTP Requests. And Next.js for the framework.
Full code:
import React from 'react'
import ReactDOM from 'react-dom'
import styles from './comments-list.module.css'
class CommentMessage extends React.Component {
constructor(props) {
super(props)
}
render() {
return (<div>
<b>{this.props.username}</b>
<span>: </span>
<span>{this.props.message}</span>
</div>)
}
}
class CommentsList extends React.Component {
constructor(props) {
super(props)
this.commentListRef = React.createRef()
const comments = []
}
loadComments() {
const axios = require('axios')
axios.get('/api/getcomments')
.then(response => {
response.data.forEach((el, idx, arr) => {
const newMessage = <CommentMessage username={el.username} message={el.message}/>
ReactDOM.render(newMessage, this.commentListRef.current)
})
})
.catch(err => {
console.log(err)
})
}
render() {
return (<div ref={this.commentListRef} onLoad={this.loadComments()}>
</div>)
}
}
export default CommentsList
But it only render this:
Expected this:
You're going about this pretty strangely; I don't know if that's on purpose or not. Regardless, the recommended approach would be to store the comments as part of your component's state, and update the state when you get the comments.
Like this:
class CommentsList extends React.Component {
constructor(props) {
super(props)
this.state = {
comments: []
};
this.commentListRef = React.createRef()
const comments = []
}
loadComments() {
const axios = require('axios')
axios.get('/api/getcomments')
.then(response => {
this.setState({
comments: response.data
});
})
.catch(err => {
console.log(err)
})
}
componentDidMount(){
this.loadComments();
}
render() {
return (<div ref={this.commentListRef}>
(this.state.comments.map(comment => (
<CommentMessage username={comment.username} message={comment.message}/>
)))
</div>)
}
}
Also, your onLoad wasn't working as you had expected. It will call loadComments every time the component renders, and I don't even know if onLoad is a proper event on a div.
At any rate, if you absolutely wanted to do it the way you did it, you would have to mount each node into its own container. As you have it right now, each comment is overwriting the contents of commentListRef. So you'd have to create a new element, append that to commentListRef, and mount the react component to that:
loadComments() {
const axios = require('axios')
axios.get('/api/getcomments')
.then(response => {
response.data.forEach((el, idx, arr) => {
const element = document.createElement('div');
this.commentListRef.current.appendChild(element);
const newMessage = <CommentMessage username={el.username} message={el.message}/>
ReactDOM.render(newMessage, element)
})
})
.catch(err => {
console.log(err)
})
}
ReactDOM.render will only render one component for a given container. From the docs:
Any existing DOM elements inside are replaced when first called. Later calls use React’s DOM diffing algorithm for efficient updates.
Basically when you call ReactDOM.render in a loop, React is treating each given component as an update to the previous component, rather than rendering each individually.
Best practice is to render a single component at the root container (usually called <App>). However it seems you've already done this as these ReactDOM.render calls are happening within another component. Generally, you should only need to use ReactDOM.render once within an app.
Instead you can store the data in the CommentsList component's state and just return the children components from the parent's render method.
For example:
class CommentsList extends React.Component {
constructor(props) {
super(props)
this.state = {
comments: [],
}
}
loadComments = () => {
const axios = require('axios')
axios.get('/api/getcomments')
.then(response => {
this.setState(prev => ({...prev, comments: response.data}));
})
.catch(err => {
console.log(err)
})
}
render() {
const { comments } = this.state;
return (
<React.Fragment>
{comments.map(e => (
<CommentMessage key={e.id} username={e.username} message={e.message}/>
))}
</React.Fragment>
)
}
}
Note: I've also passed a key to the CommentMessage component to give each child a stable identity (see docs for more info). Had to guess, but I assume a comment would have an id value, if not, you can choose a different unique value for the comment to use as a key.
Also I'd recommend moving to React Hooks over class-based components—a lot easier to work with once you get a grasp on hooks.
I've seen examples that show how to pass props from a child to its parent with a onClick on onChange event on the child component, but am struggling to figure out how to pass props up passively.
What i'd like is to have the child component that performs a fetch operation and then passes the response up to the parent.
// PARENT component
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
homeLink: 'inital'
}
}
handleNamechange(newName) {
this.setState({
homeLink: newName
})
}
render() {
return(
<section>
<h1>{this.state.homeLink}</h1>
<GetUserComponent
changeLink={this.handleNamechange.bind(this)}
/>
</section>
)
}
}
export default App;
And the part I struggle with is sending the props up to the parent WITHOUT the onClick, and just pass the props once the fetch is complete
// CHILD Component
class GetUserComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
homeLink: 'xxxx'
}
}
componentDidMount() {
fetch('https://someapi/getusername', {
})
.then(function(response) {
return response.json()
})
.then((data) => {
this.setState(
{ username: data }
)
})
}
onChangeLink() {
this.props.changeLink(this.state.homeLink)
}
render() {
return (
<div>
<span onClick={this.onChangeLink.bind(this)}
>Change Header Link</span>
</div>
)
}
}
export default GetUserComponent;
I'm not sure if I'm doing this wrong, or whether this simply can't be done and you HAVE to use the click event, but either way would really appreciate your help.
You have to call the parents function:
componentDidMount() {
fetch('https://someapi/getusername', {
})
.then(function(response) {
return response.json()
})
.then((data) => {
this.props.changeLink(data);
})
}
It will then execute the handleNamechange function and update the parents state.
In your case, I think the parent must do the fetch, and give as props the result to the children.
If you really need the child fetch the data, you have to call the callback changeLink given as a props from the parent to the child as it :
componentDidMount() {
fetch('https://someapi/getusername', {
})
.then(function(response) {
return response.json()
})
.then((data) => {
this.setState(
{ username: data }
)
this.props.changeLink(data)
})
}
I'm trying to pass data from my database to a page in my react project. The database stores the user data and the data is called with validateCookie() function. I'm getting data from the validateCookie function but I can't seem to get the data out of the function to the main page so I can use it to update the user's state and calendar and return that to update their information in the database.
The setState is not sending data to the page state. I've tried so much but I'm still new to react so I'm a bit out of my league
import ScheduleSelector from 'react-schedule-selector'
import React, { Component } from 'react';
import Moment from 'moment';
import { Row, Col, Button } from 'react-bootstrap';
import API from '../../utils/API';
class Availability extends Component {
constructor(props) {
super(props);
this.state = {
user: [],
email: "",
calendar: [],
schedule: [],
}
// this.handleInputChange = this.handleInputChange.bind(this);
// this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
this.validateCookie();
console.log(this.state.user); // coming back empty because validate cookie is not passing data upstream
}
handleSubmit = (event) => {
event.preventDefault();
// let schedule = this.state.schedule;
// // alert("Your availability has been submitted successfully!");
// let ISOschedule = this.state.schedule.map(date => Moment(date).toISOString());
// let newCalendar = this.state.schedule
console.log(this.state.user);
API.updateAvailability(
this.state.user.email,
this.state.user.calendar,
this.state.user.schedule)
.then(r => {
console.log(r);
}).catch(e => {
console.log(e);
})
}
handleChange = newSchedule => {
this.setState({ schedule: newSchedule.map(date => Moment(date).toISOString()) })
}
validateCookie() {
API.validateCookie()
.then(res => res.json())
.then(res => {this.setState({ user: res})})
.then(res => {
console.log(this.state) // coming back with loading data aka empty
console.log(this.state.user) // coming back with all appropriate data
})
.catch(err => console.log(err));
console.log(this.state.user) // coming back empty
}
render() {
return (
<div>
<form ref="form" onSubmit={this.handleSubmit}>
<ScheduleSelector
selection={this.state.schedule}
numDays={7}
minTime={0}
maxTime={23}
onChange={this.handleChange}
/>
<Row>
<Col>
<Button type="submit" className="float-right">Submit Availability</Button>
</Col>
</Row>
</form>
</div>
)
}
}
export default Availability;
I think the problem is that in your validateCookie method, you are expecting the state to change as soon as you call the setState function. It is important to know that setState() does not immediately mutate this.state but creates a pending state transition.
Refer to this answer for more information.
One solution could be to check when this.state actually gets updated before you render anything in your render function.
Just like Swanky said, the setState() doesn't update immediately and you can listen for state change and re-render the UI. I have done some cleaning up to your setState below;
validateCookie = () => {
API.validateCookie()
.then(res => res.json())
.then(res => {
this.setState({...this.state, user: res.user})
console.log(this.state.user);
})
.catch(err => console.log(err));
}
I called an API using fetch inside a componentDidMount but i need the API URL to change with the state I set in a textInput.
I can see my state is changing in console.log() but it seems it's not changing inside the componentDidMount.
constructor(props) {
super(props)
this.state = {
codigoCarro: "", //this is the state i need to change
}
}
componentDidMount() {
fetch
(`https://parallelum.com.br/fipe/api/v1/carros/marcas/${this.state.codigoCarro}/modelos`) //i need the state to change here
.then((response) => response.json())
.then((responseJson) => {
this.setState({
modeloCarro: responseJson.modelos
})
})
.catch((error) => {
console.log(error)
})
}
render() {
return (
<Text>Please insert the number informed in the input below</Text>
<TextInput
placeholder="Código da marca"
onChangeText={(codigoCarro) => {
this.setState({ codigoCarro });
}}
/>
);
}
It might be worth reading over the React documentation for componentDidMount() as this function is only called once the component is inserted into the dom tree meaning it will not be called every time your state updates.
You might be looking for the componentDidUpdate() function which is called after each render, however, I would still read the documentation as you may introduce excessive netowrk requests by changing this.
The final product may look something like this:
componentDidUpdate(prevProps, prevState) {
if(prevState.codigoCarro == this.state.codigoCarro) return;
fetch
(`https://parallelum.com.br/fipe/api/v1/carros/marcas/${this.state.codigoCarro}/modelos`) //i need the state to change here
.then((response) => response.json())
.then((responseJson) => {
this.setState({
modeloCarro: responseJson.modelos
})
})
.catch((error) => {
console.log(error)
})
}