Forwarding state vs forwarding props to child component - javascript

I have problem with forwarding state from parent to child component, these 2 components are class components.
When forwarding state from parent to child component, I want to use state showModal variable in child component as show as state variable:
this.state = {
show: this.props.show
}
This variable is being used to active the modal.
When I use it as this.props.show, the state has been forwarded to child component, and updated, but when I use props in this.state in child component it hasn't been updated. Has anyone idea where the problem is?
First - Parent component:
import React, { Component } from 'react';
import Modal from './UI/Modal';
class EnteredBooks extends Component {
constructor(props) {
super(props)
this.state = {
enteredBook: this.props.enteredBook,
showModal: false
}
}
detailsHandler = () => {
this.setState({
showModal: true
})
}
render() {
let show = this.state.showModal;
return (
<div>
<div className="product">
<img src="{this.props.enteredWatch.bookUrl}" />
<p>{this.props.enteredWatch.bookType}</p>
<p>euro{this.props.enteredWatch.bookPrice}</p>
<button
className="details-button"
onClick={this.detailsHandler}
>
Details
</button>
<Modal show={this.state.showModal} watch={this.state.enteredWatch} />
<button className="buy-button">Buy</button>
</div>
</div>
);
}
}
export default EnteredWatches;
Second - Child component:
import React, {Component} from 'react';
import classes from './Modal.css';
class Modal extends React.Component {
constructor(props) {
super(props)
this.state = {
book: this.props.book,
show: this.props.show
}
}
return(
<div>
<div className="Modal"
style={{
transform: this.state.show ? 'translateY(0)' : 'translateY(-100vh)',
opacity: this.state.show ? '1':'0'
}}>
<img src={this.state.book.bookUrl} />
<p>{this.state.book.bookType}</p>
<p>{this.state.book.watchUrl}</p>
<button className="details-button">Details</button>
<button className="buy-button">Buy</b
</div>
</div>
);
}
}
export default Modal;

constructor runs only once. It's suitable for calculating initial state. But this case is what getDerivedStateFromProps hook is for. It allows to calculate a state from props every time a component is updated, including initialization:
static getDerivedStateFromProps(props) {
return {
book: props.book,
show: props.show
};
}

I edited some parts of your code.I am not sure that you are asking this but hope it will enlight you for your problem.
First - Parent component:
import React, {
Component
} from 'react';
import Modal from './UI/Modal';
class EnteredBooks extends Component {
constructor(props) {
super(props)
this.state = {
enteredBook:"",
showModal: false
}
}
detailsHandler = () => {
this.setState({
showModal: true
enteredBook: this.props.enteredBook
})
}
render() {
let show = this.state.showModal;
return ( <
div >
<
div className = "product" >
<
img src = "{this.props.enteredWatch.bookUrl}" / >
<
p > {
this.props.enteredWatch.bookType
} < /p> <
p > euro {
this.props.enteredWatch.bookPrice
} < /p> <
button className = "details-button"
onClick = {
this.detailsHandler
} >
Details <
/button> <
Modal show = {
this.state.showModal
}
watch = {
this.state.enteredWatch
}
/> <
button className = "buy-button" > Buy < /button> <
/div> <
/div>
);
}
}
export default EnteredWatches;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Second - Child component:
import React, {
Component
} from 'react';
import classes from './Modal.css';
class Modal extends React.Component {
constructor(props) {
super(props)
this.state = {
book: "",
show: ""
}
}
componentWillReceiveProps(props) {
const {
book,
show
}: this.props;
if (book !== props.book || show !== props.show) {
this.setState({
book: props.book,
show: props.show
});
}
}
return ( <
div >
<
div className = "Modal"
style = {
{
transform: this.state.show ? 'translateY(0)' : 'translateY(-100vh)',
opacity: this.state.show ? '1' : '0'
}
} >
<
img src = {
this.state.book.bookUrl
}
/> <
p > {
this.state.book.bookType
} < /p> <
p > {
this.state.book.watchUrl
} < /p> <
button className = "details-button" > Details < /button> <
button className = "buy-button" > Buy < /b < /
div > <
/div>
);
}
}
PS : Also you can use componentWillReceiveProps(props) event for the
parent component.

Related

React onClick event in array.map()

Edit: I have included the full code for better clarity
I am not sure if this is possible. I am trying to pass an onClick event via props but the click event does not work.
The parent component looks like this:
import React from 'react'
import { getProductsById } from '../api/get'
import Product from './Product'
import { instanceOf } from 'prop-types'
import { withCookies, Cookies } from 'react-cookie'
class Cart extends React.Component {
static propTypes = {
cookies: instanceOf(Cookies).isRequired
}
constructor(props) {
super(props)
const { cookies } = props;
this.state = {
prods: cookies.get('uircartprods') || '',
collapsed: true,
total: 0,
}
this.expand = this.expand.bind(this)
this.p = [];
}
componentDidMount() {
this.getCartProducts()
}
handleClick = (o) => {
this.p.push(o.id)
const { cookies } = this.props
cookies.set('uircartprods', this.p.toString(), { path: '/' , sameSite: true})
this.setState({prods: this.p })
console.log('click')
}
getCartProducts = async () => {
let products = []
if (this.state.prods !== '') {
const ts = this.state.prods.split(',')
for (var x = 0; x < ts.length; x++) {
var p = await getProductsById(ts[x])
var importedProducts = JSON.parse(p)
importedProducts.map(product => {
const prod = <Product key={product.id} product={product} handler={() => this.handleClick(product)} />
products.push(prod)
})
}
this.setState({products: products})
}
}
expand(event) {
this.setState({collapsed: !this.state.collapsed})
}
handleCheckout() {
console.log('checkout clicked')
}
render() {
return (
<div className={this.state.collapsed ? 'collapsed' : ''}>
<h6>Your cart</h6>
<p className={this.state.prods.length ? 'hidden' : ''}>Your cart is empty</p>
{this.state.products}
<h6>Total: {this.props.total}</h6>
<button onClick={this.handleCheckout} className={this.state.prods.length ? '' : 'hidden' }>Checkout</button>
<img src="./images/paypal.png" className="paypal" alt="Paypal" />
<a className="minify" onClick={this.expand} alt="My cart"></a>
<span className={this.state.prods.length ? 'pulse' : 'hidden'}>{this.state.prods.length}</span>
</div>
)
}
}
export default withCookies(Cart)
The Product component:
import React from 'react';
class Product extends React.Component {
constructor(props) {
super(props);
this.state = {
showDetails: false,
showModal: false,
cart: []
}
this.imgPath = './images/catalog/'
}
render() {
return (
<div className="Product">
<section>
<img src={this.imgPath + this.props.product.image} />
</section>
<section>
<div>
<h2>{this.props.product.title}</h2>
<h3>{this.props.product.artist}</h3>
<p>Product: {this.props.product.product_type}</p>
<h4>${this.props.product.price}</h4>
<button className="button"
id={this.props.product.id} onClick={this.props.handler}>Add to cart</button>
</div>
</section>
</div>
)
}
}
export default Product
If I log this.props.handler I get undefined. Everything works apart from the click handler, I was wondering if it might have something to with the async function. I am very new to React, there are still some concepts I'm not sure about, so any help is appreciated.
Okay, I see a few issues here.
First, there is no need to call this.handleClick = this.handleClick.bind(this) in the constructor, because you are using an arrow function. Arrow functions do not have a this context, and instead, accessing this inside your function will use the parent this found in the Class.
Secondly, it is wrong to store components in state. Instead, map your importedProducts inside the render function.
Thirdly, the issue with your handler is that this.props.handler doesn't actually call handleClick. You will notice in the definition handler={(product) => this.handleClick} it is returning the function to the caller, but not actually calling the function.
Try this instead.
class Product extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<button className="button" id={this.props.product.id} onClick={this.props.handler}>
Add to cart
</button>
</div>
);
}
}
export default Product;
import Product from './Product'
class Cart extends React.Component {
constructor(props) {
super(props);
}
handleClick = (o) => {
console.log('click');
};
render() {
return (
<div>
{importedProducts.map((product) => {
return <Product key={product.id} product={product} handler={() => this.handleClick(product)} />;
})}
</div>
);
}
}
export default Cart;

How can I update my child component state from the parent class in React?

I currently have a parent class (App) and a child component (FoodItem). The App component maps a list of FoodItem components.
Each individual FoodItem component has a state called clickCount which increments each time its clicked by the user. However, I need the reset button in my App component to reset the FoodItem state to 0 onClick for all FoodItem components in the map.
Any help with this would be much appreciated.
Update - I have managed to get the reset button to update the FoodItem child component, however it only updates the last item in the mapped list, whereas I require it to update all items in the list.
class App extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
this.state = {
foods: FoodCards,
calorieCount: 0,
vitaminACount: 0,
vitaminDCount: 0,
};
this.increment = this.increment.bind(this);
}
increment = (calories = 0, vitaminA = 0, vitaminD = 0) => {
this.setState({
calorieCount: Math.round(this.state.calorieCount + calories),
vitaminACount: Math.round((this.state.vitaminACount + vitaminA) * 100) / 100,
vitaminDCount: Math.round((this.state.vitaminDCount + vitaminD) * 100) / 100
});
};
resetCounters = () => {
this.myRef.current.resetClickCount();
this.setState({
calorieCount: 0,
vitaminACount: 0,
vitaminDCount: 0,
});
};
render() {
return (
<div className="App">
<main className="products-grid flex flex-wrap">
{this.state.foods.map((item, i) => {
return <FoodItem key={item.id} ref={this.myRef} name={item.name} img={item.img} calories={item.calories} vitamin_a={item.vitamin_a} vitamin_d={item.vitamin_d} updateTotals = {this.increment} />
})}
</main>
<footer>
<div className="reset" onClick={() => this.resetCounters()}>Reset</div>
</footer>
</div>
);
}
}
export default App;
class FoodItem extends React.Component {
constructor(props) {
super(props);
this.state = {
clickCount: 0
};
}
handleUpdateTotals = (calories, vitamin_a, vitamin_d) => {
this.props.updateTotals(calories, vitamin_a, vitamin_d);
this.clickIncrement();
}
clickIncrement = () => {
this.setState({
clickCount: this.state.clickCount + 1
});
}
resetClickCount = () => {
this.setState({
clickCount: 0
});
}
render() {
return (
<div className="product" onClick={() => this.handleUpdateTotals(this.props.calories, this.props.vitamin_a, this.props.vitamin_d)}>
<p>{this.props.name}</p>
{this.state.clickCount > 0 ? <p>Selected: {this.state.clickCount}</p> : <p>Not Selected</p>}
<img src={this.props.img} alt="" />
</div>
);
}
}
You should lift the state up per the react docs. When two children affect the same state (reset button and clickCount), the state should be lifted up to the parent that contains both of these children. So your count should be stored in the parent.
But... to answer your question, you can easily do something like this to trigger a render each time the button is clicked (not recommended though):
const [resetCount, setResetCount] = useState(false);
const resetCountClicked = () => setResetCount(!resetCount);
return
<>
<Child resetCount={resetCount}></Child>
<button onClick={() => resetCountClicked()}>Reset Count</button>
</>
And in the child include:
useEffect(() => setCount(0), [resetCount]);
This is using hooks with function components and useEffect to run an event each time the prop resetCount changes.
With classes you can use a ref passed to the child as shown in this post.
const { Component } = React;
class Parent extends Component {
constructor(props) {
super(props);
this.child = React.createRef();
}
onClick = () => {
this.child.current.resetCount();
};
render() {
return (
<div>
<Child ref={this.child} />
<button onClick={this.onClick}>Reset Count</button>
</div>
);
}
}
And in the child...
resetCount() {
this.setState({
clickCount: 0
});
}

How do I use the props in order to create a condition in ReactJS where I am able to change the state of my app

I have two components, the parent "App" component and the Hero component which contains an image and left and right arrow.
I want to use the right arrow to move the imageIndex + 1 until it reaches the images.length, and I want to have the left arrow to have a condition that I can't subtract if imageIndex = 0.
So something like: ( This part of the code is not added in my code yet because I keep getting undefined)
if (this.props.imageIndex > 0) {
this.setState({
// decrease the imageIndex by 1
})
}
if (this.props.imageIndex < this.props.images.length - 1){
this.setState({
// increase the imageIndex by 1
})
}
will be the condition or something like it.
App.jS (Parent Component)
export default class App extends Component {
constructor() {
super();
this.state = {
language: "english",
render: 'overview',
imageIndex: 0,
}
}
render() {
// to make sure the state changes
console.log(this.state.language)
const {render} = this.state
return <Fragment>
<Hero imageIndex = {this.state.imageIndex} />
</Fragment>;
}
}
How would I add that in my Hero Component which contains this code:
Hero.js
class Hero extends Component {
constructor(props) {
super(props);
this._ToggleNext = this._ToggleNext.bind(this);
}
_ToggleNext(props) {
console.log(this.props.listing.images.length)
console.log(this.props.imageIndex)
}
_TogglePrev(props) {
console.log(this.props.listing.images.length)
console.log(this.props.imageIndex)
}
render() {
const { listing: { images = [], name, location = {} } = {} } = this.props;
return <div className="hero">
<img src={images[0]} alt="listing" />
<a onClick={this._TogglePrev}) className="hero__arrow hero__arrow--left">◀</a>
<a onClick={this._ToggleNext} className="hero__arrow hero__arrow--right">▶</a>
<div className="hero__info">
<p>{location.city}, {location.state}, {location.country}</p>
<h1>{name}</h1>
</div>
</div>;
}
}
const getHero = gql`
query getHero {
listing {
name
images
location {
address,
city,
state,
country
}
}
}
`;
export default function HeroHOC(props) {
return <Query
query={getHero}
>
{({ data }) => (
<Hero
{...props}
listing={data && data.listing || {}} // eslint-disable-line no-mixed-operators
/>
)}
</Query>;
}
One solution is to define the data and functionality in the parent component, in this case App, and pass those down as props to the child which will focus on the rendering.
(code not tested but should give you the basic idea)
class App extends Component {
state = {
imageIndex: 0,
listing: {
images: ['foo.jpg', 'bar.jpg'],
name: 'foo',
location: {...}
}
}
_ToggleNext = () => {
const { imageIndex, listing } = this.state;
if (imageIndex === listing.images.length - 1) {
this.setState({imageIndex: 0});
}
}
_TogglePrev = () => {
const { imageIndex, listing } = this.state;
if (imageIndex === 0) {
this.setState({imageIndex: listing.images.length - 1});
}
}
render() {
return (
<Fragment>
<Hero
listing={this.state.listing}
imageIndex={this.state.imageIndex}
toggleNext={this._ToggleNext}
togglePrev={this._TogglePrev}
/>
</Fragment>
);
}
}
Hero component:
const Hero = props => {
const { listing, imageIndex, togglePrev, toggleNext } = props;
return (
<div className="hero">
<img src={listing.images[imageIndex]}/>
<a onClick={togglePrev})>◀</a>
<a onClick={toggleNext}>▶</a>
<div className="hero__info">
...
</div>
</div>
);
};

Cannot call redux action in my component

I'm learning redux with react-native and I'm trying to use actions in "container-component" pattern.
I want to call a redux action in my component and I'm using mapDispatchToProps in order to do this.
But when the component calls the action, I get the famous red screen with
this.props.myDecrementor is not a function
and when I debug and console.log(this.props) in my component no actions or props are available.
Let me show my code :
myContainer.js
import { connect } from "react-redux";
import { incrementStepCount, decrementStepCount } from "../actions";
class ChooseHour extends Component {
constructor(props) {
super(props);
this.addStep = this.addStep.bind(this);
this.removeStep = this.removeStep.bind(this);
}
addStep() {
this.props.myIncrementor();
}
removeStep() {
this.props.myDecrementor();
}
render() {
return ( <
View style = {
{
flex: 1
}
} >
<
Button onPress = {
this.addStep
}
style = {
styles.btnAddEtape
}
small transparent iconLeft >
<
Text > Add Step < /Text> <
/Button> <
InputStep key = {
1
}
label = "MyStep" / >
<
/View>
);
}
}
function mapStateToProps(state) {
return {
order: state.order
};
}
function mapDispatchToProps(dispatch) {
return {
myDecrementor: () => dispatch(decrementStepCount()),
myIncrementor: () => dispatch(incrementStepCount())
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ChooseHour);
myComponent.js
export default class InputStep extends Component {
decrementStepCountinComponent() {
this.props.myDecrementor();
}
render() {
return ( < Item style = {
styles.inputContainer
}
inlineLabel > < Label > {
this.props.label
} < /Label> <
Input style = {
styles.input
}
placeholder = "Step" / > < Icon name = "times"
type = "FontAwesome"
style = {
{
color: "red"
}
}
onPress = {
() => this.decrementStepCountinComponent()
}
/> < /
Item > );
}
}
Calling actions in container works, but not in the component ...
I've read this post What is mapDispatchToProps? but I really don't understand why it is not working.
Thank you in advance for your help.
You need to pass myDecrement() function to inputstep component from container as props.
In myContainer add
<InputStep myDecrementor = {this.props.myDecrementor}

How to setState from child component in React

I would like to set state of parent component from child component. I tried using props however its giving error Uncaught TypeError: this.props.setTopicClicked is not a function. And is there more efficient way for setting state of parent component instead of using props? I would like to set state of isTopicClicked: true
main-controller.jsx
import {React, ReactDOM} from '../../../build/react';
import SelectedTopicPage from '../selected-topic-page.jsx';
import TopicsList from '../topic-list.jsx';
import topicPageData from '../../content/json/topic-page-data.js';
export default class MainController extends React.Component {
state = {
isTopicClicked: false,
topicPageData
};
onClick(topicID) {
this.setState({
isTopicClicked: true,
topicsID: topicID
});
};
setTopicClicked(event){
this.setState({isTopicClicked: event});
};
render() {
return (
<div className="row">
{this.state.isTopicClicked
? <SelectedTopicPage topicsID={this.state.topicsID} key={this.state.topicsID} topicPageData={topicPageData}/>
: <TopicsList onClick={ this.onClick.bind(this) }/>}
</div>
);
}
};
selected-topic-page.jsx
import {React, ReactDOM} from '../../build/react';
import SelectedTopicPageMarkup from './selected-topic-page-markup.jsx';
import NextPrevBtn from './next-prev-btn.jsx';
export default class SelectedTopicPage extends React.Component {
state = {
topicPageNo: 0,
total_selected_topic_pages: 1
};
navigateBack(topicPageNo) {
if (this.state.topicPageNo > 0){
topicPageNo = this.state.topicPageNo - 1;
}
else {
topicPageNo = 0;
}
this.setState({topicPageNo : topicPageNo});
};
navigateNext(totalPagesInSelectedTopic) {
let topicPageNo;
if (totalPagesInSelectedTopic > this.state.topicPageNo + 1){
topicPageNo = this.state.topicPageNo + 1;
}
else if (totalPagesInSelectedTopic == this.state.topicPageNo + 1) {
this.props.setTopicClicked(true);
}
else {
topicPageNo = this.state.topicPageNo;
}
this.setState({topicPageNo : topicPageNo});
};
render() {
let topicsID = this.props.topicsID;
let topicPageNo = this.state.topicPageNo;
return (
<div>
{this.props.topicPageData.filter(function(topicPage) {
// if condition is true, item is not filtered out
return topicPage.topic_no === topicsID;
}).map(function (topicPage) {
let totalPagesInSelectedTopic = topicPage.topic_pages.length;
return (
<div>
<div>
<SelectedTopicPageMarkup headline={topicPage.topic_pages[0].headline} key={topicPage.topic_no}>
{topicPage.topic_pages[topicPageNo].description}
</SelectedTopicPageMarkup>
</div>
<div>
<NextPrevBtn moveNext={this.navigateNext.bind(this, totalPagesInSelectedTopic)} key={topicPage.topic_no} moveBack={this.navigateBack.bind(this, topicPageNo)}/>
</div>
</div>
);
}.bind(this))}
</div>
);
};
};
It seems you forgot to pass setTopicClicked to the child:
setTopicClicked={this.setTopicClicked.bind(this)}
Your <SelectedTopicPage /> does not contain setTopicClicked as props which results into the error
<SelectedTopicPage
topicsID={this.state.topicsID}
key={this.state.topicsID}
topicPageData={topicPageData}/>
You can try using a flux implementation to handle the state of your application and just pass props to the component. Otherwise, I think you're stuck in passing in setting the state using the component or its children.

Categories

Resources