Problem updating parent state from child component in React - javascript

I have a class component(actually the collection of the same components) where I have 2 buttons + and - to increase and decrease quantity of watches. Min amount of watches is 1 and max amount is 10. I have regulated this with this 2 functions increaseAmountHandler and decreaseAmountHandler. With this two buttons it's all ok. But the problem is that I have to sum up the value of calculated watches in parent component and I cannot forward the summed up values of the watches to a parent component to state variable totalAmount. Cannot use Redux becacuse it's a collection of watches component and each have own + and - button already occupied with this 2 increaseAmountHandler, decreaseAmountHandler functions.
Anyone idea how to solve this?
Child component:
import React, { Component } from 'react';
import Modal from '.././UI/Modal';
class SelectedWatch extends Component {
constructor(props) {
super(props)
this.state = {
watchQuantity: 1,
watchAmount: 1
}
}
increaseAmountHandler = () => {
if(this.state.watchQuantity < 1) {
this.setState({
watchQuantity: 0,
watchAmount: 0
})
return;
} else if (this.state.watchQuantity >= 10){
this.setState({
watchQuantity: 10,
watchAmount: this.props.selectedWatch.selectedWatchPrice * this.state.watchQuantity
})
return;
}
this.setState({
watchQuantity: this.state.watchQuantity + 1,
watchAmount: this.props.selectedWatch.selectedWatchPrice * this.state.watchQuantity
})
}
decreaseAmountHandler = () => {
if(this.state.watchQuantity < 1) {
this.setState({
watchQuantity: 0,
watchAmount: 0
})
return;
} else if (this.state.watchQuantity >= 10){
this.setState({
watchQuantity: 9,
watchAmount: this.props.selectedWatch.selectedWatchPrice * this.state.watchQuantity
})
return;
}
this.setState({
watchQuantity: this.state.watchQuantity - 1,
watchAmount: this.props.selectedWatch.selectedWatchPrice * this.state.watchQuantity
})
}
render() {
return (
<div className={"shopping-cart-product" + (this.state.watchQuantity < 1 ? ' notDisplayed' : '')}>
<div className="product-info">
<div>
<h3>{this.props.selectedWatch.selectedWatchName}</h3>
<p>${this.props.selectedWatch.selectedWatchPrice} × {this.state.watchQuantity}</p>
</div>
<img src={this.props.selectedWatch.selectedWatchUrl} />
</div>
<div className="product-count">
<button onClick={this.decreaseAmountHandler}>-</button>
<span>{this.state.watchQuantity}</span>
<button onClick={this.increaseAmountHandler}>+</button>
</div>
</div>
);
}
}
export default SelectedWatch;
Parent component:
import React, { Component } from 'react';
import EnteredWatch from '.././components/EnteredWatch/EnteredWatch';
import SelectedWatch from '.././components/SelectedWatch/SelectedWatch';
class App extends Component {
constructor(props) {
super(props)
this.state = {
watchName: '',
watchDescription: '',
watchUrl: '',
watchPrice: '',
watchId: '',
watchAmount: '',
watchQuantity: 1,
enteredWatchList: [],
selectedWatchName: '',
selectedWatchDescription: '',
selectedWatchUrl: '',
selectedWatchPrice: '',
selectedWatchId: '',
selectedWatchAmount: '',
selectedWatchQuantity: 1,
selectedWatchList: [],
totalAmount: 0,
}
}
submitHandler = (event) => {
event.preventDefault();
let watchId = Math.floor((Math.random() * 100) + 1);
let watchName = this.state.watchName;
let watchDescription = this.state.watchDescription;
let watchUrl = this.state.watchUrl;
let watchPrice = this.state.watchPrice;
let watchQuantity = this.state.watchQuantity;
this.setState({
enteredWatchList: this.state.enteredWatchList.concat({watchName, watchUrl, watchDescription, watchPrice, watchId, watchQuantity})
})
add = (selectedWatchName, selectedWatchUrl, selectedWatchDescription, selectedWatchPrice, index, selectedWatchQuantity) => {
let arr = this.state.selectedWatchList;
let found = arr.some(el => {
return el.selectedWatchName === selectedWatchName;
});
if (!found) {
return arr.concat({selectedWatchName, selectedWatchUrl, selectedWatchDescription, selectedWatchPrice, index, selectedWatchQuantity});
} else {
return this.state.selectedWatchList;
}
}
buyWatchHandler = (selectedWatchName, selectedWatchUrl, selectedWatchDescription, selectedWatchPrice, index, selectedWatchQuantity) => {
let arr = this.add(selectedWatchName, selectedWatchUrl, selectedWatchDescription, selectedWatchPrice, index, selectedWatchQuantity);
this.setState({
selectedWatchName: selectedWatchName,
selectedWatchUrl: selectedWatchUrl,
selectedWatchDescription: selectedWatchDescription,
selectedWatchPrice: selectedWatchPrice,
selectedWatchId: index,
selectedWatchQuantity: selectedWatchQuantity,
selectedWatchList: arr
});
}
render() {
const enteredWatches = this.state.enteredWatchList.map((enteredWatch, index) => {
return <EnteredWatch
key={index}
enteredWatch={enteredWatch}
selected={this.buyWatchHandler.bind(this, enteredWatch.watchName, enteredWatch.watchUrl,
enteredWatch.watchDescription, enteredWatch.watchPrice, index, enteredWatch.watchQuantity)}
/>
});
const selectedWatches = this.state.selectedWatchList.map((selectedWatch, index) => {
const active = this.state.activeIndex;
return <SelectedWatch
key={index}
active={index === active}
selectedWatch={selectedWatch}
/>
});
return (
<div className="App">
<div className="container-fluid">
<div className="container">
<div className="add-product">
<form>
<div>
<label>Product name:</label>
<input
type="text"
placeholder="Casio Watch"
required
value={this.state.watchName}
onChange={event => this.setState({watchName: event.target.value})}
/>
</div>
<div>
<label>Product description:</label>
<textarea
placeholder="Sample description..."
value={this.state.watchDescription}
onChange={event => this.setState({watchDescription: event.target.value})}
>
</textarea>
</div>
<div>
<label>Product image:</label>
<input
type="text"
placeholder="http://...jpg"
value={this.state.watchUrl}
pattern="https?://.+" required
onChange={event => this.setState({watchUrl: event.target.value})}
/>
</div>
<div>
<label>Product price:</label>
<input
type="number"
min="0"
placeholder="22"
value={this.state.watchPrice}
onChange={event => this.setState({watchPrice: event.target.value})}
/>
</div>
<button
type="submit"
onClick={event => this.submitHandler(event)}
>
Add a new Task
</button>
</form>
</div>
<div className="list-products">
<ul>
{enteredWatches}
</ul>
</div>
<div className="shopping-cart">
<div className="shopping-cart-products">
<ul>
{selectedWatches}
</ul>
</div>
<div className="shopping-cart-summary">
<div>Total: <b>${this.state.totalAmount}</b></div>
<div><button onClick={this.summaryHandler}>Purchase</button></div>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default App;

The parent has to keep track of how many watches have been added.
Make the parent smart (has state), and the children dumb (no state).
Manage all the state in the parent, and put the click handlers in the parent too.
Pass those handlers down to the child, to be fired when its buttons are clicked. Something like this:
class Parent extends React.Component {
this.state = {
cart: [],
watches: [
{ id: 1, name: "Casio", description: "...", price: 25 },
{ id: 2, name: "Rolex", description: "...", price: 3000 },
{ id: 3, name: "Timex", description: "...", price: 10 },
],
}
handleClickIncrement = (watchId) => {
//add it to the cart (or increment it if its already there)
}
handleClickDecrement = (watchId) => {
//remove it from the cart (or deccrement it if its already there)
}
getCartTotal() {
//loop over cart and calculate
}
renderWatches() {
this.state.watches.map(watch => (
<Watch id={watch.id}
name={watch.name}
description={watch.description}
price={watch.price}
onClickIncrement={() => { this.handleClickIncrement(watch.id); }}
onClickDecrement={() => { this.handleClickDecrement(watch.id); }}
))
}
render() {
<div>
<h1>Our selection of watches:</h1>
{this.renderWatches()}
<h1>Your cart total: {this.getCartTotal()}</h1>
</div>
}
}
class Watch extends React.Component {
props = {
id,
name,
description,
price,
quantityInCart,
onClickIncrementButton,
onClickDecrementButton
}
render() {
<div>
<h1>{this.props.name}</h1>
<p>{this.props.description}</p>
<h5>${this.props.price}</h5>
<button onClick={this.props.onClickIncrementButton}>+</button>
<button onClick={this.props.onClickDecrementButton}>-</button>
</div>
}
}

Related

How to make a simple loop slider in the React?

Sorry for my English)
Do not judge strictly, since I just started working with the react.
I made a simple slider on the react and now I want to make it cyclic.
But I can’t. In my code it seems to be cyclic, but for some reason it skips the last picture.
how can i fix it?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [
{
id: 1,
name: "Product 1",
price: 50,
q: 0,
category: "Sporting Goods",
images: [
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property04.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property02.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property01.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property03.jpg"
],
currentImageIndex: 0,
isCycleMode: false,
cantGoPrev: false,
cantGoNext: true
},
{
id: 2,
name: "Product 2",
price: 70,
q: 0,
category: "Sporting Goods",
images: [
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property04.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property02.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property01.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property03.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property02.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property01.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property03.jpg"
],
currentImageIndex: 0,
isCycleMode: false,
cantGoPrev: false,
cantGoNext: true
}
]
};
}
nextSlideHandler = (e, item, index ) => {
let arr = [...this.state.data];
let newIndex = this.state.data[index].currentImageIndex;
if (e.currentTarget.dataset.direction === "next") {
if (newIndex < this.state.data[index].images.length - 1) {
newIndex = this.state.data[index].currentImageIndex + 1;
arr[index].cantGoPrev = true;
this.setState({data:arr})
}
if (newIndex === this.state.data[index].images.length - 1) {
newIndex = 0;
arr[index].cantGoNext = true;
this.setState({data:arr})
}
} else {
if (newIndex > 0) {
newIndex = this.state.data[index].currentImageIndex - 1;
arr[index].cantGoNext = true;
this.setState({data:arr})
}
if (newIndex === 0) {
arr[index].cantGoPrev = false;
this.setState({data:arr})
}
}
arr[index].currentImageIndex = newIndex;
this.setState({ data:arr });
}
render() {
return (
<div className="App">
<div>
<h3>Products</h3>
<div className="collection">
{this.state.data.map((item, index) => (
<div key={item.id} className="product">
<div className="product__image">
<div>
<button
disabled={!item.cantGoPrev}
data-direction="prev"
onClick={(e)=> this.nextSlideHandler(e,item, index)}
className="prev"
>
prev
</button>
</div>
<div>
<img
src={item.images[item.currentImageIndex]}
alt=""
/>
{item.images.currentImageIndex}
</div>
<div>
<button
disabled={!item.cantGoNext}
data-direction="next"
onClick={(e)=> this.nextSlideHandler(e, item, index)}
className="next"
>
next
</button>
</div>
</div>
<div className="product__name">{item.name}</div>
<div className="product__price">{item.price}</div>
</div>
))}
</div>
</div>
</div>
);
}
}
What is the best way to write a slider?
I will be glad of any help
First: There are many ways to achieve what you are trying to do, but this is how I would have done it.
Instead of using index to update different state, I would make a own component for Product. Inside that component you have full access to that products state and props. This makes it easier to work with the correct data.
I would also remove the next/previous-logic from the state and just do a check if the buttons should be active on render.
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
data: [
{
id: 1,
name: 'Product 1',
price: 50,
q: 0,
category: 'Sporting Goods',
images: [
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property04.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property02.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property01.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property03.jpg'
],
currentImageIndex: 0,
isCycleMode: false
},
{
id: 2,
name: 'Product 2',
price: 70,
q: 0,
category: 'Sporting Goods',
images: [
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property04.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property02.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property01.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property03.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property02.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property01.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property03.jpg'
],
currentImageIndex: 0,
isCycleMode: false
}
]
}
}
handleChange = (arr) => {
// State updates based on other state should be asynchronous
// https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
this.setState((state, props) => {
const oldArr = [...state.data]
const arrIndex = oldArr.findIndex(x => x.id === arr.id)
oldArr[arrIndex] = arr
return ({
data: oldArr
})
})
}
render () {
return (
<div className='App'>
<div>
<h3>Products</h3>
<div className='collection'>
{this.state.data.map((item) => (
<Product
item={item}
key={item.id}
onChange={this.handleChange}
/>
))}
</div>
</div>
</div>
)
}
}
class Product extends React.Component {
handleSlideChange = (e) => {
const arr = { ...this.props.item }
if (e.currentTarget.dataset.direction === 'next') {
arr.currentImageIndex++
} else {
arr.currentImageIndex--
}
this.props.onChange(arr)
};
render () {
const { item } = this.props
return (
<div key={item.id} className='product'>
<div className='product__image'>
<div>
<button
disabled={item.currentImageIndex <= 0}
data-direction='prev'
onClick={this.handleSlideChange}
className='prev'
>
Prev
</button>
</div>
<div>
<img src={item.images[item.currentImageIndex]} alt='' />
{item.images.currentImageIndex}
</div>
<div>
<button
disabled={item.currentImageIndex >= item.images.length - 1}
data-direction='next'
onClick={this.handleSlideChange}
className='next'
>
Next
</button>
</div>
</div>
<div className='product__name'>{item.name} {item.currentImageIndex}</div>
<div className='product__price'>{item.price}</div>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
<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>
<div id="root"></div>

ReactJS: Reset-Render Child Component on Parent state change

I have a strange - at least for me :-) - issue with my Components.
I have a map() function generating a list of Child elements. When I change the filter and the list repopulates the state of the children remain as it was. For example, if the second Alumni Child Component has state {height:auto} the new second Allumni Child has again {height: auto}.
I find it really odd because this is not the same element, it's a new element corresponding to another person, with new content passed through props.
Long story short how do I force my new Child elements to have initial state {height: 0}?
There is a lot of details hidden but the core of my Parent App is as follows:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
filterName: "",
filterInitial: "Α",
....
filteredGraduates:[],
loaderVisible:true
};
this.updateInitial = this.updateInitial.bind(this)
...
}
componentDidMount(){
...
}
updateInitial(letter) {
if (letter==='*'){
this.setState({
filterInitial: "*",
filteredGraduates : [...graduates]
})
}else{
this.setState({
filterInitial: letter,
filteredGraduates : graduates.filter( (graduate) => graduate.Surname.charAt(0)===letter )
})
}
console.log('this is the letter: ',letter);
}
render() {
return (
<div className="app">
<div className="filters">
<div className="name-pagination">
<span
onClick={ () => this.updateInitial('*')}
className={'*'==this.state.filterInitial? 'active': ''}
>Α - Ω
</span>
<hr/>
{letters.map( (letter) => {
return(
<span
onClick={ () => this.updateInitial(letter)}
className={letter==this.state.filterInitial? 'active': ''}
>{letter}</span>
)
}
)}
</div>
</div>
</div>
{this.state.loaderVisible && <Loader /> }
{!this.state.loaderVisible && <div className="graduates-wrapper">
{this.state.filteredGraduates
.sort( (x,y) => x.Surname.localeCompare(y.Surname) )
.map( (graduate) => {
return(
<div>
<Allumni
key={graduate.id}
.... a lot of props ...
/>
</div>
)
})
}
</div>}
</div>
);
}
}
and my Child is:
class Allumni extends React.Component {
constructor(props) {
super(props);
this.state = {
height:0
};
this.updateHeight = this.updateHeight.bind(this)
}
updateHeight() {
this.setState({
height: this.state.height === 0 ? 'auto' : 0,
});
};
render() {
const {
name,
surName,
.......
} = this.props
return (
<div className="allumni-wrapper">
<div className="allumni-main-info">
<span className="allumni-surname">{surName}</span><br/><span className="allumni-name">{name}</span>
</div>
<div className="allumni-extra">
<span className="allumni-year">{yearOf}</span><br/>
<span className="allumni-job">{job}</span> / <span className="allumni-home-city">{homeCity}</span>
</div>
<div className="allumni-details">
<AnimateHeight
duration={ 500 }
height={ this.state.height }
>
<p><span className="attribute-name">... {fatherName}</p>
<p><span className="attribute-name">...{studies}</p>
....
</AnimateHeight>
</div>
<div className="allumni-actions" onClick={this.updateHeight}>
<AccountCardDetailsOutlineIcon size={30}/>
<span className={'actions-toggle ' + (this.state.height >= 0 ? '' : 'rotated') }><ChevronDownIcon/></span>
</div>
</div>
);
}
}
export default Allumni
thank you

front-end just remove only last item in array

I'm having a issue with React.
my parent component:
class RoomPrice extends React.Component {
constructor(props){
super(props)
this.state = {
room: this.props.room,
prices: []
};
this.handleDeletePrice = this.handleDeletePrice.bind(this);
}
handleDeletePrice(price_index){
let prices = this.state.prices;
prices.splice(price_index, 1);
this.setState({prices: prices});
}
listPrices(){
console.log(this.state.prices)
return this.state.prices.map((item, index) => {
return (
<AdditionalPrice
key={index}
price={item}
index={index}
handleDeletePrice={this.handleDeletePrice}
/>
)
});
}
renderBasePrice(){
return(
<div id="list_prices">
{ this.listPrices() }
</div>
)
}
render(){
return(
<div>
{this.renderBasePrice()}
</div>
)
}
}
my child component
class AdditionalPrice extends React.Component {
constructor(props){
super(props)
this.state = {
price: this.props.price
}
this.handleKeyChange = this.handleKeyChange.bind(this);
this.handleValueChange = this.handleValueChange.bind(this);
this.handleDeletePrice = this.handleDeletePrice.bind(this);
}
componentWillReceiveProps(nextProps){
this.setState({price: nextProps.price})
}
handleKeyChange(event){
let price = this.state.price;
price.key = event.target.value
this.setState({price: price})
}
handleValueChange(event){
let price = this.state.price;
price.value = event.target.value
this.setState({price: price})
}
handleDeletePrice(){
this.props.handleDeletePrice(this.props.index);
}
renderForm(){
let key = this.state.price.key;
let value = this.state.price.value;
return(
<div className="form-row">
<div className="col-5">
<input type="text" className="form-control" placeholder="Key" onChange={this.handleKeyChange} required/>
</div>
<div className="col-5">
<input type="number" className="form-control" placeholder="Value" onChange={this.handleValueChange} required/>
</div>
<div className="col-2">
<button className="btn btn-warning" type="button" onClick={this.handleDeletePrice}>
<i className="material-icons">delete_forever</i>
</button>
</div>
<input type="hidden" className="form-control" name={"base_price["+key+"]"} value={value} />
</div>
)
}
render() {
return(
<div>
{this.renderForm()}
</div>
)
}
}
i try to delete a item which was get in children, but it always removes last element instead. I thought it have some problem with index
I want to delete the particular element, it always deletes the last element from the render list array.
please help me to sort this problem
Try doing this instead.
handleAddNewPrice(){
const { prices } = this.state;
let new_price = {"key": "", "value": ""}
this.setState({ prices: [...prices, new_price] })
}
Edit
and also this:
handleDeletePrice(price_index){
let prices = [...this.state.prices]; //make a seperate copy of state.
prices.splice(price_index, 1);
this.setState({prices: prices});
}
Problem is in your props. The props.index is received once, so if you want to the delete function worked you need use props.index as a state like price. This is sample codes you need to change in the AdditionalPrice Component:
this.state = {
price: this.props.price,
index: this.props.index
}
componentWillReceiveProps(nextProps){
this.setState({
price: nextProps.price,
index: nextProps.index
})
}
handleDeletePrice(){
this.props.handleDeletePrice(this.state.index);
}
i found the problem
my field in child component haven't set the value. see below
<input type="text" className="form-control" placeholder="Key" value={key} onChange={this.handleKeyChange} required/>
thanks all

Fetch data from API when form's search button clicked and show data on another page in React JS

I am developing a React JS web application where I have a form with four select fields (Make, Model, Min price and Max price) and a Search button. The data for search results will be fetched from API according to the selection of options. I want to show that data on another page in a card (page route path: /search) when user clicked on search button. I am using react router. The API url/end point is https://mysterious-journey-51969.herokuapp.com/api/search-vehicle/?q=mercedes&m=sprinter&pf=0&pt=100000 where "q" field matches Vehicle Make, "m" field matches Model, "pf" field matches Min Price, "pt" field matches Max Price. How I can do that?
Here is my Form component code:
import React, { Component } from 'react';
import { Form, FormGroup, Input } from 'reactstrap';
import { veh_data } from '../shared/vehicle_make_and_models';
const defaultValues = [
{ value: 0, text: 0, key: 1 },
{ value: 500, text: 500, key: 2 },
{ value: 1000, text: 1000, key: 3 },
{ value: 1500, text: 1500, key: 4 },
{ value: 2000, text: 2000, key: 5 },
{ value: 2000, text: 2000, key: 6 }
];
const MIN_TITLE = { selected: true, disabled: true, text: 'Min Price' };
const MAX_TITLE = { selected: true, disabled: true, text: 'Max Price' };
class ImgAndForm extends Component {
constructor(props) {
super(props);
this.handleSearch = this.handleSearch.bind(this);
this.keyToOption = this.keyToOption.bind(this);
this.renderOptions = this.renderOptions.bind(this);
this.handleModelChange = this.handleModelChange.bind(this);
this.state = {
minData: [MIN_TITLE, ...defaultValues],
maxData: [MAX_TITLE, ...defaultValues],
minValue: null,
maxValue: null,
modelSelected: null
};
}
renderOptions(data) {
return data.map(datum => {
// this allows us to indicate whether we are selecting or disabling
const selected = datum.selected || false;
const disabled = datum.disabled || false;
return (
<option key={datum.key} value={datum.value} selected={selected} disabled={disabled}>
{datum.text}
</option>
);
});
}
handleModelChange(event) {
console.log(event.target.value);
this.setState({ modelSelected: event.target.value });
}
handleSearch(event) {
alert("Search button clicked");
}
keyToOption(key) {
return key.split("-")
.map(word => word.slice(0, 1).toUpperCase() + word.slice(1))
.join(" ");
}
handleMinSelect = event => {
const value = event.target.value;
const newMaxValues = [];
defaultValues.forEach(datum => {
if (datum.value >= Number.parseInt(value, 10)) {
newMaxValues.push(datum);
}
});
this.setState({
maxData: [MAX_TITLE, ...newMaxValues],
minValue: value
});
};
handleMaxSelect = event => {
const value = event.target.value;
this.setState({ maxValue: value });
};
render() {
const vehicles = veh_data.reduce((acc, veh, i) => {
let make = Object.keys(veh)[0],
vehModels = veh[make];
return {
makes: [
...acc.makes,
<option key={make + i} value={make}>{this.keyToOption(make)}</option>
],
models: {
...acc.models,
[make]: vehModels.map((model, i) => {
return (
<option key={make + model + i} value={model}>
{this.keyToOption(model)}
</option>
);
})
}
};
}, { makes: [], models: [] });
const selectedModels =
this.state.modelSelected && this.state.modelSelected.length ? (
vehicles.models[this.state.modelSelected]
) : (
<option value="">Model (select make first)</option>
);
return (
<div>
<header className="headerbg d-flex">
<div className="container my-auto">
<div className="row">
<div className="offset-1 col-10 offset-lg-0 col-lg-4">
<div id="search-form-div" className="container">
<div className="row">
<div className="col-12 my-4">
<h3>Search</h3>
<Form onSubmit={this.handleSearch}>
<FormGroup>
<Input
onChange={e => this.handleModelChange(e)}
type="select"
name="q"
id="q"
>
<option value="">Make</option>
{vehicles.makes}
</Input>
</FormGroup>
<FormGroup>
<Input type="select" name="m" id="m">
{selectedModels}
</Input>
</FormGroup>
<FormGroup>
<Input type="select"
name="pf"
id="pf"
value={this.state.minValue}
onChange={this.handleMinSelect}>
{this.renderOptions(this.state.minData)}
</Input>
</FormGroup>
<FormGroup>
<Input
type="select"
name="pt"
id="pt"
value={this.state.maxValue}
onChange={this.handleMaxSelect}>
{this.renderOptions(this.state.maxData)}
</Input>
</FormGroup>
<FormGroup>
<Input type="submit" name="search" id="search" className="btn btn-primary" value="Search" />
</FormGroup>
</Form>
</div>
</div>
</div>
</div>
</div>
</div>
</header>
</div>
);
}
}
export default ImgAndForm;
Here is my Search result component code:
import React, { Component } from 'react';
import Smallheader from './SmallHeader';
import { Card, CardImg, CardTitle, CardSubtitle } from 'reactstrap';
class SearchResult extends Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (
<div>
<Smallheader />
<div className="my-5">
<div className="container text-center" id="contactContainer">
<div className="row">
<div className="col-lg-12 mx-auto">
<h2 className="text-center">Search Results</h2>
<hr className="my-4 thick-hr" />
</div>
</div>
<div className="row">
<div className="col-6 col-lg-3 mt-4">
<Card>
<a href="#">
<CardImg src="" className="img-fluid" />
<CardTitle>Title Here</CardTitle>
<CardSubtitle>Price Here</CardSubtitle>
</a>
</Card>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default SearchResult;
Here is a working solution...
https://codesandbox.io/s/lrv2w3qxlq?moduleview=1
I've imported your SearchResults component and put it directly below your ImgAndForm, but you can move it anywhere in that render function.
For this specific situation you would need a way to render this on a new 'page' you would need a way to manage shared application state, like Redux or at least a container component as #MikeZinn mentioned, but to do that properly would require as significant amount of work to implement the routing and re-architect your entire program. (If you want I can show you a small hack to produce the same result without that for now, but I'd advise looking into a more permanent solution.)
Since the SearchResults component can be 'stateless' I removed the constructor function, but I left it as a class for now because this component will likely need state eventually.
I added the axios library to fetch the data from the API, but any other XHR module already used in your program will do.
NOTE: Since the specific API endpoints that your form is currently able to query are unavailable, I've hard coded the 'mercedes' example you provided, but the program will log both 'realQuery' and 'dummyQuery' so you see that it is producing the correct query structure for whenever you fix that.
Form Component
import React, { Component } from "react";
import { Form, FormGroup, Input } from "reactstrap";
// import { veh_data } from '../shared/vehicle_make_and_models';
import SearchResult from "./result";
import axios from "axios";
const veh_data = [
{ "alfa-romeo": ["145", "90", "Alfa 6", "Alfasud"] },
{ "aston-martin": ["15", "2-Litre", "AM Vantage", "Atom", "Cygnet", "DB2"] },
{ audi: ["100", "200", "A1", "A2", "A3", "A4", "A5", "A6", "A7"] }
];
const defaultValues = [
{ value: 0, text: 0, key: 1 },
{ value: 500, text: 500, key: 2 },
{ value: 1000, text: 1000, key: 3 },
{ value: 1500, text: 1500, key: 4 },
{ value: 2000, text: 2000, key: 5 },
{ value: 2000, text: 2000, key: 6 }
];
const MIN_TITLE = { selected: true, disabled: true, text: "Min Price" };
const MAX_TITLE = { selected: true, disabled: true, text: "Max Price" };
class ImgAndForm extends Component {
constructor(props) {
super(props);
this.handleSearch = this.handleSearch.bind(this);
this.keyToOption = this.keyToOption.bind(this);
this.renderOptions = this.renderOptions.bind(this);
this.handleModelChange = this.handleModelChange.bind(this);
this.state = {
minData: [MIN_TITLE, ...defaultValues],
maxData: [MAX_TITLE, ...defaultValues],
minValue: "",
maxValue: "",
modelSelected: "",
makeSelected: "",
searchResults: ""
};
}
renderOptions(data) {
return data.map(datum => {
// this allows us to indicate whether we are selecting or disabling
const selected = datum.selected || false;
const disabled = datum.disabled || false;
return (
<option
key={datum.key}
value={datum.value}
selected={selected}
disabled={disabled}
>
{datum.text}
</option>
);
});
}
handleModelChange(event) {
console.log(event.target.value);
this.setState({ modelSelected: event.target.value });
}
handleMakeChange(event) {
console.log(event.target.value);
this.setState({ makeSelected: event.target.value });
}
async handleSearch(event) {
event.preventDefault();
alert("Search button clicked");
let { makeSelected, modelSelected, minValue, maxValue } = this.state;
let realQuery =
"https://mysterious-journey-51969.herokuapp.com/api/search-vehicle/?" +
`q=${makeSelected.split("-").join("")}` +
`&m=${modelSelected.split("-").join("")}` +
`&pf=${minValue}` +
`&pt=${maxValue}`;
let dummyQuery =
"https://mysterious-journey-51969.herokuapp.com/api/search-vehicle/?q=mercedes&m=sprinter&pf=0&pt=100000";
console.log("realQuery (was not run)", realQuery);
console.log("dummyQuery (was run)", dummyQuery);
let res = await axios.get(dummyQuery).catch(err => console.log(err));
console.log("res", res.data);
if (res && res.data) {
this.setState(prevState => {
return {
...prevState,
searchResults: res.data
};
});
}
}
keyToOption(key) {
return key
.split("-")
.map(word => word.slice(0, 1).toUpperCase() + word.slice(1))
.join(" ");
}
handleMinSelect = event => {
const value = event.target.value;
const newMaxValues = [];
defaultValues.forEach(datum => {
if (datum.value >= Number.parseInt(value, 10)) {
newMaxValues.push(datum);
}
});
this.setState({
maxData: [MAX_TITLE, ...newMaxValues],
minValue: value
});
};
handleMaxSelect = event => {
const value = event.target.value;
this.setState({ maxValue: value });
};
render() {
const vehicles = veh_data.reduce(
(acc, veh, i) => {
let make = Object.keys(veh)[0],
vehModels = veh[make];
return {
makes: [
...acc.makes,
<option key={make + i} value={make}>
{this.keyToOption(make)}
</option>
],
models: {
...acc.models,
[make]: vehModels.map((model, i) => {
return (
<option key={make + model + i} value={model}>
{this.keyToOption(model)}
</option>
);
})
}
};
},
{ makes: [], models: [] }
);
const selectedModels =
this.state.makeSelected && this.state.makeSelected.length ? (
vehicles.models[this.state.makeSelected]
) : (
<option value="">Model (select make first)</option>
);
return (
<div>
<header className="headerbg d-flex">
<div className="container my-auto">
<div className="row">
<div className="offset-1 col-10 offset-lg-0 col-lg-4">
<div id="search-form-div" className="container">
<div className="row">
<div className="col-12 my-4">
<h3>Search</h3>
<Form onSubmit={this.handleSearch}>
<FormGroup key={1}>
<Input
onChange={e => this.handleMakeChange(e)}
type="select"
name="q"
id="q"
>
<option value="">Make</option>
{vehicles.makes}
</Input>
</FormGroup>
<FormGroup key={2}>
<Input
onChange={e => this.handleModelChange(e)}
type="select"
name="m"
id="m"
>
{selectedModels}
</Input>
</FormGroup>
<FormGroup key={3}>
<Input
type="select"
name="pf"
id="pf"
value={this.state.minValue}
onChange={this.handleMinSelect}
>
{this.renderOptions(this.state.minData)}
</Input>
</FormGroup>
<FormGroup key={4}>
<Input
type="select"
name="pt"
id="pt"
value={this.state.maxValue}
onChange={this.handleMaxSelect}
>
{this.renderOptions(this.state.maxData)}
</Input>
</FormGroup>
<FormGroup key={5}>
<Input
type="submit"
name="search"
id="search"
className="btn btn-primary"
value="Search"
/>
</FormGroup>
</Form>
<SearchResult results={this.state.searchResults} />
</div>
</div>
</div>
</div>
</div>
</div>
</header>
</div>
);
}
}
export default ImgAndForm;
Results Component
import React, { Component } from "react";
// import Smallheader from './SmallHeader';
import { Card, CardImg, CardTitle, CardSubtitle } from "reactstrap";
class SearchResult extends Component {
renderResults() {
let { results } = this.props;
console.log("results", results);
if (results && results.length) {
return results.map(({ price, text, title, remote_image }, i) => {
return (
<Card key={"card-" + i}>
<a href="#">
<CardImg src={remote_image} className="img-fluid" />
<CardTitle>{title}</CardTitle>
<CardSubtitle>{price}</CardSubtitle>
</a>
</Card>
);
});
}
}
render() {
return (
<div>
{/* <Smallheader /> */}
<div className="my-5">
<div className="container text-center" id="contactContainer">
<div className="row">
<div className="col-lg-12 mx-auto">
<h2 className="text-center">Search Results</h2>
<hr className="my-4 thick-hr" />
</div>
</div>
<div className="row">
<div className="col-6 col-lg-3 mt-4">{this.renderResults()}</div>
</div>
</div>
</div>
</div>
);
}
}
export default SearchResult;
This is exactly the type of problem Redux Solves without using Redux you will need to store the state on a shared parent component. For example,
class Search extends Component {
state = {
searchResult: null,
};
handleSearch = searchResult => {
this.setState({
searchResult,
});
}
render(){
const { searchResult, } = this.state;
if(searchResult === null){
return (
<ImgAndForm handleSearch={this.handleSearch} />
);
}
return (
<SearchResult searchResult={searchResult} />
);
}
}

React.js Increment and Decrement counter not mapping correctly

Let me preface that I'm in the process of learning React and I'm still pretty green at this.
I'm going to give the necessary parts of the code:
I have built a counter with increment and decrement buttons that are utilized by ways of state, they work just fine until I introduce and array and map over it. Then things start to break down. I know my code is wrong, I know there's something amiss however I'm completely ignorant as to what to even look for.
In my counting.js I have:
const players = [
{
name: "Jon Smith",
score: 10,
id: 1,
},
{
name: "Jon Doe",
score: 40,
id: 2,
},
{
name: "John Ham",
score: 30,
id: 3,
},
];
Which I have mapped in here:
class Counting extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
nameof: 'Full Name',
}
this.incrementCount = this.incrementCount.bind(this)
this.decrementCount = this.decrementCount.bind(this)
}
incrementCount(e) {
this.setState({
count: (this.state.count + 1),
})
}
decrementCount(e) {
this.setState({
count: (this.state.count - 1),
})
}
render() {
const listPlayers = players.map((players) =>
<Counter
key={players.id}
incrementCount={this.incrementCount}
decrementCount={this.decrementCount}
nameof={players.name}
count={players.score}
/>
);
return (
<div className="wrap">
<Titles header="Counter" subhead="A setState() project" subtitle="this will change" />
<h3>This doesn't work correctly</h3>
<ul>{listPlayers}</ul>
<ScoreList>
<h3> works</h3>
<li>
<Counter
incrementCount={this.incrementCount}
decrementCount={this.decrementCount}
nameof={this.state.nameof}
count={this.state.count}
/>
</li>
<li>
<Counter
incrementCount={this.incrementCount}
decrementCount={this.decrementCount}
nameof={this.state.nameof}
count={this.state.count}
/>
</li>
</ScoreList>
</div>
)
}
}
I have imported my Counter.jswhich is comprised of:
class Counter extends Component {
render() {
const { count } = this.props
const { decrementCount } = this.props
const { incrementCount } = this.props
const { nameof } = this.props
return (
<div>
<CountCell>
<Row style={{alignItems: 'center'}}>
<Col>
<CountButton
onClick={incrementCount}>
<Icon
name="icon" className="fa fa-plus score-icon"
/>
</CountButton>
</Col>
<Col >
<ScoreName>{nameof}</ScoreName>
</Col>
<Col >
<Score>{count}</Score>
</Col>
<Col>
<CountButton
onClick={decrementCount}>
<Icon
name="icon" className="fa fa-minus score-icon"
/>
</CountButton>
</Col>
</Row>
</CountCell>
</div>
)
}
}
So the increment and decrement buttons are only working globally and only for my static <li>, not my ones generated from the array. If I'm making any sense at all, how do I individually map my inc/dec buttons to each <li> and not globally?
Thank you!
You need to keep the state also be an array of objects, each storing data for a corresponding user
class Counting extends React.Component {
constructor(props) {
super(props);
this.state = {
countInfo: []
}
this.incrementCount = this.incrementCount.bind(this)
this.decrementCount = this.decrementCount.bind(this)
}
incrementCount(index) {
var countInfo = [...this.state.countInfo];
if(countInfo[index]) {
countInfo[index].count = countInfo[index].count + 1
countInfo[index].nameOf = players[index].name
}
else {
countInfo[index] = {count: 1, nameOf: players[index].name}
}
this.setState({
countInfo
})
}
decrementCount(index) {
var countInfo = [...this.state.countInfo];
if(countInfo[index]) {
countInfo[index].count = countInfo[index].count - 1
countInfo[index].nameOf = players[index].name
}
else {
countInfo[index] = {count: -1, nameOf: players[index].name}
}
this.setState({
countInfo
})
}
render() {
const listPlayers = players.map((players, index) =>
<Counter
key={players.id}
incrementCount={() => this.incrementCount(index)}
decrementCount={() => this.decrementCount(index)}
nameof={players.name}
count={players.score}
/>
);
return (
<div className="wrap">
<Titles header="Counter" subhead="A setState() project" subtitle="this will change" />
<h3>This doesn't work correctly</h3>
<ul>{listPlayers}</ul>

Categories