In my React app, I have two Components, Main and Menu Component. Main Component is the parent component of Menu. Menu shows a list of items and upon clicking one of the item, it updates Main's state with the help of a function that I pass as props to Menu. Below is the code for better understanding:
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
dishes: dishes,
selectedDish: null
};
this.selectDish=this.selectDish.bind(this);
}
selectDish(dishId){
this.setState({ selectedDish: dishId});
}
render() {
return (
<div className="App">
<Menu dishes={this.state.dishes} onClick={this.selectDish} />
</div>
);
}
}
export default Main;
And below is the Menu Component:
class Menu extends Component {
constructor(props){
super(props);
this.selectdish=this.selectdish.bind(this);
}
selectdish(dishId){
return this.props.onClick(dishId);
}
render() {
const menu = this.props.dishes.map((dish) => {
return (
<div className="col-12 col-md-5 m-1">
<Card key={dish.id}
onClick={this.selectdish(dish.id)}>
</Card>
</div>
);
});
}
}
export default Menu;
I have omissed some irrelevant parts of the code.
So the workflow should be that when we click over one of dishes rendered by the Menu, that dish's id should be passed back to Main and update the state variable ```selectedDish````, as seen in the method selectDish.
But in the browser console, I get the error Cannot update during existing state transition.
The weird thing is that if I don't pass any dish id and set the selectedDish to a fixed value like 1, everything works fine.
Please help me guys to identify if there is any problem in my event handler, because that is the only part that seems to contain an error.
Thank You!
You are not passing the onClick of the Card a function but you are already calling that function with this.selectdish(dish.id). This will initiate the flow on render, not on click.
You have three options here.
You either wrap that in an additional function like this: onClick={() => {this.selectdish(dish.id)}}>. That way, you are passing a function to the onClick, which is required, and not executing that.
Or you make selectdish return a function like this:
selectdish(dishId){
return () => {this.props.onClick(dishId)};
}
Or you add a name to the card and access the name of the element from the click event:
class Menu extends Component {
constructor(props){
super(props);
this.selectdish=this.selectdish.bind(this);
}
selectdish(event){
return this.props.onClick(event.target.name);
}
render() {
const menu = this.props.dishes.map((dish) => {
return (
<div className="col-12 col-md-5 m-1">
<Card key={dish.id}
name={dish.id}
onClick={this.selectdish}> // this is now the function without calling it
</Card>
</div>
);
});
}
}
export default Menu;
You have some problem in your code:
property key must be define in root <div>
render method in Menu component returns nothing
-
class Menu extends React.Component {
constructor(props) {
super(props);
this.selectdish = this.selectdish.bind(this);
}
selectdish(dishId) {
return this.props.onClick(dishId);
}
render() {
return this.props.dishes.map(dish => {
return (
<div className="col-12 col-md-5 m-1" key={dish.id}>
<div onClick={() => this.selectdish(dish.id)}>{dish.id}</div>
</div>
);
});
}
}
See my playground where I fixed this issues:
https://codesandbox.io/s/react-playground-ib6pr?file=/index.js
Related
I'm super new to react, this is probably a terrible question but I'm unable to google the answer correctly.
I have a component (CogSelector) that renders the following
import React from "react"
import PropTypes from "prop-types"
import Collapsible from 'react-collapsible'
import Cog from './cog.js'
const autoBind = require("auto-bind")
import isResultOk from "./is-result-ok.js"
class CogSelector extends React.Component {
constructor(props) {
super(props)
this.state = {
docs: null,
loaded: false,
error: null
}
autoBind(this)
}
static get propTypes() {
return {
selectCog: PropTypes.func
}
}
shouldComponentUpdate(nextProps, nextState){
if (nextState.loaded === this.state.loaded){
return false;
} else {
return true;
}
}
componentDidMount() {
fetch("/api/docs")
.then(isResultOk)
.then(res => res.json())
.then(res => {
this.setState({docs: res.docs, loaded: true})
}, error => {
this.setState({loaded: true, error: JSON.parse(error.message)})
})
}
render() {
const { docs, loaded, error } = this.state
const { selectCog } = this.props
if(!loaded) {
return (
<div>Loading. Please wait...</div>
)
}
if(error) {
console.log(error)
return (
<div>Something broke</div>
)
}
return (
<>
Cogs:
<ul>
{docs.map((cog,index) => {
return (
<li key={index}>
<Cog name={cog.name} documentation={cog.documentation} commands={cog.commands} selectDoc={selectCog} onTriggerOpening={() => selectCog(cog)}></Cog>
</li>
// <li><Collapsible onTriggerOpening={() => selectCog(cog)} onTriggerClosing={() => selectCog(null)} trigger={cog.name}>
// {cog.documentation}
// </Collapsible>
// </li>
)
})}
{/* {docs.map((cog, index) => { */}
{/* return ( */}
{/* <li key={index}><a onClick={() => selectCog(cog)}>{cog.name}</a></li>
)
// })} */}
</ul>
</>
)
}
}
export default CogSelector
the collapsible begins to open on clicking, then it calls the selectCog function which tells it's parent that a cog has been selected, which causes the parent to rerender which causes the following code to run
class DocumentDisplayer extends React.Component{
constructor(props) {
super(props)
this.state = {
cog: null
}
autoBind(this)
}
selectCog(cog) {
this.setState({cog})
}
render(){
const { cog } = this.state
const cogSelector = (
<CogSelector selectCog={this.selectCog}/>
)
if(!cog) {
return cogSelector
}
return (
<>
<div>
{cogSelector}
</div>
<div>
{cog.name} Documentation
</div>
<div
dangerouslySetInnerHTML={{__html: cog.documentation}}>
</div>
</>
)
}
}
export default DocumentDisplayer
hence the cogSelector is rerendered, and it is no longer collapsed. I can then click it again, and it properly opens because selectCog doesn't cause a rerender.
I'm pretty sure this is just some horrible design flaw, but I would like my parent component to rerender without having to rerender the cogSelector. especially because they don't take any state from the parent. Can someone point me to a tutorial or documentation that explains this type of thing?
Assuming that Collapsible is a stateful component that is open by default I guess that the problem is that you use your component as a variable instead of converting it into an actual component ({cogSelector} instead of <CogSelector />).
The problem with this approach is that it inevitably leads to Collapsible 's inner state loss because React has absolutely no way to know that cogSelector from the previous render is the same as cogSelector of the current render (actually React is unaware of cogSelector variable existence, and if this variable is re-declared on each render, React sees its output as a bunch of brand new components on each render).
Solution: convert cogSelector to a proper separated component & use it as <CogSelector />.
I've recently published an article that goes into details of this topic.
UPD:
After you expanded code snippets I noticed that another problem is coming from the fact that you use cogSelector 2 times in your code which yields 2 independent CogSelector components. Each of these 2 is reset when parent state is updated.
I believe, the best thing you can do (and what you implicitly try to do) is to lift the state up and let the parent component have full control over all aspects of the state.
I solved this using contexts. Not sure if this is good practice but it certainly worked
render() {
return (
<DocContext.Provider value={this.state}>{
<>
<div>
<CogSelector />
</div>
{/*here is where we consume the doc which is set by other consumers using updateDoc */}
<DocContext.Consumer>{({ doc }) => (
<>
<div>
Documentation for {doc.name}
</div>
<pre>
{doc.documentation}
</pre>
</>
)}
</DocContext.Consumer>
</>
}
</DocContext.Provider>
)
}
then inside the CogSelector you have something like this
render() {
const { name, commands } = this.props
const cog = this.props
return (
//We want to update the context object by using the updateDoc function of the context any time the documentation changes
<DocContext.Consumer>
{({ updateDoc }) => (
<Collapsible
trigger={name}
onTriggerOpening={() => updateDoc(cog)}
onTriggerClosing={() => updateDoc(defaultDoc)}>
Commands:
<ul>
{commands.map((command, index) => {
return (
<li key={index}>
<Command {...command} />
</li>
)
}
)}
</ul>
</Collapsible>
)}
</DocContext.Consumer>
)
}
in this case it causes doc to be set to what cog was which is a thing that has a name and documentation, which gets displayed. All of this without ever causing the CogSelector to be rerendered.
As per the reconciliation algorithm described here https://reactjs.org/docs/reconciliation.html.
In your parent you have first rendered <CogSelector .../> but later when the state is changed it wants to render <div> <CogSelector .../></div>... which is a completely new tree so react will create a new CogSelector the second time
I use a component called "Modal" that I want to make global so I can use it in any other component. Modal will be used in all the components that need it.
My problem is that now the onclick {this.props.stateModal} in Widgets does not work and show nothing.
This is my Widgets.js
class Widgets extends Component {
render(){
return (
<aside className="widgets">
<div id="bq-datos">
<span>Todas tus campañas</span>
<a onClick={this.props.stateModal} className="content-datos orange" data-bq-datos="999"><div>Llamadas <span>ENTRANTES</span></div></a>
<a className="content-datos violet" data-bq-datos="854"><div>Llamadas <span>SALIENTES</span></div></a>
</div>
{
this.props.isModalOpen
? (
<Modal
stateModal = {this.props.stateModal}
isModalOpen={this.props.isModalOpen} >
<ModalWidgets/>
</Modal>
)
: null
}
<Comunicacion/>
</aside>
);
}
}
I need {this.props.stateModal} to work on my Modal component (in Modal.js)
This is my Modal.js with code for {this.props.stateModal} but not works.
import React, { Component } from 'react';
class Modal extends Component {
constructor(props) {
super(props);
this.state = {
isModalOpen: false,
};
this.stateModal = this.stateModal.bind(this);
}
stateModal() {
this.setState({
isModalOpen: !this.state.isModalOpen
});
alert('¡Ohhhh');
}
render(){
if(this.props.isOpen){
return (
<div id="modal">
{this.props.children}
<ModalWidgets/>
</div>
);
} else {
return null;
}
}
}
class ModalWidgets extends Component {
render(){
if(this.props.isModalOpen){
return(
<article id="md-descansos" className="medium">
hola tú!!
</article>
);
}
else{
return(
<div>k pasa!</div>
);
}
}
}
export default Modal;
I think that i need do something in my Modal.js but i don't know what it is
Edit:
I have changed the components to use Modal as the parent of all the other Modal that I want to use, such as ModalWidgets. But now when you click on the button of {this.props.stateModal} in Widgts not works.
Thanks!
You have to use stateModal function somewhere in your Modal component. Something like:
render(){
if(this.props.isOpen){
return (
<ModalGenerico>
<div id="modal">
<button type="button" onClick={this.stateModal}>Click here</button>
{this.props.children}
</div>
</ModalGenerico>
);
} else {
return <ModalGenerico />;
}
}
The button in the example above should be replaced with your backdrop of the modal (or anything else as you like).
Edited: You should take a look at this article State vs Props. Because I notice that you weren't clear the usage of them.
Besides, I don't think there's such thing called global component as you described. Every components in react are reusable and can be imported anywhere in the project.
I'm having problems with my first React application.
In practice, I have a hierarchy of components (I'm creating a multimedia film gallery) which, upon clicking on a tab (represented by the Movie component) must show the specific description of the single film (SingleMovieDetails).
The problem is that the DOM is updated only on the first click, then even if the SingleMovieDetails props change, the DOM remains locked on the first rendered movie.
Here's the code i wrote so far...
//Movie component
import React from "react";
import styles from "./Movie.module.scss";
import PropTypes from "prop-types";
class Movie extends React.Component{
constructor(props){
super(props);
this.imgUrl = `http://image.tmdb.org/t/p/w342/${this.props.movie.poster_path}`;
}
render(){
if(!this.props.size)
return <div onClick={this.props.callbackClick(this.props.movie.id)}
name={this.props.movie.id}
className={styles.movieDiv}
style={{backgroundImage: `url(${this.imgUrl})`}}></div>;
return <div onClick={() => this.props.callbackClick(this.props.movie.id)}
name={this.props.movie.id}
className={styles.movieDivBig}
style={{backgroundImage: `url(${this.imgUrl})`}}></div>;
}
}
Movie.propTypes = {
movie: PropTypes.any,
callbackClick: PropTypes.any
};
export default Movie;
SingleMovieDetails.js
import React from "react";
import styles from "./SingleMovieDetails.module.scss";
import Movie from "../Movie";
import SingleMovieDescription from "../SingleMovieDescription";
import MovieCast from "../MovieCast";
import SingleMovieRatings from "../SingleMovieRatings";
class SingleMovieDetails extends React.Component{
constructor(props){
super(props);
console.log(props);
this.state = props;
console.log('constructor', this.state.movie)
}
render(){
console.log('SMD', this.state.movie)
return (
<>
<div className={styles.container}>
<div className={styles.flayer}>
<Movie size={'big'} movie={this.state.movie}/>
</div>
<div className={styles.description}>
<SingleMovieDescription movie={this.state.movie}/>
<MovieCast></MovieCast>
</div>
<div className={styles.ratings}>
<SingleMovieRatings />
</div>
</div>
</>
);
}
}
export default SingleMovieDetails;
MovieCarousel.js
import React from "react";
import PropTypes from "prop-types";
import Movie from "../Movie";
import styles from "./MovieCarousel.module.scss";
import SingleMovieDetails from "../SingleMovieDetails";
class MovieCarousel extends React.Component {
constructor(props) {
super(props);
this.state = [];
this.callbackClickMovie = this.callbackClickMovie.bind(this);
}
callbackClickMovie(id) {
const singleMovieApi = `https://api.themoviedb.org/3/movie/${id}?api_key=b6f2e7712e00a84c50b1172d26c72fe9`;
fetch(singleMovieApi)
.then(function(response) {
return response.json();
})
.then(data => {
this.setState({ selected: data });
});
}
render() {
let details = null;
if (this.state.selected) {
details = <SingleMovieDetails movie={this.state.selected} />;
}
let counter = 6;
let movies = this.props.movies.map(el => {
let element = (
<Movie movie={el} callbackClick={this.callbackClickMovie} />
);
counter--;
if (counter >= 0) return element;
return;
});
let content = (
<>
<h2 className={styles.carouselTitle}>{this.props.title}</h2>
{movies}
{details}
</>
);
return content;
}
}
MovieCarousel.propTypes = {
children: PropTypes.any
};
export default MovieCarousel;
I would be really grateful if someone could help me. I have been on it for two days but I can't really deal with it
This is because in SingleMovieDetails component, you are storing the props values in state and not updating the state on props change. constructor will not get called again so state will always have the initial values.
You have two options to solve the issue:
Directly use the props values instead of storing data in state (preferred way). Like this:
class SingleMovieDetails extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<>
<div className={styles.container}>
<div className={styles.flayer}>
<Movie size={'big'} movie={this.props.movie}/>
</div>
<div className={styles.description}>
<SingleMovieDescription movie={this.props.movie}/>
<MovieCast></MovieCast>
</div>
<div className={styles.ratings}>
<SingleMovieRatings />
</div>
</div>
</>
);
}
}
Use getDerivedStateFromProps, and update the state value on props change.
Same issue with Movie component also, put this line in the render method, otherwise it will always show same image:
const imgUrl = `http://image.tmdb.org/t/p/w342/${this.props.movie.poster_path}`
And use this imgUrl variable.
your Problem is just related to one file: SingleMovieDetails.js
Inside the constructor you´re setting the component state to get initialized with the props (send to the component the first time)
But inside your render() method you are referencing that state again:
<Movie size={'big'} movie={this.state.movie}/>
All in all thats not completely wrong, but you need to do one of two things:
Add a method to update your component state with the nextPropsReceived (Lifecycle Method was called: will receive props, if you are using the latest version you should use: getDerivedStateFromProps)
preferred option: you dont need a state for the movie component, so just use the props inside the render function (this.props.movie)
afterwards you can also delete the constructor, because there is nothing special inside. :)
edit:
So, just to be clear here: Since you´re only setting the state once (the constructor is not called on every lifecycle update) you will always only have the first value saved. Changing props from outside will just trigger render(), but wont start the constructor again ;D
currently working on react. I have two components lets say ad and home . Inside home components i have one image and on click event of that image i want to render ad inside home component below image. Is there any simples method . thank you!
check this. i think this is what you want
//dynamically generate div
let DynamicDiv=()=>{
return (<div>
<p>i am here</p>
</div>)
}
class App extends Component {
constructor(props){
super(props)
this.state={
visible:false //visibility of Component
}
this.divVisiblity=this.divVisiblity.bind(this) //function is bind when user clicks on pic
}
divVisiblity(){
//this function will get called when user clicks on function
this.setState(()=>{return {visible:!this.state.visible}}) //changes visible state of Component when user clicks
}
render() {
return (
<div>
<div className="App">
{/* onClick is assigned function named divVisiblity */}
<img onClick={this.divVisiblity} src="https://placekitten.com/g/200/300" alt="test"/>
{/*this is ternary if else statement in js */}
{/* if visible = true ,display Component else dont */}
<div>
{this.state.visible && <DynamicDiv/>}
</div>
);
}
}
I think that will help to you.
export default class HomeComponent extends Component<Props> {
constructor(props) {
super(props);
this.state = {
renderAdComponent: false
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler() {
this.setState({renderAdComponent: !this.state.renderAdComponent})
}
render() {
return (
<View>
<Image onClick={this.onClickHandler}/>
{this.state.renderAdComponent ? <AdComponent/> : null}
</View>
);
}
}
What #sdkcy suggested is okay but the ternary operator isn't really needed. You can do the following
{ this.state.isAdShown && <ComponentToShow /> }
This gets rid of the useless : null result.
I've got a nest of react components (its a modal).
<Modal> // Grandparent
<Footer> // Parent
<Button label="Click Me" clickAction={
() => {
/* So what goes here? */
}
}/> //Child
</Footer>
</Modal>
Modal passes all children the close modal function. This function gets passed again to all children of footer.
export default class Footer extends React.Component {
constructor(props) {
super(props);
this.handleClose = this.handleClose.bind(this);
}
handleClose() {
this.props.closeModal();
}
render() {
const childrenWithProps = React.Children.map(this.props.children,
(child) => React.cloneElement(child, {
closeModal: this.handleClose
})
);
return (
<footer}>
{childrenWithProps}
</footer>
);
}
}
The internals of the button class look like this:
constructor(props) {
super(props);
this.click = this.click.bind(this);
}
click() {
this.props.clickAction();
}
render() {
return <a
href='#'
onClick={this.click}
>
{this.props.label}
</a>
}
What I don't want to do is bake the close method right into the button since it's supposed to only have access to it when it's a child of modal footer.
So the thing I'm trying to solve (see the first code block) is what goes in the /* So what goes here? */ area. Using 'this' just returns 'undefined' so I'm having trouble reaching my closeModal method.
Any suggestions?
Ah figured out the problem. The arrow functions didn't have 'this' bound.
<Button label="Click Me" clickAction={
// Nope
// () => {this.closeModal()}
// Yup
function(){this.closeModal()}
}/>
You might consider only passing props to components that need to use them. In other words, just explicitly pass the closeModal function to your button as a prop:
function Footer (props) {
return (
<footer>{props.children}</footer>
)
}
function Button (props) {
return (
<a href='#' onClick={props.onClick}>{props.label}</a>
)
}
class Modal extends React.Component {
constructor (props) {
super(props)
this.closeModal = this.closeModal.bind(this)
}
closeModal () {
/* implement me */
}
render () {
return (
<div>
<Footer>
<Button label='Click Me' onClick={this.closeModal} />
</Footer>
</div>
)
}
This way, you don't have to think about the context of the button, i.e. which props are being "invisibly" passed to it via a parent pattern like the cloneElement technique you've used. Some components which are children of Footer might not need access to closeModal, so there's no reason to pass it to them.