How do I preserve previous state of React InstantSearch - reactjs

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.

Related

Some Problem in Params, Not getting the Emp id ? possible solution

Error Are : Uncaught TypeError: Cannot read properties of undefined (reading 'params'),
The above error occurred in the component:
I might think i problem is in the passing props
App component
class App extends Component {
render() {
return (
<BrowserRouter>
<Routes>
<Route exact path="/" element={<Navigate to={"/card"} />}></Route>
<Route path="/card" element={<Card />} />
<Route path="/edit/:id" element={<EditEmployee />} />
</Routes>
</BrowserRouter>
);
}
}
card Component
const emp1 = {
empId: 100,
empName: "Jack",
age: 30,
salary: 50000,
image: image1,
achievements:
"Has got 3 bravo awards and 1 MVP award. Has worked on cutting edge technologies as well",
};
const emp2 = {
empId: 101,
empName: "Jane",
age: 24,
salary: 40000,
image: image2,
achievements: "No major achievements so far",
};
export default class Card extends React.Component {
empArr = [emp1, emp2];
render() {
return (
<div>
<h3 className="text-center text-primary">Employee Details</h3>
<div className="row">
{this.empArr.map((emp) => (
<Employee key={emp.empId} emp={emp} />
))}
</div>
</div>
);
}
}
Employee Component
class Employee extends React.Component {
state = {
achievements: null,
edit: null,
};
handleEdit = () => {
this.setState(() => ({ edit: true }));
};
handleView = () => {
this.setState(() => ({ achievements: this.props.emp.achievements }));
};
render() {
const { emp } = this.props;
if (this.state.edit) {
return <Navigate to={"/edit/" + emp.empId} push></Navigate>;
}
return (
<div className="card" style={{ width: 200 }}>
<img
className="card-img-top"
src={emp.image}
height="200"
alt="Card cap"
/>
<div className="card-body">
<h5 className="card-title text-center">{emp.empName}</h5>
<p className="card-text">
<span>Id: {emp.empId}</span>
<br />
<span>Age: {emp.age}</span>{" "}
{emp.age < 25 && <span className="text-info"> - Fresher</span>}
<br />
<span>Salary: {emp.salary}</span>
<br />
</p>
<p>
<i>{this.state.achievements}</i>
</p>
<button
type="button"
className="btn btn-primary"
onClick={this.handleEdit}
>
Edit
</button>
<button className="btn btn-success" onClick={this.handleView}>
View
</button>
</div>
</div>
);
}
}
EditEmployee Component
class EditEmployee extends React.Component {
render() {
return <h3>The selected ID is {this.props.match.params.id}</h3>;
}
}
export default EditEmployee;
Result should be like this
WHAT I WANT
but i am getting this error on click edit
enter image description here
WHAT I AM GETTING /ERROR IMAGE
You want to get the params via the useParams() hook:
const EditEmployee = () => {
const id = useParams()
return (
<h3>The selected ID is {id}</h3>;
)
}
export default EditEmployee;
Sorry that's a functional component. You can translate it to a class...
Did you just console.log(this.props) in your EditEmployee to see what you're getting?
You may need to wrap your class component in a function component as illustrated here.
Documentation here

How to pass data from one component to another in React [duplicate]

This question already has answers here:
How to pass data from child component to its parent in ReactJS?
(18 answers)
Closed 1 year ago.
I want to send variable data Store component to Related Component, I am getting Value const catId=this.state.data.category_id; now i am sending via props to Related Component but I dont know how to use props in Realted Component, i want to assign inside of didmonunt function for example
const url = "https://localhost:3030/api/v5/web/related";
const postBody = {
category_id: catId,
here my code:
Store.js
import React, { Component } from "react";
// import ReactDOM from "react-dom";
import { Helmet } from "react-helmet";
import { Grid, Image, Icon, Segment, Card } from "semantic-ui-react";
import TopMenuStrip from "../ComponentList/TopMenuStrip";
import LogoSection from "../ComponentList/LogoSection";
import { withRouter } from "react-router-dom";
import OfferList from "./OfferList";
import Related from "./Related";
import CopyRight from "../ComponentList/CopyRight";
import Footer from "../ComponentList/Footer";
import CustomerReview from "./CustomerReview";
class Store extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoading: true,
};
}
async componentDidMount() {
console.log(this.props.match.params.id);
let url_id = this.props.match.params.id;
const url = "https://localhost:3030/api/v5/web/details";
const postBody = {
store_id: url_id,
offer_type: "",
};
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(postBody),
};
fetch(url, requestOptions)
.then((response) => response.json())
.then((json) => {
this.setState({ data: json });
//console.log(json);
// console.log(json);
})
.catch((error) => console.error(error))
.finally(() => {
this.setState({ isLoading: false });
});
}
render() {
var i;
// console.log(this.props)
if (!this.state.data.how_to) {
return null;
}
if (!this.state.data.store_terms) {
return null;
}
const catId=this.state.data.category_id;
const seokeyWord = this.state.data.seo_keywords;
const seoDescription = this.state.data.seo_description;
return (
<>
<Helmet>
<meta charSet="utf-8" />
<title>test</title>
<meta name="description" content={seokeyWord} />
<meta name="keywords" content={seoDescription} />
<meta name="google-site-verification" content="rtIRrUNRpgZ_lFtlfS8LJ0-8j_d569BXS9NOGa_nM6Y" />
</Helmet>
<TopMenuStrip />
<LogoSection />
<div>
<Grid className="storedetails">
<Grid.Column width={11}>
<div className="storeImage">
<div className="store-bg">
<h2>
<span>{this.state.data.store_name}</span>
<span className="store-right">
{
(i =
0 < this.state.data.rating ? (
<p>
<span className="rateNumber">
{this.state.data.rating}
</span>
<Icon id={i} className="star"></Icon>
</p>
) : (
<p className="no-reviews">No Reviews</p>
))
}
</span>
</h2>
<p className="">{this.state.data.summary} </p>
{/* <p><span className='rateNumber'>{this.state.data.rating}</span><Icon className='star'/><Icon className='star'/><Icon className='star'/><Icon className='star'/><Icon className='star'/><span className='totalRate'>39000</span></p> */}
<div
className="store-back"
Style={
"background: url(" + this.state.data.store_images + ")"
}
>
{/* <Image src= alt="" className='storeImage'/> */}
<Image
src={this.state.data.logo}
alt=""
className="storeImageLogo"
/>
</div>
</div>
<div className="related-section">
<OfferList />
</div>
</div>
</Grid.Column>
<Grid.Column className="four-1" width={5}>
<div className="storeAbout" centered>
<h3>About</h3>
<p
dangerouslySetInnerHTML={{
__html: this.state.data.description,
}}
></p>
</div>
<CustomerReview />
<Related CatID={catId}/>
</Grid.Column>
</Grid>
<Grid className="related-footer">
<Grid.Column width={16}>
</Grid.Column>
</Grid>
</div>
<Footer />
<CopyRight />
</>
);
}
}
export default withRouter(Store);
Related.js
import React, { Component } from 'react';
// import ReactDOM from "react-dom";
import { Grid, Icon, Segment, Card } from "semantic-ui-react";
import { Link } from "react-router-dom";
import { LazyLoadImage } from 'react-lazy-load-image-component';
class Related extends Component{
constructor(props) {
super(props);
this.state = {
data: [],
visible: 6,
isLoading: true,
dataLoaded: false,
rec:CatID,
};
this.loadMore = this.loadMore.bind(this);
}
loadMore() {
this.setState({
visible: this.state.visible + 2,
dataLoaded: this.state.visible >= this.state.data.length
});
}
async componentDidMount() {
// console.log(this.props.match.params.id);
const url = "https://localhost:3030/api/v5/web/related";
const postBody = {
category_id: CatID,
offer_type: "",
};
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(postBody),
};
fetch(url, requestOptions)
.then((response) => response.json())
.then((json) => {
this.setState({ data: json.stores });
// console.log(json);
// console.log(json);
})
.catch((error) => console.error(error))
.finally(() => {
this.setState({ isLoading: false });
});
}
render()
{
return (
<>
<div className="related-stores" centered>
<h3 className="realted-heading">Related Stores </h3>
<Grid className="">
{this.state.data.slice(0, this.state.visible).map((x, i) => {
return (
<Grid.Column columns={8} key={i}>
<Segment>
<Card>
<Link to={x.store_url}>
<div className="image">
<LazyLoadImage src={x.image} wrapped ui={false} className="img related"/>
</div>
</Link>
<LazyLoadImage src={x.logo} alt="" className="collection-logo" />
<Card.Content>
<Link to={x.store_url}>
{" "}
<Card.Header>{x.name}</Card.Header>
</Link>
<Card.Description>{x.store_summary}</Card.Description>
</Card.Content>
<Card.Content extra>
<p className="rewards">
<span>
<LazyLoadImage src="/images/rewards.png" alt="" className="img"/>
</span>
Cash rewards upto <span>AED {x.cashback}</span>
</p>
<p className="location">
<span>
<LazyLoadImage src="/images/location.png" alt="" className="img"/>
</span>
<span className="store-location" key="index">{x.store_branches}</span>
{/* {x.store_branches.map((locations, index) => (
<span className="store-location" key="index">
{index === 0 ? (
<span>{locations.store_location}</span>
) : index >= 1 ? (
<span>
, {locations.store_location}
</span>
) : null}
</span>
))} */}
</p>
</Card.Content>
</Card>
</Segment>
</Grid.Column>
);
})}
</Grid>
{
!this.state.dataLoaded && (
<Grid>
<Grid.Column width={16}>
<p className="related-load">
<span
onClick={this.loadMore}
className="btn btn-primary load-more"
Style="cursor:pointer;">
Loading Store
</span>
</p>
</Grid.Column>
</Grid>
)
}
</div>
</>
)
}
}
export default Related;
In the Store component, pass storeCategory as props to the Related component.
In the Related component, access categoryId from the props and set it as category_id in postBody.
class Related extends Component {
...
async componentDidMount() {
...
const postBody = {
category_id: this.props.categoryId,
...
}
...
}
class Store extends Component {
...
render() {
...
return(
...
<Related categoryId={storeCategory} />
...
);
}
}
Explanation:
Related is a child of Store (parent).
To pass data from a parent component to a child component, use props.
Set an attribute on , e.g. categoryId, and assign the data (storeCategory) to it:
<Related categoryId={storeCategory} />
Store will then receive categoryId among its props. In Store, you can thus access categoryId from the props:
this.props.categoryId
Further reading:
Components and props

add item to cart page with react ( only with this.state )

its a long explaition it will be easier if you see screenshots first!
i'm trying to make a website, which inside 3 divs, with 3 different backgrounds. under those 3 divs there are 3 rows of buttons with different colors
you click on a color, it changes the background of one of the main divs. it will be more clear with the image/codesandbox i give here
each click you make changes "this.state.color1" or "color2" to the color you clicked on.
what i wanna do is add a "saved selection page", which you can see color combinations you saved by clicking another buttons called "save".
each click on this save button should return the 3 current colors, send them over to "Saved" page, and they should stay there and never change.
and if you click the save button again, it keeps the previous selections, and add the new one under the previous one.
kinda the same way we click on "add to cart" on websites.
i tried over a week to figure it out by many different ways, like putting a new state called "savedColors" and stuff like that but i didnt figure it out.
here it is on codesandbox
main page:
import React from "react";
import "./main.style.css";
import Buttons1 from "../buttons/buttons1-component";
import Buttons2 from "../buttons/buttons2-component";
import Buttons3 from "../buttons/buttons3-component";
import Saved from "../saved/saved.component";
class Main extends React.Component {
constructor(props) {
super(props);
this.changeColor1 = this.changeColor1.bind(this);
this.changeColor2 = this.changeColor2.bind(this);
this.changeColor3 = this.changeColor3.bind(this);
this.changeToSavedPage = this.changeToSavedPage.bind(this);
this.goToMainPage = this.goToMainPage.bind(this);
this.saveScheme = this.saveScheme.bind(this);
this.state = {
color1: "#fff285",
color2: "#38306b",
color3: "#c4cc90",
page: "main"
};
}
changeToSavedPage() {
this.setState({ page: "saved" });
}
goToMainPage() {
this.setState({ page: "main" });
}
changeColor1(id) {
this.setState({
color1: id
});
}
changeColor2(id) {
this.setState({
color2: id
});
}
changeColor3(id) {
this.setState({
color3: id
});
}
saveScheme() {
alert("see notes in the function");
// hey guys thanks for looking at my code,
// so what im trying to, is to get all 3
// current colors thats been seleceted.
// pass them over to "saved page", and show them there,
// each time you click "save selection" button,
// it will show the NEW CURRENT selection in the saved page.
// but will NOT delete the previos selection, just add a new one
// kind a like Add To Cart button!
}
render() {
return (
<div className="container">
{this.state.page === "main" && (
<div>
<div className="main-colors">
<div
className="color"
style={{ backgroundColor: this.state.color1 }}
>
<h1>color 1</h1>
</div>
<div
className="color"
style={{ backgroundColor: this.state.color2 }}
>
<h1>color 2</h1>
</div>
<div
className="color"
style={{ backgroundColor: this.state.color3 }}
>
<h1>color 3</h1>
</div>
</div>
<div className="buttons-container">
<p>change color 1:</p>
<Buttons1 changeColor1={this.changeColor1} />
<p>change color 2:</p>
<Buttons2 changeColor2={this.changeColor2} />
<p>change color 3:</p>
<Buttons3 changeColor3={this.changeColor3} />
</div>
<div className="btns">
<button onClick={() => this.saveScheme()}>save selection</button>
<button onClick={() => this.changeToSavedPage()}>
go to saved page
</button>
</div>
</div>
)}
{/* saved page */}
{this.state.page === "saved" && (
<Saved goToMainPage={this.goToMainPage} />
)}
</div>
);
}
}
export default Main;
saved page:
import React from "react";
import "./saved.styles.scss";
import SavedPiece from "../saved-piece/saved-piece.component";
class Saved extends React.Component {
constructor(props){
super(props);
// this.num = this.props.isClicked;
this.state = {
}
}
render(){
return(
<div className="saved-container">
<h1>saved</h1>
{/*
here we should see the saved combinations....
*/}
</div>
)
}
}
export default Saved;
import React from "react";
const Saved = (props) => {
const {saved} = props
return (
<>
<div className="saved-container">
<h1>saved matches page</h1>
{
saved && saved.map((obj, i) => {
return <div className="main-colors" key = {i}>
<div
className="color"
style={{ backgroundColor: obj.color1 }}
>
<h1>color 1</h1>
</div>
<div
className="color"
style={{ backgroundColor: obj.color2 }}
>
<h1>color 2</h1>
</div>
<div
className="color"
style={{ backgroundColor: obj.color3 }}
>
<h1>color 3</h1>
</div>
</div>
})
}
<button onClick={() =>
props.goToMainPage()}>
back to main page
</button>
</div>
</>
)
}
export default Saved;
import React from "react";
import React from "react";
import "./main.style.css";
import Buttons1 from "../buttons/buttons1-component";
import Buttons2 from "../buttons/buttons2-component";
import Buttons3 from "../buttons/buttons3-component";
import Saved from "../saved/saved.component";
class Main extends React.Component {
constructor(props) {
super(props);
this.changeColor1 = this.changeColor1.bind(this);
this.changeColor2 = this.changeColor2.bind(this);
this.changeColor3 = this.changeColor3.bind(this);
this.changeToSavedPage = this.changeToSavedPage.bind(this);
this.goToMainPage = this.goToMainPage.bind(this);
this.saveScheme = this.saveScheme.bind(this);
this.state = {
color1: "#fff285",
color2: "#38306b",
color3: "#c4cc90",
page: "main",
saved: []
};
}
changeToSavedPage() {
this.setState({ page: "saved" });
}
goToMainPage() {
this.setState({ page: "main" });
}
changeColor1(id) {
this.setState({
color1: id
});
}
changeColor2(id) {
this.setState({
color2: id
});
}
changeColor3(id) {
this.setState({
color3: id
});
}
saveScheme() {
const { color1, color2, color3 } = this.state
this.setState({
saved: [
...this.state.saved,
{
color1,
color2,
color3
}
]
})
}
render() {
return (
<div className="container">
{this.state.page === "main" && (
<div>
<div className="main-colors">
<div
className="color"
style={{ backgroundColor: this.state.color1 }}
>
<h1>color 1</h1>
</div>
<div
className="color"
style={{ backgroundColor: this.state.color2 }}
>
<h1>color 2</h1>
</div>
<div
className="color"
style={{ backgroundColor: this.state.color3 }}
>
<h1>color 3</h1>
</div>
</div>
<div className="buttons-container">
<p>change color 1:</p>
<Buttons1 changeColor1={this.changeColor1} />
<p>change color 2:</p>
<Buttons2 changeColor2={this.changeColor2} />
<p>change color 3:</p>
<Buttons3 changeColor3={this.changeColor3} />
</div>
<div className="btns">
<button onClick={() => {
console.log(this.state.saved)
this.saveScheme()
}}>save selection</button>
<button onClick={() => {
this.changeToSavedPage()
}}>
go to saved page
</button>
</div>
</div>
)}
{/* saved page */}
{this.state.page === "saved" && (
<Saved
goToMainPage={this.goToMainPage}
saved = {this.state.saved}
/>
)}
</div>
);
}
}
export default Main;
We added an array to our main component state called saved, then when a user saves, we update the state by adding the values of the current selections, and using the spread operator to maintain the old selected values and not mutate them. Then we pass our saved array as a prop to our child component, and we map it.

Trying to build a champion Select Screen using React

I am still learning React and i am trying to build a champion(which in this case is name hashiras) selection screen. I am taking data from an array in the react app and i am trying to select one and display a toggle that this champion(hashira) has been chosen. However i am a bit confused on state and have been struggling with trying to display the name of the chosen hashira among the 4 i have in the array. Also my images are not appearing.
What i am trying to obtain is when i click on choose, the top text will display the name of the chosen hashira.
My final goal is to toggle a form to enter the own username after the hashira is chosen which i will try by myself.
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {render} from 'react-dom';
import {rengokuimg} from './Images/rengokuIMG.png';
import {sanemiimg} from './Images/rengokuIMG.png';
import {shinobuimg} from './Images/rengokuIMG.png';
import {giyuuimg} from './Images/rengokuIMG.png';
let hashiraList=[
{"name":"Rengoku Kyojiro", "description":"Flame Hashira", "age":20, "element":"Flame","imgsource":rengokuimg,"Choice":"Rengoku Chosen"},
{"name":"Giyuu Tomioka", "description":"Water Hashira", "age":21, "element":"Water","imgsource":giyuuimg,"Choice":"Giyuu Chosen"},
{"name":"Sanemi Shinazugawa", "description":"Wind Hashira", "age":22, "element":"Wind","imgsource":sanemiimg,"Choice":"Sanemi Chosen"},
{"name":"Shinobu Kocho", "description":"Insect Hashira", "age":22, "element":"Poison","imgsource":shinobuimg,"Choice":"Shinobu Chosen"}
]
const Hashira =({name, description,element,age,imgsource,Choice}) => {
return(
<section>
<h2>Name: {name}</h2>
<p>Description: {description}</p>
<div><img src={imgsource}/></div>
<p>Element: {element}</p>
{/* <button onClick={()=>console.log({name},console.log({age}))}>Choose </button> */}
<button onClick={()=>console.log({Choice})}>Select the {element} hashira</button>
</section>
)
}
const NotSelected=()=>
<div>
<h1>No Hashira Selected.</h1>
</div>
const Selected=()=>
<div><h2>You have chosen hashira</h2></div>
class HashiraSelect extends React.Component{
state = {
Chosen : false,
formdisplay :false,
selected:false
}
toggleOpenClosed({name}){
this.setState({
Chosen: !this.state.Chosen,
selected: {name}
})
}
render(){
console.log(this.state)
const {hashiras} = this.props
return(
<div>
{this.state.selected ? <Selected/> : <NotSelected/>}
{/* <h1> Have you chosen a Hashira : {this.state.Chosen ? 'Chosen' : 'Not yet'}</h1> */}
{hashiras.map(
(hashira, idx) =>
<Hashira
key={idx}
name={hashira.name}
description={hashira.description}
age={hashira.age}
element={hashira.element}
Choice={hashira.Choice}
imgsource={hashira.imgsource}
Selected={this.state.Selected}
/>
)}
{/* <button onClick ={this.toggleOpenClosed}>Choose</button> */}
</div>
)
}
}
{/* <Hashira name="Sanemi" description="The wind Hashira." element="Wind"/> */}
render(
<HashiraSelect hashiras={hashiraList}/>,
document.getElementById('root')
)
Thank you once more for the help.
Sandbox link
An onChange handler should be created in HashiraSelect. Pass this handler as a prop to Hashira component. When button is click, pass the selected hashira value to the onChange handler and set it in state.
Check out the WORKING DEMO
const Hashira = ({
name,
description,
element,
age,
imgsource,
Choice,
onChange
}) => {
return (
<div>
<section>
<h2>Name: {name}</h2>
<p>Description: {description}</p>
{/* <div><img src={rengokuimg}/></div> */}
<p>Element: {element}</p>
{/* <button onClick={()=>console.log({name},console.log({age}))}>Choose </button> */}
<button onClick={() => onChange(Choice)}>
Select the {element} hashira
</button>
</section>
</div>
);
};
class HashiraSelect extends React.Component {
state = {
Chosen: false,
formdisplay: false,
choice: null
};
toggleOpenClosed() {
this.setState({
Chosen: !this.state.Chosen
});
}
onChange = choice => {
this.setState({ choice, Chosen: true });
};
render() {
console.log(this.state);
const { hashiras } = this.props;
const { choice } = this.state;
return (
<div>
<h1>
Have you chosen a Hashira : {this.state.Chosen ? "Chosen" : "Not yet"}
</h1>
{choice && <h2>{`Hashira Chosen:${choice}`}</h2>}
{hashiras.map((hashira, idx) => (
<Hashira
key={idx}
name={hashira.name}
description={hashira.description}
age={hashira.age}
element={hashira.element}
Choice={hashira.Choice}
onChange={this.onChange}
// imgsource={hashira.imgsource}
Selected={this.state.Selected}
// Choice={this.state.Chosen}
/>
))}
{/* <button onClick ={this.toggleOpenClosed}>Choose</button> */}
</div>
);
}
}
// <Hashira name="Sanemi" description="The wind Hashira." element="Wind"/>
render(
<HashiraSelect hashiras={hashiraList} />,
document.getElementById("root")
);

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.

Resources