ComponentWillReceiveProps doesn't update the state first time - reactjs

My render method is as follows
render() {
const language = this.props.language.default.portal;
const currentUserEmail = getUserEmail();
let cars = carData.filterCars(this.props.carsToShow, this.props.filters);
return (
<div className="contentRight noPadding col-xl-10 col-lg-10 col-md-10 col-sm-9 col-xs-7">
<div className="cars" style={{position: 'relative'}}>
<ReactCSSTransitionGroup transitionName="example" transitionAppear={true} transitionAppearTimeout={500} transitionEnterTimeout={500} transitionLeaveTimeout={500}>
<div>
{this.showMsg(cars)}
<Shuffle>
{cars.map((car, i) => {
const initialReg = car.initialRegistration.slice(0,3) + car.initialRegistration.slice(6,10);
// str.slice(1, 4) extracts the second character through the fourth character (characters indexed 1, 2, and 3)
return (
<div key={car.chassis} className="carBox noPadding" style={{position: "relative"}}>
<div className="carBoxContent">
<PhotoAndFavorites car={car} language={language} favoriteActions={this.props.actionFavorites} favorites={this.props.favorites}/>
<div className="carNameAndDesc">
<div><Link to="" style={{textDecoration: 'none'}}>{car.make} {car.model}</Link></div>
<div>{car.desc}</div>
</div>
<div className="carPrice">
<div>{car.price}</div>
<div>{car.btw}</div>
</div>
<div className="extraFeatures" style={{marginBottom: 5, backgroundColor: '#eee'}}>
</div>
<div className="mainFeatures">
<div><img src="../images/portal/user/status/fuel-icon.png" style={{height: 12}}/> <span>{car.fuel}</span></div>
<div><img src="../images/portal/user/status/road-icon.png" style={{height: 12}}/> <span>{car.mileage}</span></div>
<div><img src="../images/portal/user/status/calendar-icon.png" style={{height: 12}}/> <span>{initialReg}</span></div>
</div>
<MakeOfferButton{...this.props} car={car}/><
</div>
</div>
);
})}
</Shuffle>
</div>
</ReactCSSTransitionGroup>
<div className="clearfix"/>
</div>
</div>
);
}
Redux connect is as follows :
function mapStateToProps(state, ownProps){
return {
filters: state.filters,
favorites: state.favorites,
carsToShow: state.carsToShow,
carsInCart: state.cart
};
}
function mapDispatchToProps(dispatch){
return {
actionFavorites: bindActionCreators(actionFavorites, dispatch),
actionsCart: bindActionCreators(actionCart, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(cars);
MakeOfferButton component is as follows :
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import FontAwesome from 'react-fontawesome';
import {getUserEmail} from '../../../../components/homepage/login/getUserInfo';
import {cart_types} from './cars';
export default class MakeOffer extends React.Component {
constructor(props){
super(props);
this.state = {
offer: ""
}
}
componentWillReceiveProps(nextProps){
const car = this.props.car;
const userEmail = getUserEmail();
let offer = "";
if(nextProps.carsInCart.some(i => i.info.carID == car.id && i.info.user == userEmail)){
offer = parseInt(nextProps.carsInCart.filter(i => i.info.carID == car.id && i.info.user == userEmail).map(c => c.info.offer)[0]);
}
this.setState({offer: offer});
}
makeAnOffer(car, userEmail, event){
let dataToAdd = {type: cart_types.offer, info: {carID: car.id, user: userEmail, offer: this.state.offer}};
this.props.actionsCart.addToCart(dataToAdd);
}
removeOffer(car, userEmail, event){
let dataToRemove = {info: {carID: car.id, user: userEmail}};
this.props.actionsCart.removeFromCart(dataToRemove);
}
handleOfferChange(event){
(event.target.value < 1 ) ? this.setState({offer: ""}) : this.setState({offer: event.target.value});
}
render(){
const language = this.props.language;
const car = this.props.car;
const userEmail = getUserEmail();
return (
<div className="addToCardButton">
<div className="offerButtons" style={{postion: "relative"}}>
<button type="reset" className="btnReset" onClick={this.removeOffer.bind(this, car, userEmail)}><FontAwesome name="times"/></button>
<input type="number" pattern="[0-9]*" inputMode="numeric" placeholder="Your offer..." className="offerInput" value={this.state.offer} onChange={this.handleOfferChange.bind(this)}/>
<button type="submit" className="btnSubmit" onClick={this.makeAnOffer.bind(this, car, userEmail)}><FontAwesome name="check"/></button>
</div>
</div>
);
}
};
The problem is in MakeOfferButton component. The redux action is called when I call makeAnOffer function. That works fine.
But then componentWillReceiveProps should get the new props and update the state offer. And then that state should be shown in my input. But that isn't happening.
When I click on the second one, then it is shown. The first one and also the second one.
Why the state isn't showing first time?
Any advice?

Related

Pass information from an input to another component in another file

I'm having a problem, it's been a few days, I'm studying about React and Typescript and I'm developing a temperature application, I'm stopped in a part, where I want the user to click on the submit form, the information that was typed in the input is passed to another component.
Follow my two codes below
CityWeatherSearch.tsx
import { MagnifyingGlass } from 'phosphor-react'
import { FormEvent, useCallback, useRef, useState } from 'react';
import * as Styled from './style'
export function CityWeatherSearch(){
const inputRef = useRef<HTMLInputElement>(null);
const [city,setCity] = useState('');
function handleClick(event:FormEvent) {
event.preventDefault();
const inputCity = inputRef?.current?.value;
console.log({
inputCity, city
});
}
return(
<>
<Styled.BoxSearchCity>
<div className="headerSearch">
<form>
<input type="text" placeholder='Procurar Cidade...' ref={inputRef} onChange={
event => setCity(event.target.value)} />
<button type="submit" onClick={handleClick}>
<MagnifyingGlass/>
</button>
</form>
</div>
<div className="bodySearch">
{city}
</div>
</Styled.BoxSearchCity>
</>
)
}
MainWeatherLive.tsx
import {Clock} from 'phosphor-react'
import { useFetch } from '../../GetData/useFetch'
import * as Styled from './style'
type DataWeather = {
name: string,
condition:{
text:string,
icon:string
},
temp_c:number,
hour:[{
temp_c:number,
time:string,
condition:{
text:string,
icon:string
}
}]
}
export function MainWeatherLive(){
const {dataLocation: dataWeatherApi, isFetching, dataCurrent:dataCurrentApi, dataForecast:forecastApi}
= useFetch<DataWeather>('/v1/forecast.json?key=aff6fe0e7f5d4f3fa0611008221406&q=Guarulhos?days=1&aqi=no&alerts=no');
return(
<>
<Styled.HeaderBox>
<h6>Weather Now</h6>
</Styled.HeaderBox>
<Styled.Container>
{isFetching &&
<p>Carregando...</p>
}
<div className="mainInformation">
<div className="temperatura">
<span>{dataCurrentApi?.temp_c}º</span>
</div>
<div>
A cidade é {cityName}
</div>
<div className="boxCidade">
<div className="cidade">
<span>{dataWeatherApi?.name}</span>
</div>
<div className="tempoHoras">
<span>
{new Date().toLocaleTimeString('pt-BR',{hour12:false, hour:'numeric',minute:'numeric'})} - {new Date().toLocaleDateString()}
</span>
</div>
</div>
<div className="iconeTem">
<img src={dataCurrentApi?.condition.icon} alt={dataCurrentApi?.condition.text} />
</div>
</div>
<div className="footerBox">
<div className="headerFooter">
<Clock/>
<span>Horários</span>
</div>
<div className="listaHorarios">
<ul className="boxTT">
{
forecastApi?.hour?.map(weatherA =>{
const hourTemp = weatherA.time.split(" ")[1].replace(":00","");
const hourTempNumber:number = +hourTemp;
const hourNow = new Date().getHours();
return(
<>
{
hourTempNumber == hourNow &&
<li>
<div className="titulo" key={weatherA.temp_c}>
<span>{hourTempNumber}</span>
</div>
<div className="temperatura">
<img src={weatherA.condition.icon} alt={weatherA.condition.text} />
<span>{dataCurrentApi?.temp_c}º</span>
</div>
</li>
}
{
hourTempNumber > hourNow &&
<li>
<div className="titulo" key={weatherA.temp_c}>
<span>{hourTempNumber}</span>
</div>
<div className="temperatura">
<img src={weatherA.condition.icon} alt={weatherA.condition.text} />
<span>{weatherA.temp_c}º</span>
</div>
</li>
}
</>
)
})
}
</ul>
</div>
</div>
</Styled.Container>
</>
)
}
Weather.tsx
import { CityWeatherSearch } from "./WeatherC/CityWeatherSearch";
import { MainWeatherLive } from "./WeatherC/MainWeatherLive";
import { WeatherDetails } from "./WeatherC/WeatherDetails";
import coldImage from '../assets/cold.jpg'
import sunImage from '../assets/sun.jpg'
import rainImage from '../assets/rain.jpg'
import nightVideo from '../assets/night.mp4'
import night from '../assets/night.jpg'
export const TypesWeather = {
NIGHT:{
video:{
source: nightVideo
},
image:{
source: night
}
},
OVERCAST:{
video:{
source: nightVideo
},
image:{
source: night
}
},
COLD:{
image:{
source: coldImage,
title: 'Frio'
}
},
SUN:{
image:{
source: sunImage,
title: 'Verão'
}
},
RAIN:{
image:{
source: rainImage,
title: 'Chuva'
}
},
};
export type TypesWeatherV2 = keyof typeof TypesWeather;
export function Weather(){
return (
<>
<div className="globalSite" style={{background:`linear-gradient(to bottom,rgba(0,0,0,.85) 0,rgba(0,0,0,.85) 100%),url(${TypesWeather.RAIN.image.source})`}}>
</div>
<div className="boxAllWeather">
<div className="backgroundWeather" style={{backgroundImage:`url(${TypesWeather.RAIN.image.source})`}}></div>
<div className="boxAllInff">
<div className="mainWeather">
<MainWeatherLive />
</div>
<div className="otherInfoWeather">
<CityWeatherSearch />
<WeatherDetails />
</div>
</div>
</div>
</>
)
}
I want to pass the city typed in CityWeatherSearch.tsx to MainWeatherLive.tsx. Where is the space 'A cidade é {cityName}' reserved, I've tried everything, but I haven't been able to, could you help me?
You can do this in several ways:
parent -> child : use props
child -> parent : use callback/event emitter
no direct relationship : consider using state management tool like
redux
Just lift your state uo to the parent component and pass if to the cild components as props:
function WeatherPage() {
const [city,setCity] = useState('');
return (
<>
<CityWeatherSearch city={city} setCity={setCity}/>
//...
<MainWeatherLive city={city}/>
//...
</>
)
}
function CityWeatherSearch({city, setCity}) {
// your code here, only without const [city, setCity] useState()
}
function MainWeatherLive({city}) {
// your code here, now you can access city
}
If your two components don't have a direct common parent and you don't want to pass down city and setCity through a deep component hierarchy, think about using useContext to share state within your application.

Array has duplicated records when using checkboxes to populate an array using React

I have trouble with simple task of adding elements selected in checkboxes to an array in component state. It seems like the push method for state.toppings (Editor.js) is invoked twice for each checkbox click, even though console.log shows that updateFormValueCheck method is invoked once per click. Can anyone help?
This is App.js
import React, { Component } from "react";
import { Editor } from "./Editor";
import { Display } from "./Display";
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
formData: {}
}
}
submitData = (newData) => {
console.log("newData", newData)
this.setState({ formData: newData });
}
render() {
return <div className="container-fluid">
<div className="row p-2">
<div className="col-6">
<Editor submit={this.submitData} />
</div>
<div className="col-6">
<Display data={this.state.formData} />
</div>
</div>
</div>
}
}
This is Editor.js
import React, { Component } from "react";
export class Editor extends Component {
constructor(props) {
super(props);
this.state = {
toppings: ["Strawberries"]
}
this.toppings = ["Sprinkles", "Fudge Sauce",
"Strawberries", "Maple Syrup"]
}
updateFormValueCheck = (event) => {
event.persist();
this.setState(state => {
if (event.target.checked) {
state.toppings.push(event.target.name);
} else {
let index = state.toppings.indexOf(event.target.name);
state.toppings.splice(index, 1);
}
}, () => this.props.submit(this.state));
}
render() {
return <div className="h5 bg-info text-white p-2">
<div className="form-group">
<label>Ice Cream Toppings</label>
{this.toppings.map(top =>
<div className="form-check" key={top}>
<input className="form-check-input"
type="checkbox" name={top}
value={this.state[top]}
checked={this.state.toppings.indexOf(top) > -1}
onChange={this.updateFormValueCheck} />
<label className="form-check-label">{top}</label>
</div>
)}
</div>
</div>
}
}
This is Display.js
import React, { Component } from "react";
export class Display extends Component {
formatValue = (data) => Array.isArray(data)
? data.join(", ") : data.toString();
render() {
let keys = Object.keys(this.props.data);
if (keys.length === 0) {
return <div className="h5 bg-secondary p-2 text-white">
No Data
</div>
} else {
return <div className="container-fluid bg-secondary p-2">
{keys.map(key =>
<div key={key} className="row h5 text-white">
<div className="col">{key}:</div>
<div className="col">
{this.formatValue(this.props.data[key])}
</div>
</div>
)}
</div>
}
}
}
The output is:
You cannot directly mutate this.state, it can only be done using this.setState. For more info. refer this: Why can't I directly modify a component's state, really?
Therefore, you need to update your Editor component as follows.
componentDidMount is used to display the initial state during the initial rendering. Then componentDidUpdate is used to render the state changes through display component whenever it's updated.
import React, { Component } from "react";
export class Editor extends Component {
constructor(props) {
super(props);
this.state = {
toppings: ["Strawberries"],
};
this.toppings = ["Sprinkles", "Fudge Sauce", "Strawberries", "Maple Syrup"];
}
updateFormValueCheck = (event) => {
event.persist();
let data;
if (event.target.checked) {
data = [...this.state.toppings, event.target.name];
} else {
const index = this.state.toppings.indexOf(event.target.name);
const temp = [...this.state.toppings];
temp.splice(index, 1);
data = temp;
}
this.setState({
toppings: data,
});
};
componentDidMount() {
this.props.submit(this.state.toppings);
}
componentDidUpdate(prevPros, prevState) {
if (prevState.toppings !== this.state.toppings) {
this.props.submit(this.state.toppings);
}
}
render() {
console.log(this.state);
return (
<div className="h5 bg-info text-white p-2">
<div className="form-group">
<label>Ice Cream Toppings</label>
{this.toppings.map((top) => (
<div className="form-check" key={top}>
<input
className="form-check-input"
type="checkbox"
name={top}
value={this.state[top]}
checked={this.state.toppings.indexOf(top) > -1}
onChange={this.updateFormValueCheck}
/>
<label className="form-check-label">{top}</label>
</div>
))}
</div>
</div>
);
}
}
Hope this would be helpful to solve your issue.

react redux two components

I have a component that will display the products which is coming from backend and a component that receives the products to filter but I have doubt that receive by redux my product list.
should i put for my filters component receive?
or should return the same as I get in my product component?
or should I create an action to filter what I need already?
my home:
return (
<div className="container">
<h1>Shopping</h1>
<hr />
<div className="row">
<div className="col-md-3"><Filters/></div>
<div className="col-md-9"><Products/></div>
</div>
</div>
)
my component products:
import React, { Component } from 'react'
import {connect} from 'react-redux'
import { ProductsFetchData } from '../../store/actions/productsFetch';
import util from '../../util';
class HomeProducts extends Component {
componentDidMount() {
this.props.fetchData('/products');
}
render() {
const productItems = this.props.products.map( product => (
<div className="col-md-4 pt-4 pl-2">
<div className = "thumbnail text-center">
<a href={`#${product.id}`} onClick={(e)=>this.props.handleAddToCard(e,product)}>
<p>
{product.name}
</p>
</a>
</div>
<b>{util.formatCurrency(product.price)}</b>
<button className="btn btn-primary" onClick={(e)=>this.props.handleAddToCard(e,product)}>Add to Cart</button>
</div>
)
)
return (
<div className="container">
<div className="row">
{productItems}
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
products: state.Products,
hasErrored: state.ProductsHasErrored,
isLoading: state.ProductsIsLoading
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: () => dispatch(ProductsFetchData())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(HomeProducts);
my components filter
import React, { Component } from 'react';
import './style.css'
class FilterHome extends Component {
render() {
return (
<>
<div className="row">
<button className="filterbt btn btn-danger btn-rounded">Filters</button>
<div className=" mt-4 d-flex flex-column">
<p className="textCategory">CATEGORY</p>
<div className="category d-flex flex-column">
<p>Stat Trak</p>
<p>Normal</p>
</div>
<p className="textCategory">EXTERIOR</p>
<div className="category d-flex flex-column">
<p>Factory New ()</p>
<p>Minimal Wear ()</p>
<p>Field Tested ()</p>
<p>Well Worn ()</p>
<p>Battle Scarred ()</p>
</div>
</div>
</div>
</>
)
}
}
export default FilterHome;
1.redux-state: this is the registering point for all your api responses(all the data from back-end is stored here as prestine and is available as props to any container when you mapStateToProps).
2.local-state: this lives only in your container and all it's child components.
3.filter:
a)from server:
you make a request to the server and get a response of filtered products. this
is more practical.
eg: you have /products?page=1 and you want to search it by some category, let's
say by a specific company. with the data you have at the moment(page 1), you
may have only let's say 1 or even no product relevant to that company, but in fact there are n-numbers of products of the same company available at the server. so this can only be assumed as the
most practical way.
b)filtering from the local-state:
if this is what your'e trying to achieve,
1. you need only one container, HomeProducts
2. make ProductItems as a component. wer'e gonna reuse this component to render both.
**you wrote your filter as an independent container. but those filter functionality should be availabe inside the home page itself. isn't it, i mean you're filtering from the home page itself not from another page. if so, add it to the home page itself
1.HomePage
import React, { Component } from 'react'
import {connect} from 'react-redux'
import { ProductsFetchData } from '../../store/actions/productsFetch';
import util from '../../util';
import ProductItems from '<<..your relative path>>'
import FilterHome from '<<..your relative path>>'
class HomeProducts extends Component {
constructor(props) {
super(props)
this.state = {
productList: null,
}
}
componentDidMount() {
//this.props.fetchData('/products');
this.props.fetchData('page=1');
}
componentWillReceiveProps(nextProps) {
const { productList } = this.state
const { products } = this.props
// this only handles when local state is empty. add your logic here..
!productList && this.setState(prevState => ({
...prevState,
productList: products,
}))
}
handleFilter = (category) => {
// if it's an api call
const { fetchData } = this.props
fetchData(`page=1&category=${category}`)
//or you simply want to filter this local state(not-recommended)
const { products } = this.props
const productsList = [...products].filter(item => item.category === category)
this.setState(prevState => ({
...prevState,
productsList,
}))
}
render() {
const { productList } = this.state
const { handleFilter } = this
return (
<div className="container">
<FilterHome {...{ handleFilter }} />
<ProductItems {...{ productsList }} />
</div>
)
}
}
const mapStateToProps = (state) => {
return {
products: state.Products,
hasErrored: state.ProductsHasErrored,
isLoading: state.ProductsIsLoading
};
};
//it may not suit depends on your api, but you get the idea..
const mapDispatchToProps = (dispatch) => {
return {
// fetchData: () => dispatch(ProductsFetchData())
fetchData: params => dispatch(ProductsFetchData(`/products?${params}`))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(HomeProducts);
2.ProductItems
import React from 'react'
const ProductItem = ({ product }) => {
return (
<div className="col-md-4 pt-4 pl-2">
<div className = "thumbnail text-center">
<a href={`#${product.id}`} onClick={(e)=>this.props.handleAddToCard(e,product)}>
<p>
{product.name}
</p>
</a>
</div>
<b>{util.formatCurrency(product.price)}</b>
<button className="btn btn-primary" onClick={(e)=>this.props.handleAddToCard(e,product)}>Add to Cart</button>
</div>
)
}
const ProductItems = ({ productList }) => {
return (
<div className="row">
{productList && productList.map(product => <ProductItem key={product.id} {...{ product }} />)}
</div>
)
}
export default ProductItems
3.FilterHome
import React from 'react'
const FilterHome = ({ handleFilter }) => {
return (
<div className="row">
<button className="filterbt btn btn-danger btn-rounded">Filters</button>
<div className=" mt-4 d-flex flex-column">
<p className="textCategory">CATEGORY</p>
<div className="category d-flex flex-column">
<a href="" className="text-decoration-none" onClick={() => handleFilter('stat_trak')}><p>Stat Trak</p></a>
<a href="" className="text-decoration-none" onClick={() => handleFilter('normal')}><p>Normal</p></a>
</div>
<p className="textCategory">EXTERIOR</p>
<div className="category d-flex flex-column">
<a href="" className="text-decoration-none" onClick={() => handleFilter('factory_new')}><p>Factory New ()</p></a>
<a href="" className="text-decoration-none" onClick={() => handleFilter('minimal_wear')}><p>Minimal Wear ()</p></a>
<a href="" className="text-decoration-none" onClick={() => handleFilter('field_tested')}><p>Field Tested ()</p></a>
<a href="" className="text-decoration-none" onClick={() => handleFilter('well_worn')}><p>Well Worn ()</p></a>
<a href="" className="text-decoration-none" onClick={() => handleFilter('battle_scarred')}><p>Battle Scarred ()</p></a>
</div>
</div>
</div>
)
}
export default FilterHome
i roughly re-wrote it, may contain bugs..
first add it to the local state and employ a call back to the filter component..
handleFilter = (category) => {
const { Products } = this.state
const products = {...Products}
//or depends on the type, so it wont mutate the real data>> const products = [...Products]
return products.filter(item => item.category === category)
}
this is what is understood from your comment. is that it?

React app state not updating

I am creating a basic React app to hold books on certain shelves and am trying to create the functionality to move books between shelves.
The problem I have is that when I select the new target shelf from the book objects dropdown, the onUpdateShelf method in ListBooks.js does not seem to initiate the update and subsequent state change.
I am new to React, my understanding is that calling the setState function in updateShelf should trigger the re-render with the updated object.
My question then is, is my implementation wrong and where?
App.js
import React, { Component } from 'react'
import ListBooks from './ListBooks'
import * as BooksAPI from './utils/BooksAPI'
import { Route } from 'react-router-dom'
class BooksApp extends Component {
state = {
books: []
}
componentDidMount() {
BooksAPI.getAll()
.then((books) => {
this.setState(() => ({
books
}))
})
}
updateShelf = (book, shelf) => {
console.log(book)
console.log(shelf)
this.books.forEach(b => {
if(b.id === book.id && b.shelf !== book.shelf ) {
b.shelf = shelf
this.setState((currentState) => ({
books: currentState.books
}))
}
});
BooksAPI.update(book, shelf)
}
render() {
return (
<div>
<Route exact path='/' render={() => (
<ListBooks
books={this.state.books}
onUpdateShelf={this.updateShelf}
/>
)} />
</div>
)
}
}
export default BooksApp
And my ListBooks.js
import React, { Component } from 'react';
import PropTypes from 'prop-types'
import './App.css'
const shelves = [
{
key: 'currentlyReading',
name: 'Currently Reading'
},
{
key: 'wantToRead',
name: 'Want To Read'
},
{
key: 'read',
name: 'Read'
}
];
class ListBooks extends Component {
static propTypes = {
books: PropTypes.array.isRequired
}
state = {
showSearchPage: false,
query: ''
}
render() {
const { books, onUpdateShelf } = this.props
function getBooksForShelf(shelfKey) {
return books.filter(book => book.shelf === shelfKey);
}
console.log(books);
return(
<div className="app">
{this.state.showSearchPage ? (
<div className="search-books">
<div className="search-books-bar">
<a className="close-search" onClick={() => this.setState({ showSearchPage: false })}>Close</a>
<div className="search-books-input-wrapper">
{/*
NOTES: The search from BooksAPI is limited to a particular set of search terms.
You can find these search terms here:
https://github.com/udacity/reactnd-project-myreads-starter/blob/master/SEARCH_TERMS.md
However, remember that the BooksAPI.search method DOES search by title or author. So, don't worry if
you don't find a specific author or title. Every search is limited by search terms.
*/}
<input type="text" placeholder="Search by title or author"/>
</div>
</div>
<div className="search-books-results">
<ol className="books-grid"></ol>
</div>
</div>
) : (
<div className="list-books">
<div className="list-books-title">
<h1>My Reads</h1>
</div>
<div className="list-books-content">
<div>
{ shelves.map((shelf) => (
<div key={shelf.key} className="bookshelf">
<h2 className="bookshelf-title">{shelf.name}</h2>
<div className="bookshelf-books">
<ol className="books-grid">
<li>
{ getBooksForShelf(shelf.key).map((book) => (
<div key={book.id} className="book">
<div className="book-top">
<div className="book-cover" style={{ width: 128, height: 193, backgroundImage: `url(${book.imageLinks.thumbnail})` }}></div>
<div className="book-shelf-changer">
<select>
<option value="none" disabled>Move to...</option>
<option value="currentlyReading" onClick={() => onUpdateShelf(book, 'currentlyReading')} >Currently Reading</option>
<option value="wantToRead" onClick={() => onUpdateShelf(book, 'wantToRead')} >Want to Read</option>
<option value="read" onClick={() => onUpdateShelf(book, 'read')} >Read</option>
<option value="none" onClick={() => onUpdateShelf(book, '')} >None</option>
</select>
</div>
</div>
<div className="book-title">{book.title}</div>
<div className="book-authors">{book.author}</div>
</div>
))}
</li>
</ol>
</div>
</div>
)) }
</div>
</div>
<div className="open-search">
<a onClick={() => this.setState({ showSearchPage: true })}>Add a book</a>
</div>
</div>
)}
</div>
)
}
}
export default ListBooks
When you passe updateShelf to your component ListBooks, you lose the value of this inside updateShelf, and as a result this.books will be undefined.
You can solve this by, either doing this inside the constructor of BooksApp :
this.updateShelf = this.updateShelf.bind(this)
Or by using arrow functions:
<Route exact path='/' render={() => (
<ListBooks
books={this.state.books}
onUpdateShelf={() => { this.updateShelf()} }
/>
)} />
EDIT
You are already using arrow functions inside BooksApp, so what I said before isn't necessary.
But still, you should use this.state.books and not this.books inside updateShelf.

react change the class of list item on click

I have a react element like this:
import React, { PropTypes, Component } from 'react'
class AlbumList extends Component {
constructor(props) {
super(props);
this.state = {'active': false, 'class': 'album'};
}
handleClick() {
if(this.state.active){
this.setState({'active': false,'class': 'album'})
}else{
this.setState({'active': true,'class': 'active'})
}
}
render() {
var album_list
const {user} = this.props
if(user.data){
list = user.data.filter(album => album.photos).map((album => {
return <div className={"col-sm-3"} key={album.id}>
<div className={this.state.class} key={album.id} onClick={this.handleClick.bind(this)}>
<div className={"panel-heading"}>{ album.name }</div>
<div className={"panel-body"}>
<img className={"img-responsive"} src={album.photo.source} />
</div>
</div>
</div>
}))
}
return (
<div className={"container"}>
<div className="row">
{list}
</div>
</div>
)
}
}
export default AlbumList
Here map gives the list of filter data as I wanted. Here what I am doing changes the class of all the list element if I click on one.
I am getting the class name from this.state.class
How can I change the class of only element that i have clicked..
Thanks in advance ...
I have considered it once.So you have so many divs and you want to know which is clicked.My way to solve this problem is to give a param to the function handleClick and you can get the dom of the div while you click the div.Like this:
array.map(function(album,index){
return <div onClick={this.handleClick}/>
})
handleClick(e){
console.log(e.target);
e.target.className = 'active';
...
}
Then you have a param for this function.While you can use the e.target to get the dom of your div which is clicked.
There are some mistake into your code about the state.class.
class AlbumList extends Component {
constructor(props) {
super(props);
this.state = {'active': false, 'class': 'album'};
}
handleClick(e) {
if(e.target.class === 'active'){
e.target.className = 'album'
}else{
e.target.className = 'active'
}
}
render() {
var album_list
const {user} = this.props
if(user.data){
list = user.data.filter(album => album.photos).map((album => {
return (
<div className={"col-sm-3"} key={album.id}>
<div className='active' key={album.id} onClick={this.handleClick.bind(this)}>
<div className={"panel-heading"}>{ album.name }</div>
<div className={"panel-body"}>
<img className={"img-responsive"} src={album.photo.source} />
</div>
</div>
</div>
)
}))
}
return (
<div className={"container"}>
<div className="row">
{list}
</div>
</div>
)
}
}
You can try this and tell me anything wrong.

Resources