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.
Related
I am new to react and trying to understand how to debug and figure out how/why query trim is not a function below:
Full code:
import React, { Component } from 'react';
import {BrowserRouter as Router} from 'react-router-dom';
import { Link } from 'react-router-dom';
import * as BooksAPI from './data/BooksAPI';
import { observable } from 'mobx';
import { observer } from 'mobx-react';
const BookSearch = observer(class BookSearch extends Component{
state = observable({
query: '',
books: []
})
searchBooks = (query) =>{
this.setState({ query: query.trim() }); <----- here
BooksAPI.search(query, 20).then((books) =>{
console.log(books);
this.setState({ books: books });
})
}
render(){
const { query } = this.state;
const bookSearch = this.state.books.map((book) =>
<li key={book.id}>
<div 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 onChange={(event) => this.props.bookChange(book, event.target.value)}>
<option>Move to...</option>
<option value="currentlyReading">Currently Reading</option>
<option value="wantToRead">Want to Read</option>
<option value="read">Read</option>
<option value="none">None</option>
</select>
</div>
</div>
<div className="book-title">{ book.title }</div>
<div className="book-authors">{ book.authors.map((author) =>
<span key={ author + book.title } >{ author }</span>
)}</div>
</div>
</li>
) ;
return(
<Router>
<div className="App">
<div className="search-books">
<div className="search-books-bar">
<Link className="close-search" to='/'>Close</Link>
<div className="search-books-input-wrapper">
<input
type="text"
placeholder="Search by title or author"
onChange={ this.searchBooks}
/>
</div>
</div>
<div className="search-books-results">
<div className="bookshelf">
<h2 className="bookshelf-title">{ this.state.query }</h2>
<div className="bookshelf-books">
<ol className="books-grid">
{ bookSearch }
</ol>
</div>
</div>
</div>
</div>
</div>
</Router>
)
}
})
export default BookSearch
The onChange handler of an <input> component gets passed an event (which is a react synthetic event instance) rather than just the value of the input:
You can access the actual value via event.target.value:
searchBooks = (event) =>{
this.setState({ query: event.target.value.trim() });
// ...
}
i need your help because i can't make a single page website with many components who are hide and show. I have begin with this code but when I click on all link, all of the components is showing at the same time. I would show one component when I click on "portfolio" and hide the others. And if I click on "Offres" the "portfolio" is hidden like the others for show "Offres".
Thank you so much and sorry for my english.
import './style.css'
import logo_studio from './assets/logo_studio.png'
import Main from './components/Main'
import Portfolio from './components/Portfolio'
import Offres from './components/Offres'
import Contact from './components/Contact'
import Apropos from './components/Apropos'
class App extends Component {
constructor () {
super()
this.state = {
name: 'React',
showHideDemo1: false,
showHideDemo2: false,
showHideDemo3: false,
showHideDemo4: false
}
this.hideComponent = this.hideComponent.bind(this)
}
hideComponent (name) {
switch (name) {
case 'showHideDemo1':
this.setState({ showHideDemo1: !this.state.showHideDemo1 })
break
case 'showHideDemo2':
this.setState({ showHideDemo2: !this.state.showHideDemo2 })
break
case 'showHideDemo3':
this.setState({ showHideDemo3: !this.state.showHideDemo3 })
break
case 'showHideDemo4':
this.setState({ showHideDemo4: !this.state.showHideDemo4 })
break
default:
return <Main />
}
}
render () {
const { showHideDemo1, showHideDemo2, showHideDemo3, showHideDemo4 } = this.state
return (
<div className='section'>
<img src={logo_studio} class='logo1' alt='' />
<div className='connect sweep-to-right'>
<div>Espace client</div>
<i className='fas fa-user-circle userLogo' />
</div>
<div className='menu1'>
<ul>
<li className='hvr-float underline-from-left' onClick={() => this.hideComponent('showHideDemo1')}>Portfolio</li>
<li className='hvr-float underline-from-left' onClick={() => this.hideComponent('showHideDemo2')}>Offres</li>
<li className='hvr-float underline-from-left' onClick={() => this.hideComponent('showHideDemo3')}>A propos</li>
<li className='hvr-float underline-from-left' onClick={() => this.hideComponent('showHideDemo4')}>Contact</li>
</ul>
</div>
{showHideDemo1 && <Portfolio />}
{showHideDemo2 && <Offres />}
{showHideDemo3 && <Apropos />}
{showHideDemo4 && <Contact />}
</div>
)
}
}
export default App```
You have to use the Router library for this task react-router-dom. You can manage this small scenario by changing your code. As you have to show only one component at a time, you can manage this by only one state variable let say showComponentCount
Change your code by given code
import './style.css'
import logo_studio from './assets/logo_studio.png'
import Main from './components/Main'
import Portfolio from './components/Portfolio'
import Offres from './components/Offres'
import Contact from './components/Contact'
import Apropos from './components/Apropos'
class App extends Component {
constructor () {
super()
this.state = {
name: 'React',
showComponentCount: 0
}
this.showComponent = this.showComponent.bind(this)
}
showComponent (count) {
this.setState({ showComponentCount: count })
}
render () {
const { showComponentCount } = this.state
return (
<div className='section'>
<img src={logo_studio} class='logo1' alt='' />
<div className='connect sweep-to-right'>
<div>Espace client</div>
<i className='fas fa-user-circle userLogo' />
</div>
<div className='menu1'>
<ul>
<li className='hvr-float underline-from-left' onClick={() => this.showComponent(0)}>Portfolio</li>
<li className='hvr-float underline-from-left' onClick={() => this.showComponent(1)}>Offres</li>
<li className='hvr-float underline-from-left' onClick={() => this.showComponent(2)}>A propos</li>
<li className='hvr-float underline-from-left' onClick={() => this.showComponent(3)}>Contact</li>
</ul>
</div>
{showComponentCount == 0 ? <Portfolio /> : null}
{showComponentCount == 1 ? <Offres /> : null}
{showComponentCount == 2 ?<Apropos /> : null}
{showComponentCount == 3 ? <Contact /> : null}
</div>
)
}
}
export default App
In this code, I have changed hideComponent to showComponent as We have to show only one component at a time.
We just assign each component a number so that we can check it for rendering a component.
I hope this will work for you.
You should use one value to keep a track of what is opened and display it. I am using showItem to keep a track of what is open.
import './style.css'
import logo_studio from './assets/logo_studio.png'
import Main from './components/Main'
import Portfolio from './components/Portfolio'
import Offres from './components/Offres'
import Contact from './components/Contact'
import Apropos from './components/Apropos'
class App extends Component {
constructor () {
super()
this.state = {
name: 'React',
showItem: ''
}
this.hideComponent = this.hideComponent.bind(this)
}
hideComponent (name) {
switch (name) {
case 'showHideDemo1':
this.setState({ showItem: this.state.showItem !== 'showHideDemo1' ? 'showHideDemo1' : '' })
break
case 'showHideDemo2':
this.setState({ showItem: this.state.showItem !== 'showHideDemo2' ? 'showHideDemo2' : '' })
break
case 'showHideDemo3':
this.setState({ showItem: this.state.showItem !== 'showHideDemo3' ? 'showHideDemo3' : '' })
break
case 'showHideDemo4':
this.setState({ showItem: this.state.showItem !== 'showHideDemo4' ? 'showHideDemo4' : '' })
break
default:
return <Main />
}
}
render () {
const { showItem } = this.state
return (
<div className='section'>
<img src={logo_studio} class='logo1' alt='' />
<div className='connect sweep-to-right'>
<div>Espace client</div>
<i className='fas fa-user-circle userLogo' />
</div>
<div className='menu1'>
<ul>
<li className='hvr-float underline-from-left' onClick={() => this.hideComponent('showHideDemo1')}>Portfolio</li>
<li className='hvr-float underline-from-left' onClick={() => this.hideComponent('showHideDemo2')}>Offres</li>
<li className='hvr-float underline-from-left' onClick={() => this.hideComponent('showHideDemo3')}>A propos</li>
<li className='hvr-float underline-from-left' onClick={() => this.hideComponent('showHideDemo4')}>Contact</li>
</ul>
</div>
{showItem === 'showHideDemo1' && <Portfolio />}
{showItem === 'showHideDemo2' && <Offres />}
{showItem === 'showHideDemo3' && <Apropos />}
{showItem === 'showHideDemo4' && <Contact />}
</div>
)
}
}
export default App```
import React, { Component, useState } from 'react';
import algoliasearch from 'algoliasearch/lite';
import {
InstantSearch,
Hits,
SearchBox,
Highlight,
connectRefinementList,
} from 'react-instantsearch-dom';
import PropTypes from 'prop-types';
import './App.css';
const searchClient = algoliasearch(
'test',
'test'
);
class App extends Component {
constructor(props) {
super(props);
this.state = {
selectedCountries: []
}
}
render() {
const RefinementList = ({
items,
isFromSearch,
refine,
searchForItems,
createURL,
}) => {
return (
<ul style={{ listStyle: 'none' }}>
{
items &&
items.map(item => (
<li key={item.label}>
<input
type="checkbox"
checked={item.isRefined}
// href={createURL(item.value)}
style={{ fontWeight: item.isRefined ? 'bold' : '' }}
onChange={event => {
// event.preventDefault();
refine(item.value);
}}
/>
{isFromSearch ? (
<Highlight attribute="label" hit={item} />
) : (
item.label
)}{' '}
({item.count})
</li>
))}
</ul>
)
};
const CustomRefinementList = connectRefinementList(RefinementList);
return (
<div className="container">
<InstantSearch searchClient={searchClient} indexName="parterns">
<div className="search-panel">
<div className="search-panel__results">
<SearchBox
className="searchbox"
translations={{
placeholder: '',
}}
searchAsYouType={false}
/>
<Hits hitComponent={Hit} />
<br />
<br />
<button onClick={(e) => {
const that = this;
e.preventDefault();
that.setState({
selectedCountries: Array.from(new Set([...that.state.selectedCountries, 'India']))
})
}
}
>
Select India
</button>
<br />
<button onClick={(e) => {
const that = this;
e.preventDefault();
that.setState({
selectedCountries: Array.from(new Set([...that.state.selectedCountries, 'USA']))
})
}
}
>
Select Usa
</button>
<br />
<h3>Location</h3>
<div className="region">
<CustomRefinementList
operator="or"
limit={10}
defaultRefinement={[]}
attribute="region" />
</div> <br />
<CustomRefinementList
operator="or"
limit={this.state.selectedCountries.length}
defaultRefinement={this.state.selectedCountries}
attribute="country" />
<br />
<br />
</div>
</div>
</InstantSearch>
</div>
);
}
}
function Hit(props) {
return (
<article onClick={() => alert(props.hit.title)}>
<h1>
<Highlight attribute="title" hit={props.hit} />
</h1>
</article>
);
}
Hit.propTypes = {
hit: PropTypes.object.isRequired,
};
export default App;
The problem is all previously selected filters are getting cleared.
For example initially I click on filter ex: North America
So I will get filtered results for North America.
Now I want to have Country filter which will be visible when click on button ex Select USA
When I am clicking on Button for ex: Select USA then I am setting state because I want to render it dynamically but issue is previous state is getting cleared how can I preserve previous state for component.
I have a list of topics and groups being returned from an API call. Topics belongs to at least 1 or more groups. The topics are currently filtered by the groups that are selected. Each group selected is set or removed in the selectedGroups state. I have an input search box which is used to help the user find a topic, when they start typing in the textbox I want a dropdown just below showing if any topic titles match their search input. When they click that topic it should only show that topic in the topics state.
Example.. if I type..
"Jo"
We get a dropdown of topics just below as suggestions and should render as in the dropdown:-
John..
Johnny..
Joan..
etc
Then when we click one of these topics in the dropdown, the state for topics update. So yes it will just show one topic in this case.
I have the search input and onchange method called handleInputChange
I am getting an error: Property 'search' does not exist on type 'PracticeAreas'. and not sure where I should be heading towards getting this to work correctly. Any help would be really grateful, thanks
I have included example data from the API call
And the react script
Main:
import * as React from 'react';
import './PracticeAreas.css';
import IReportGroup from 'src/model/IReportGroup';
import { Loader } from '../loader/Loader';
import Suggestions from './Suggestions'
export interface IGroupTopics {
id: string
name: string,
groups: string[]
}
interface IOwnProps {
}
interface IOwnState {
groups: IReportGroup[],
topics: IGroupTopics[],
selectedGroups: IReportGroup[],
query: string,
}
class PracticeAreas extends React.Component<IOwnProps, IOwnState> {
constructor(props: IOwnProps) {
super(props);
this.state = {
groups: [],
topics: [],
selectedGroups: [],
query: ""
}
}
public render() {
const { topics } = this.state;
return topics.length > 0 ?
this.renderData(topics) :
<Loader />
}
public renderData(data: any) {
return (
<div className="col-md-12 practiceAreas">
<h1>Practice Areas</h1>
<div className="selection-refinement">
<div className="refinement-search">
<form>
<input
placeholder="Search for..."
ref={input => this.search = input}
onChange={this.handleInputChange}
/>
<Suggestions topics={this.state.topics} />
</form>
</div>
</div>
<ul className="list-inline groupedTags">
{this.state.groups.map((item,i) =>
<li key={i}>
<a className={"navigator-tags " + (this.groupInState(item) ? "active" : "")} onClick={() => this.setSelectedGroups(item)}>
{item.name}
</a>
</li>
)}
</ul>
<div className="row practiceAreasContainer">
{this.state.topics.filter(topic => this.topicInGroupSelection(topic)).map((item,i) =>
<div key={i} className="result">
<div className="col-md-6 result-item">
<div className="item-container default shadowed item-content highlight row">
<div className="col-sm-12 no-padding">
<p>Editor: John Sinclair, Eric Draven, Coco Zames</p>
<p>Beiten Burkhardt</p>
<div className="row no-margin">
<div className="col-12 col-sm-10 text-content">
<h3>
<a href="#" >{item.name}</a>
</h3>
<p className="summary">
Summary
</p>
</div>
<div className="col-10 col-sm-2 links-container rhs">
Compare
<div className="divider" />
View
</div>
</div>
</div>
</div>
</div>
</div>
)}
</div>
<div className="row text-center">
<a className="lex-primary-btn medium-btn">Load more</a>
</div>
</div>
);
}
public handleInputChange = () => {
this.setState({
query: this.search.value
}, () => {
if (this.state.query && this.state.query.length > 1) {
// this.showDropdown()
if (this.state.query.length % 2 === 0) {
this.state.topics
}
} else if (!this.state.query) {
// this.hideDropdown()
}
})
}
public componentDidMount() {
fetch(`.../api/v2/navigator/reports/topics`, {
method: "GET",
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
}})
.then((res) => res.json()
.then((data) => {
this.setState({
groups: data.groups,
topics: data.data
});
}));
}
public setSelectedGroups = (group: IReportGroup) => {
// remove from state
if (this.groupInState(group)) {
this.setState(state => ({
selectedGroups: state.selectedGroups.filter(t => t.id !== group.id)
}));
// set state
} else {
this.setState(previousState => ({
selectedGroups: [...previousState.selectedGroups, group]
}));
}
}
public topicInGroupSelection = (topic: IGroupTopics) => {
return (this.state.selectedGroups.length > 0 ? this.state.selectedGroups.some(item => topic.groups.some(group => group === item.id)) : true)
}
public groupInState = (group: IReportGroup) => {
return this.state.selectedGroups.some(item => group.id === item.id);
}
}
export default PracticeAreas
Suggestions (which should topics in the state):
import * as React from 'react';
const Suggestions = (props) => {
const options = props.topics.map(r => (
<li key={r.id}>
{r.name}
</li>
))
return <ul>{options}</ul>
}
export default Suggestions
Data ex:
<ReportSelectionCriteriaResponse xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/">
<Data xmlns:d2p1="http://schemas.datacontract.org/2004/07/">
<d2p1:NavigatorReportSelection>
<d2p1:About>test title 4</d2p1:About>
<d2p1:Groups xmlns:d4p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d4p1:guid>d21384b5-27be-4bfc-963d-0d2ad40dbbfb</d4p1:guid>
</d2p1:Groups>
<d2p1:Id>2fb2783c-f48e-4d49-8098-0d39e4a16e7a</d2p1:Id>
<d2p1:Name>Test</d2p1:Name>
<d2p1:ParentId i:nil="true"/>
<d2p1:Selected>false</d2p1:Selected>
<d2p1:Type>Topics</d2p1:Type>
<d2p1:Visible>true</d2p1:Visible>
</d2p1:NavigatorReportSelection>
<d2p1:NavigatorReportSelection>
<d2p1:About i:nil="true"/>
<d2p1:Groups xmlns:d4p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d4p1:guid>2fb2783c-f48e-4d49-8098-0d39e4a16e7a</d4p1:guid>
</d2p1:Groups>
<d2p1:Id>47cb7f1d-2267-426c-9f7f-0df3b9291fb7</d2p1:Id>
<d2p1:Name>Another test topic</d2p1:Name>
<d2p1:ParentId i:nil="true"/>
<d2p1:Selected>false</d2p1:Selected>
<d2p1:Type>Topics</d2p1:Type>
<d2p1:Visible>true</d2p1:Visible>
</d2p1:NavigatorReportSelection>
</Data>
<Groups xmlns:d2p1="http://schemas.datacontract.org/2004/07/">
<d2p1:NavigatorReportSelectionGroup>
<d2p1:Focused>false</d2p1:Focused>
<d2p1:Id>2fb2783c-f48e-4d49-8098-0d39e4a16e7a</d2p1:Id>
<d2p1:Name>Allan's Test group</d2p1:Name>
<d2p1:Order>0</d2p1:Order>
<d2p1:Type>Topics</d2p1:Type>
</d2p1:NavigatorReportSelectionGroup>
<d2p1:NavigatorReportSelectionGroup>
<d2p1:Focused>false</d2p1:Focused>
<d2p1:Id>47cb7f1d-2267-426c-9f7f-0df3b9291fb7</d2p1:Id>
<d2p1:Name>Another test topic group</d2p1:Name>
<d2p1:Order>1</d2p1:Order>
<d2p1:Type>Topics</d2p1:Type>
</d2p1:NavigatorReportSelectionGroup>
</Groups>
</ReportSelectionCriteriaResponse>
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?