Why can't i use two state in a reactjs class component? - reactjs

class Pumashoe01 extends Component {
state = {
products: [
{
"_id": "1",
"title": "Puma Shoes",
"src": [
"https:h_1440,q_90,w_1080/v1/assets/images/11335328/2020/5/21/63e51b29-0e3d-4e0e-a9c5-6f2c34720ef41590079916986PumaMenNavyBlueGreenLevelIDPSneakers4.jpg",
],
"description": "Men Black Flex Fire V1 IDP Sneakers",
"content": " A pair of round-toe black sneakers, has regular styling, lace-up detail Textile upper Cushioned footbed Textured and patterned outsole.",
"price": 2364,
}
],
index: 0
};
myRef = React.createRef();
handleTab = index => {
this.setState ({ index: index })
const images = this.myRef.current.children;
for (let i = 0; i < images.length; i++) {
images[i].className = images[i].className.replace("active", "");
}
images[index].className = "active";
};
componentDidMount() {
const { index } = this.state;
this.myRef.current.children[index].className = "active";
}
constructor(props) {
super(props);
this.state = {count: 0 }
this.increment = this.increment.bind(this)
}
increment() {
this.setState( {count:this.state.count + 1},
() => {
console.log('callback value', this.state.count)})
console.log(this.state.count)}
render() {
const { products, index } = this.state;
return (
{
products.map(item => (
<div className="details" key={item._id}>
<div className="big-img">
<img src={item.src[index]} alt="" />
</div>
<div className="box">
<div className="row">
<h2>{item.title}</h2>
<span>₹{item.price}</span>
</div>
<p>{item.description}</p>
<p>{item.content}</p>
<DetailsThumb images={item.src} tab={this.handleTab} myRef={this.myRef} />
<p>You clicked {this.state.count } times</p>
<button onClick={this.increment} className="cart">Add to cart</button>
<button className="Buynow">Buy Now</button>
</div>
</div>
))
}
</div>
);
};
}
Why can't i use two state with same spelling in a class component. if i set same state spelling in two times it displays map is undefined error. If i change the state spelling in the constructor its function will not work. help me with this why dont i use two different functionality state in a single class component on reactjs.

Related

React .map and setState weird behavior

The following is a simplified version of the part of the entire code.
The entire app is basically supposed to be a note taking app built on React, and currently I'm stuck on its respective note's editing function.
So the following script part basically is supported to do:
Render an array of <Card /> components based on the App this.state.notes array
By clicking the Edit note button it sets the App this.state.noteEditingId state
(so the React instance can know later which generated Card is currnetly being edited by the id)
By clicking the Save Edit button it tries to update the App this.state.notes array with the submitted edit text.
(see, I used a lot of filters to try to achieve this, since I don't have a good idea to how to more nicely achieve this. I believe there should be a nicer way)
But the result is not what I expect.
(While it supposed to achieve updating the expected Card component's notes array with the new note instance's new note "note" text,
it updates the notes array with the different notes's note instance's "note" text. I cannot explain this clearly, since this is an idk-what-is-wrong type of issue to me. )
const Card = (props) => {
const [noteEditing, setNoteEditing] = useState(false);
return (
<div {...props}>
<div>
<div>
<span>
<button onClick={() => {
noteEditing ? setNoteEditing(false) : setNoteEditing(true);
props.thisApp.setState({ noteEditingId: props.config.id })
}}>Edit note</button>
</span>
{noteEditing
?
<div>
<textarea className='__text' />
<button onClick={() => {
let note = document.querySelector('.__text').value
let current_note = props.thisApp.state.notes.filter(a => a.id == props.config.id)[0]
let notesAfterRemoved = props.thisApp.state.notes.filter(a => a.id !== props.config.id)
if (props.thisApp.state.noteEditingId == props.config.id)
{
props.thisApp.setState({
notes: [...notesAfterRemoved, { ...current_note, note: note }]
})
}
}}>
Save Edit
</button>
</div>
: ""
}
</div>
</div>
</div>
)
}
class App extends React.Component {
constructor() {
super();
this.state = {
notes: [
{
note: "note1.",
id: nanoid(),
},
{
note: "note2.",
id: nanoid(),
},
{
note: "note3.",
id: nanoid(),
},
]
};
}
render() {
return (
<div>
<h2>
Notes ({this.state.notes.length})
</h2>
<div className='__main_cards'>
<div>
{this.state.notes.map((a, i) => {
return <Card key={i} className="__card" thisApp={this} config={
{
note: a.note,
}
} />
})}
</div>
</div>
</div>
)
}
}
So what can I do fix to make the part work properly? Thanks.
You should pass the current note also and get rid of the filter in card component:
this.state.notes.map((note, i) => {
return (
<Card
key={i}
className="__card"
thisApp={this}
currentNote={note}
/>
);
})
And then remove this:
let current_note = props.thisApp.state.notes.filter(a => a.id == props.config.id)[0]
And then rather than finding the notes without the edited note you can create a new array with edited data like this:
const x = props.thisApp.state.notes.map((n) => {
if (n.id === props.currentNote.id) {
return {...n, note}
}
return n
})
And get rid off this line:
let notesAfterRemoved = props.thisApp.state.notes.filter(a => a.id !== props.config.id)
Also, change this
noteEditing ? setNoteEditing(false) : setNoteEditing(true);
To:
setNoteEditing(prev => !prev)
As it is much cleaner way to toggle the value.
And I believe, there is no need to check that if the noteEditingId is equal to current active note id.
So you can get rid off that also (Correct me if I am wrong!)
Here's the full code:
const Card = (props) => {
const [noteEditing, setNoteEditing] = useState(false);
const textareaRef = useRef();
return (
<div {...props}>
<div>
<div>
<span>
<button
onClick={() => {
setNoteEditing((prev) => !prev); // Cleaner way to toggle
}}
>
Edit note
</button>
</span>
{noteEditing && (
<div>
<textarea className="__text" ref={textareaRef} />
<button
onClick={() => {
let note = textareaRef.current.value;
const x = props.thisApp.state.notes.map(
(n) => {
if (n.id === props.currentNote.id) {
return { ...n, note };
}
return n;
}
);
props.thisApp.setState({
notes: x,
});
}}
>
Save Edit
</button>
</div>
)}
</div>
</div>
</div>
);
};
class App extends React.Component {
constructor() {
super();
this.state = {
notes: [
{
note: "note1.",
id: nanoid(),
},
{
note: "note2.",
id: nanoid(),
},
{
note: "note3.",
id: nanoid(),
},
],
};
}
render() {
return (
<div>
<h2>Notes ({this.state.notes.length})</h2>
<div className="__main_cards">
<div>
{this.state.notes.map((note, i) => {
return (
<Card
key={i}
className="__card"
thisApp={this}
currentNote={note}
/>
);
})}
</div>
</div>
</div>
);
}
}
}
);
props.thisApp.setState({
notes: x,
});
}
}}
>
Save Edit
</button>
</div>
)}
</div>
</div>
</div>
);
};
class App extends React.Component {
constructor() {
super();
this.state = {
notes: [
{
note: "note1.",
id: nanoid(),
},
{
note: "note2.",
id: nanoid(),
},
{
note: "note3.",
id: nanoid(),
},
],
};
}
render() {
return (
<div>
<h2>Notes ({this.state.notes.length})</h2>
<div className="__main_cards">
<div>
{this.state.notes.map((note, i) => {
return (
<Card
key={i}
className="__card"
thisApp={this}
currentNote={note}
/>
);
})}
</div>
</div>
</div>
);
}
}
I hope this is helpful for you!

React - hide and show individual element

I am trying to toggle show/hide on individual elements. I thought the key={item._id} will be enough to toggle the individual items, but when clicking the toggle button every element is revealed.
This is my attempt: https://codesandbox.io/s/thirsty-torvalds-xnkt1?file=/src/App.js
I would really appreciate any suggestion. Thanks
You can split that in to a component and maintain the toggle state there which is "show". I have updated my answer in your CodeSandbox link click here
//App.js
import React from "react";
import "./styles.css";
import "./styles/tailwind-pre-build.css";
import Book from './Book';
const list = [
{
_id: "1",
book: "Witcher 1",
author: "Andrzej Sapkowski"
},
{
_id: "2",
book: "Witcher 2",
author: "Andrzej "
},
{
_id: "3",
book: "Witcher 3",
author: "Andrzej Sapkowski"
}
];
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
const listItem = list.map(item => {
return (
<div className="flex p-4">
<div key={item._id}>{item.book}</div>
{/* <button
onClick={this.handleShow}
className="px-4 font-bold bg-gray-300"
>
toggle
</button>
<div>{this.state.show ? <div>{item.author}</div> : null}</div> */}
<Book item={item}/>
</div>
);
});
return (
<div className="text-2xl">
<div>List of books</div>
{listItem}
</div>
);
}
}
// Book.js
import React from "react";
import "./styles.css";
import "./styles/tailwind-pre-build.css";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
show: false
};
}
handleShow = () => {
this.setState({
show: !this.state.show
});
};
render(){
return(
<>
<button
onClick={this.handleShow}
className="px-4 font-bold bg-gray-300"
>
toggle
</button>
<div>{this.state.show ? <div>{this.props.item.author}</div> : null}</div>
</>
)
}
}
You can just change the show variable in the state to take the index of the element like this :
handleShow = (idx) => {
this.setState({
show: idx
});
};
and your constructor becomes :
constructor(props) {
super(props);
this.state = {
show: -1
};
}
And your list becomes :
const listItem = list.map(item => {
return (
<div className="flex p-4">
<div key={item._id}>{item.book}</div>
<button
onClick={() => this.handleShow(item._id)}
className="px-4 font-bold bg-gray-300"
>
toggle
</button>
<div>{this.state.show === item._id ? <div>{item.author}</div> : null}</div>
</div>
);
});
I think that will do it !
you can save list into state and add a isToggle prop for every object, and handleShow change the isToggle state of clicked target item, code will look like this:
import React from "react";
import "./styles.css";
import "./styles/tailwind-pre-build.css";
const list = [
{
_id: "1",
book: "Witcher 1",
author: "Andrzej Sapkowski",
isToggle: false,
},
{
_id: "2",
book: "Witcher 2",
author: "Andrzej ",
isToggle: false,
},
{
_id: "3",
book: "Witcher 3",
author: "Andrzej Sapkowski",
isToggle: false,
}
];
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
list
};
}
handleShow = (e) => {
const index = this.state.list.findIndex(t => t._id === e.currentTarget.dataset.id);
const list = [...this.state.list];
list[index].isToggle = !list[index].isToggle;
this.setState({ list });
};
render() {
const listItem = list.map(item => {
return (
<div className="flex p-4">
<div key={item._id}>{item.book}</div>
<button
data-id={item._id}
onClick={this.handleShow}
className="px-4 font-bold bg-gray-300"
>
toggle
</button>
<div>{item.isToggle ? <div>{item.author}</div> : null}</div>
</div>
);
});
return (
<div className="text-2xl">
<div>List of books</div>
{listItem}
</div>
);
}
}
Updated and working code here
Changes in code:
In list data structure
const list = [
{
_id: "1",
book: "Witcher 1",
author: "Andrzej Sapkowski",
show: false // add additional key to toggel
},
...
];
In constructor
constructor(props) {
super(props);
this.state = {
books: list // defined books state
};
}
in handleShow
handleShow = event => {
console.log(event.target.id);
const books = this.state.books;
const bookId = event.target.id; // get selected book id
for (let index = 0; index < books.length; index++) {
if (bookId === books[index]._id) {
books[index].show = !books[index].show; //toggle the element
}
}
this.setState({ books });
};
In render:
render() {
const listItem = this.state.books.map(item => { //use state to iterate
return (
<div key={item._id} className="flex p-4">
<div key={item._id}>{item.book}</div>
<button
id={item._id} //add key to get the selected book
onClick={this.handleShow}
className="px-4 font-bold bg-gray-300"
>
toggle
</button>
<div>{item.show ? <div>{item.author}</div> : null}</div> // use key `show` to toggle it
</div>
);
});
if you want to show them toggled individually you can do like this:
import React from "react";
import "./styles.css";
import "./styles/tailwind-pre-build.css";
const list = [
{
_id: "1",
book: "Witcher 1",
author: "Andrzej Sapkowski"
},
{
_id: "2",
book: "Witcher 2",
author: "Andrzej "
},
{
_id: "3",
book: "Witcher 3",
author: "Andrzej Sapkowski"
}
];
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: false,
showId: null,
};
}
handleShow = (id) => {
this.setState({
showId: id
});
};
render() {
const listItem = list.map(item => {
return (
<div className="flex p-4">
<div key={item._id}>{item.book}</div>
<button
onClick={() => this.handleShow(item._id)}
className="px-4 font-bold bg-gray-300"
>
toggle
</button>
<div>{this.state.showId === item._id ? <div>{item.author}</div> : null}</div>
</div>
);
});
return (
<div className="text-2xl">
<div>List of books</div>
{listItem}
</div>
);
}
}

How to loop this state in React?

I try to loop my state with a .map but it didn't work (I don't see my log in the console) ...
{this.state["cards"].map(card => console.log(card.title))}
Please tell me what's the error...
I have my state here (which a see the log in my console) :
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
cards: []
};
fs.readdir(testFolder, (err, files) => {
files.forEach(file => {
this.state.cards.push({
title: "Test1",
pic:
"https://seeklogo.com/images/C/confluence-logo-D9B07137C2-seeklogo.com.png",
content: "Content",
link: "#"
});
});
});
console.log(this.state); // I see this one
}
Here is my console log :
Object {cards: Array[2]}
cards: Array[2]
0: Object
title: "Test1"
pic: "https://seeklogo.com/images/C/confluence-logo-D9B07137C2-seeklogo.com.png"
content: "Content"
link: "#"
1: Object
title: "Test1"
pic: "https://seeklogo.com/images/C/confluence-logo-D9B07137C2-seeklogo.com.png"
content: "Content"
link: "#"
EDIT : Here is my render :
render() {
return (
<div>
<div className="container-fluid text-center">
<h2 className="h2_title">Hi.</h2>
</div>
<div className="main">
<ul className="cards">
{this.state["cards"].map(cards => (
<Card
key={cards.title}
link={cards.link}
title={cards.title}
pic={cards.pic}
/>
))}
</ul>
</div>
</div>
);
}
He doesn't display my cards and if I replace it by console.log it didn't display it in my console
Thanks for helping me !
You need to make your operations inside the componentDidMount and update the state like this:
import React, {Component} from "react";
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
cards: [],
loading: true
};
}
componentDidMount() {
let cards = [];
fs.readdir(testFolder, (err, files) => {
files.forEach(file => {
cards.push({
title: "Test1",
pic:
"https://seeklogo.com/images/C/confluence-logo-D9B07137C2-seeklogo.com.png",
content: "Content",
link: "#"
});
});
this.setState({
cards,
loading: false
})
});
}
render() {
const {cards, loading} = this.state;
if (loading) {
return (
<div>Getting Files, please wait...</div>
)
}
if (cards.length === 0) {
return (
<div>No files found</div>
)
}
return (
<div>
<div className="container-fluid text-center">
<h2 className="h2_title">Hi.</h2>
</div>
<div className="main">
<ul className="cards">
{cards.map(card => (
<Card
key={card.title}
link={card.link}
title={card.title}
pic={card.pic}
/>
))}
</ul>
</div>
</div>
);
}
}
You shouldn't directly change the state like this this.state.cards.push

Toggle class only on one element, react js

I`m changing class after clicking and it works.
The problem is that, classes change simultaneously in both elements and not in each one separately. Maybe someone could look what I'm doing wrong. Any help will be useful.
import React, { Component } from "react";
class PageContentSupportFaq extends Component {
constructor(props) {
super(props);
this.state = {
isExpanded: false
};
}
handleToggle(e) {
this.setState({
isExpanded: !this.state.isExpanded
});
}
render() {
const { isExpanded } = this.state;
return (
<div className="section__support--faq section__full--gray position-relative">
<div className="container section__faq">
<p className="p--thin text-left">FAQ</p>
<h2 className="section__faq--title overflow-hidden pb-4">Title</h2>
<p className="mb-5">Subtitle</p>
<div className="faq__columns">
<div
onClick={e => this.handleToggle(e)}
className={isExpanded ? "active" : "dummy-class"}
>
<p className="mb-0">
<strong>First</strong>
</p>
</div>
<div
onClick={e => this.handleToggle(e)}
className={isExpanded ? "active" : "dummy-class"}
>
<p className="mb-0">
<strong>Second</strong>
</p>
</div>
</div>
</div>
</div>
);
}
}
export default PageContentSupportFaq;
Every element must have its seperate expanded value. So we need an array in state.
And here is the code:
import React, { Component } from "react";
class PageContentSupportFaq extends Component {
state = {
items: [
{ id: 1, name: "First", expanded: false },
{ id: 2, name: "Second", expanded: true },
{ id: 3, name: "Third", expanded: false }
]
};
handleToggle = id => {
const updatedItems = this.state.items.map(item => {
if (item.id === id) {
return {
...item,
expanded: !item.expanded
};
} else {
return item;
}
});
this.setState({
items: updatedItems
});
};
render() {
return this.state.items.map(el => (
<div
key={el.id}
onClick={() => this.handleToggle(el.id)}
className={el.expanded ? "active" : "dummy-class"}
>
<p className="mb-0">
<strong>{el.name}</strong>
<span> {el.expanded.toString()}</span>
</p>
</div>
));
}
}
export default PageContentSupportFaq;
You can get two state one state for first and another for a second and handle using two function like this
import React, { Component } from 'react';
class PageContentSupportFaq extends Component {
constructor(props) {
super(props)
this.state = {
isExpanded: false,
isExpanded2:false,
}
}
handleToggle(e){
this.setState({
isExpanded: !this.state.isExpanded
})
}
handleToggle2(e){
this.setState({
isExpanded2: !this.state.isExpanded2
})
}
render() {
const {isExpanded,isExpanded2} = this.state;
return (
<div className="section__support--faq section__full--gray position-relative">
<div className="container section__faq">
<p className="p--thin text-left">FAQ</p>
<h2 className="section__faq--title overflow-hidden pb-4">Title</h2>
<p className="mb-5">Subtitle</p>
<div className="faq__columns">
<div onClick={(e) => this.handleToggle(e)} className={isExpanded ? "active" : "dummy-class"}>
<p className="mb-0"><strong>First</strong></p>
</div>
<div onClick={(e) => this.handleToggle2(e)} className={isExpanded2 ? "active" : "dummy-class"}>
<p className="mb-0"><strong>Second</strong></p>
</div>
</div>
</div>
</div>
);
}
}
export default PageContentSupportFaq;
You'll need to track toggled classes in array, that way it will support arbitrary number of components:
// Save elements data into array for easier rendering
const elements = [{ id: 1, name: "First" }, { id: 2, name: "Second" }];
class PageContentSupportFaq extends Component {
constructor(props) {
super(props);
this.state = {
expanded: []
};
}
handleToggle(id) {
this.setState(state => {
if (state.isExpanded.includes(id)) {
return state.isExpanded.filter(elId => elId !== id);
}
return [...state.expanded, id];
});
}
render() {
return elements.map(el => (
<div
key={el.id}
onClick={() => this.handleToggle(el.id)}
className={this.isExpanded(el.id) ? "active" : "dummy-class"}
>
<p className="mb-0">
<strong>{el.name}</strong>
</p>
</div>
));
}
}

Trying to load multiple C3 charts in same react component

I am trying to map over an array and get a chart to appear alongside with each element, but it doesn't seem to work. This same code appeared once correctly, but no other time and I am not sure what I am missing.
I tried to change the id name to where it tags the chart and I did that by adding an index variable, but still not working
import React from 'react'
import c3 from '/c3.min.js'
class SearchedFood extends React.Component {
constructor(props) {
super(props)
this.state = {
}
this.graph = this.graph.bind(this)
}
graph(index) {
c3.generate({
bindto: '#class' + index,
data: {
columns: [
[1, 2, 3], [2, 3,4]
],
type: 'bar'
},
bar: {
width: {
ratio: 0.3
}
}
})}
render () {
return (
<div>
{this.props.foodResults.map((food, i) => {
return (
<div key={i}>
<label>{food.recipe.label}</label>
<img className="card-img-top" src={food.recipe.image} height="250" width="auto"></img>
<a href={food.recipe.url}>{food.recipe.source}</a>
<p>{food.recipe.dietLabels[0]}</p>
<div>
{food.recipe.ingredientLines.map((ingredient, i) => {
return (
<p key={i}>{ingredient}</p>
)
})}
</div>
<p>Calories {Math.floor(food.recipe.calories/food.recipe.yield)}</p>
<div id={`class${i}`}>{this.graph(i)}</div>
</div>
)
})}
</div>
)
}
}
export default SearchedFood
bindto: '#class' + index,/{this.graph...} isn't gonna work. React doesn't render directly/immediately to the DOM.
Looks like you can use elements with bindTo - your best bet is to use a ref
class SearchedFoodRow extends React.Component {
componentDidMount() {
c3.generate({
bindTo: this.element,
...
})
}
render() {
const { food } = this.props
return (
<div>
<label>{food.recipe.label}</label>
<img className="card-img-top" src={food.recipe.image} height="250" width="auto"></img>
<a href={food.recipe.url}>{food.recipe.source}</a>
<p>{food.recipe.dietLabels[0]}</p>
<div>
{food.recipe.ingredientLines.map((ingredient, i) => {
return (
<p key={i}>{ingredient}</p>
)
})}
</div>
<p>Calories {Math.floor(food.recipe.calories/food.recipe.yield)}</p>
<div ref={ element => this.element = element } />
</div>
)
}
}
and then
class SearchFood extends React.Component {
render() {
return (
<div>
{ this.props.foodResults.map((food, i) => <SearchedFoodRow key={i} food={food} />)}
</div>
)
}
}

Resources