How to use properties on onClick from on child and parent component? - reactjs

I'm now leaning react and having a lot of fun with, but also having my first issue than I cannot solved by myself.
I would like to use a child component to filter on his parent list. I mean I would like to use a child component to filter a list from his parent.. I hope my code would be more understandable than my explications...
Does anyone can explain how should I use onclick in both class and maybe optimize my code ? Thanks
product.jsx
class Items extends Component {
state = {
count: 0,
carbondioxide: [
{ key: 90, name: "All", value: "All" },
{ key: 91, name: "Still Water", value: "Still Water" },
{ key: 92, name: "Sparkling Water", value: "Sparkling Water" },
],
brands: [
{
key: 0,
name: "Fountain",
category: "Still Water",
},
{
key: 1,
name: "Kitchen",
category: "Sparkling Water",
},
{
key: 2,
name: "Shower",
category: "Still Water",
},
],
filterBrands: [],
};
componentDidMount() {
this.setState({
filterBrands: this.state.brands,
});
}
handleClick = (name) => {
let filterBrands = [];
if (name === "All") {
filterBrands = this.state.brands;
} else {
filterBrands = this.state.brands.filter(
(brands) => brands.category === name
);
}
this.setState({ filterBrands });
};
}
render() {
return (
<div className="ctg-flex">
<GroupButtonFilter
carbondioxide={this.state.carbondioxide}
onClick={this.handleClick.bind(this, name)} //filterBrands onclick
//onClick={() => {
// this.handleClick.bind(this, this.state.carbondioxide.name);
//}}
/>
{this.state.filterBrands.map((id, brands) => (
<Item
key={id.key}
dataImg={id.imageUrl}
dataText={id.name}
dataPrice={id.price}
/>
))}
</div>
);
}
}
export default Items;
and GroupeButtonFilter.jsx
class GroupButtonFilter extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="group-button">
{this.props.carbondioxide.map(({ name, value }) => (
<Button
key={this.props.carbondioxide.key}
value={this.props.carbondioxide.value}
// onClick={this.props.handleClick.bind(this, this.name)} //filterBrands onclick
onClick={this.props.onClicktest} //filterBrands onclick
>
{name}
</Button>
))}
</div>
);
}
}

There are couple of issue on your code.
You are binding your:
onClick={() => {
this.handleClick.bind(this, this.state.carbondioxide.name);
}}
and your handleClick is using arrow function so you can remove bind from this i.e
onClick={() => {
this.handleClick( this.state.carbondioxide.name);
}}
Still its not correct. which is our second point
In GroupButtonFilter you are expecting onClicktest and while passing in GroupButtonFilter you are sending onClick this can reduce to this:
<Button
key={this.props.carbondioxide.key}
value={this.props.carbondioxide.value}
// onClick={this.props.handleClick.bind(this, this.name)} //filterBrands onclick
onClick={this.props.onClicktest} //filterBrands onclick
>
{name}
</Button>
You need to pass the value that button is clicked but in onClick you are sending carbondiooxide name. You need to do this. Inside GroupFilter:
<button
key={this.props.carbondioxide.key}
// onClick={this.props.handleClick.bind(this, this.name)} //filterBrands onclick
onClick={() => this.props.onClicktest(name)} //filterBrands onclick
>
{name}
</button>
Here is complete code:
import React, { Component } from "react";
import "./styles.css";
class GroupButtonFilter extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="group-button">
{this.props.carbondioxide.map(({ name, value }) => (
<button
key={this.props.carbondioxide.key}
// onClick={this.props.handleClick.bind(this, this.name)} //filterBrands onclick
onClick={() => this.props.onClicktest(name)} //filterBrands onclick
>
{name}
</button>
))}
</div>
);
}
}
class Items extends Component {
state = {
count: 0,
carbondioxide: [
{ key: 90, name: "All", value: "All" },
{ key: 91, name: "Still Water", value: "Still Water" },
{ key: 92, name: "Sparkling Water", value: "Sparkling Water" }
],
brands: [
{
key: 0,
name: "Fountain",
category: "Still Water"
},
{
key: 1,
name: "Kitchen",
category: "Sparkling Water"
},
{
key: 2,
name: "Shower",
category: "Still Water"
}
],
filterBrands: []
};
componentDidMount() {
this.setState({
filterBrands: this.state.brands
});
}
handleClick = (name) => {
console.log(name);
let filterBrands = [];
if (name === "All") {
filterBrands = this.state.brands;
} else {
filterBrands = this.state.brands.filter(
(brands) => brands.category === name
);
}
this.setState({ filterBrands });
};
render() {
return (
<div className="ctg-flex">
<GroupButtonFilter
carbondioxide={this.state.carbondioxide}
onClicktest={this.handleClick}
// onClicktest={this.handleClick.bind(
// this.state.carbondioxide.name,
// this.name
// )}
/>
{this.state.filterBrands.map((id, brands) => (
<div key={id.name}>{id.name}</div>
))}
</div>
);
}
}
export default function App() {
return (
<div className="App">
<Items />
</div>
);
}
here is the demo:https://codesandbox.io/s/affectionate-fire-zcqc1?file=/src/App.js:0-2168

write a function in your parent component
handleClick = () => {
/// do your filtering
}
pass this handleClick to your child component
<GroupButtonFilter handleClickInParent ={this.handleClick} />
....
</GroupButtonFilter>
then inside your child component
<Button. onClick={() => this.props.handleClick())}>
</Button>

Related

change text of a specific button when clicked in React

I want to change the text of a specific button when I click on that button in React. But the issue is when I click the button the title will change for all buttons!
class Results extends Component {
constructor() {
super();
this.state = {
title: "Add to watchlist"
}
}
changeTitle = () => {
this.setState({ title: "Added" });
};
render() {
return (
<div className='results'>
{
this.props.movies.map((movie, index) => {
return (
<div className='card wrapper' key={index}>
<button className='watchListButton' onClick={this.changeTitle}>{this.state.title}</button>
</div>
)
})
}
</div>
)
}
}
You would need to come up with a mechanism to track added/removed titles per movie. For that, you would have to set your state properly. Example:
this.state = {
movies: [
{id: 1, title: 'Casino', added: false},
{id: 2, title: 'Goodfellas', added: false}
]
This way you can track what's added and what's not by passing the movie id to the function that marks movies as Added/Removed. I have put together this basic Sandbox for you to get you going in the right direction:
https://codesandbox.io/s/keen-moon-9dct9?file=/src/App.js
And here is the code for future reference:
import React, { Component } from "react";
import "./styles.css";
class App extends Component {
constructor() {
super();
this.state = {
movies: [
{ id: 1, title: "Casino", added: false },
{ id: 2, title: "Goodfellas", added: false }
]
};
}
changeTitle = (id) => {
this.setState(
this.state.movies.map((item) => {
if (item.id === id) item.added = !item.added;
return item;
})
);
};
render() {
const { movies } = this.state;
return (
<div className="results">
{movies.map((movie, index) => {
return (
<div className="card wrapper" key={index}>
{movie.title}
<button
className="watchListButton"
onClick={() => this.changeTitle(movie.id)}
>
{movie.added ? "Remove" : "Add"}
</button>
</div>
);
})}
</div>
);
}
}
export default App;

Reactjs, control a list input

I need to control a list of list of input like this:
import React from "react";
import "./styles.css";
const testData = [
{
name: "list 1",
content: [
{
id: 1,
value: " this is value 1 of list 1"
},
{
id: 2,
value: " this is value 2 of list 1"
},
{
id: 3,
value: " this is value 3 of list 1 "
}
]
},
{
name: "list 2",
content: [
{
id: 1,
value: " this is value 1 of list 2"
},
{
id: 2,
value: " this is value 2 of list 2"
},
{
id: 3,
value: " this is vainput'liste 3 of list 2"
}
]
}
];
class MyList extends React.Component {
constructor(props) {
super(props);
this.state = {
data: this.props.list
};
}
handleChange = (e, index) => {
const newData = this.state.data;
newData[index] = e.target.value;
this.setState({
data: newData
});
};
updateData = newData => {
this.setState({
data: newData
});
};
add = () => {
const newData = this.state.data;
newData.push({
id: newData.length + 1,
value: "new data"
});
this.setState({
data: newData
});
};
remove = (e, index) => {
const newData = this.state.data;
newData.splice(index, 1);
this.setState({
data: newData
});
};
save = (e, index) => {
//TODO
};
render() {
return (
<div className="App">
<button onClick={this.add}> Add </button>
<button onClick={this.save}> save </button>
<tr>input'list
{this.state.data.map((item, index) => (
<td>
<input
value={item.value}
onChange={e => this.handleChange(e, index)}
/>
title
<button onClick={e => this.remove(e, index)}> remove </button>
</td>
))}
</tr>
</div>
);
}
}input'list
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
title: testData[0].name,
data: testData[0].content
};
this.ref = React.createRef();
}
changeList = (e, index) => {
this.setState({
title: testData[index].name,
data: testData[index].content
});
this.ref.current.updateData(testData[index].content);
};
render() {
return (
<>
{testData.map((item, index) => (
<button onClick={e => this.changeList(e, index)}>
{" "}
{item.name}{" "}
</button>
))}
this is {this.state.title}
<MyList ref={this.ref} list={this.state.data}>
{" "}
</MyList>{" "}
</>
);
}
}
export default App;
I need some basic method like show list, save, add more input in list, remove ... And with my code, everything works fine. But I feel it's not clean since there's many ways to do 1 thing in React. Can someone read it and give me some feedback? There's 2 things I thought are not good, but I don't know how to improve:
function handleChange of component MyList: I have to calculator index of input to re-render.
I use A Ref to update and get Data from component MyList.

Error: Objects are not valid as a React child (found: object with keys {id, name})

I'm encountering an issue while compiling my reactJS component:
import React, { Component } from "react";
class StatefullComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [
{ id: 1, name: "Perceuse" },
{ id: 2, name: "disqueuse" },
{ id: 3, name: "marteau" },
{ id: 4, name: "clé de 17" },
{ id: 5, name: "meuleuse" }
],
newItem: ""
};
this.addNewItemToState = this.addNewItemToState.bind(this);
this.handleChange = this.handleChange.bind(this);
this.removeThisItem = this.removeThisItem.bind(this);
}
sortItems() {
this.setState({
items: this.state.items.sort()
});
}
addNewItemToState() {
this.setState({
items: [...this.state.items, this.state.newItem],
newItem: ""
});
}
removeThisItem(bidule) {
const tmparray = this.state.items.filter(bazar => {
return bazar.id !== bidule;
});
console.log(tmparray);
this.setState({
items: tmparray
});
}
handleChange(e) {
this.setState({
newItem: e.target.value
});
}
render() {
return (
<div className="statefull">
<h2>Satefull component, build with a class</h2>
<ul>
{this.state.items.map(item => (
<li className="lambda">
{item}
<button onClick={this.removeThisItem(item.id)}>
supprimer cet item
</button>
</li>
))}
</ul>
<input
type="text"
placeholder="Ajouter un element"
value={this.state.newItem}
onChange={this.handleChange}
/>
<button onClick={this.addNewItemToState}>Ajouter</button>
</div>
);
}
}
export default StatefullComponent;
and my App.js:
import "./App.scss";
import React from "react";
import StatelessComponent from "./components/stateless-component";
import StatefullComponent from "./components/statefull-component";
function App() {
return (
<div className="App">
<StatefullComponent />
</div>
);
}
export default App;
Why do I get this error?
'Error: Objects are not valid as a React child (found: object with keys {id, name}). If you meant to render a collection of children, use an array instead.'
Did I badly formatted my items array in my state? It's important to me to have an id because I want to be able to delete an item.
Any clue? Thanks.
That's because you are trying to render an object in map. Change your ul to this:
<ul>
{
this.state.items.map((item, index) => <li className="lambda" key={index}>{item.name}<button onClick={this.removeThisItem(item.id)}>supprimer cet item</button></li>)
}
</ul>
Hope this works for you.
You need to display name using {item.name} but not using {item}. This is reason you get that error.
Also I noticed the following issues:
1-) You need to add a key property to li tag like this: <li className="lambda" key={item.id}>. React needs key property in a list.
2-) You need to change onClick={this.removeThisItem(item.id)} to <button onClick={() => this.removeThisItem(item.id)}>. You are calling the removeThisItem method, but it shouldn't be called.
import React, { Component } from "react";
class StatefullComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [
{ id: 1, name: "Perceuse" },
{ id: 2, name: "disqueuse" },
{ id: 3, name: "marteau" },
{ id: 4, name: "clé de 17" },
{ id: 5, name: "meuleuse" }
],
newItem: ""
};
this.addNewItemToState = this.addNewItemToState.bind(this);
this.handleChange = this.handleChange.bind(this);
this.removeThisItem = this.removeThisItem.bind(this);
}
sortItems() {
this.setState({
items: this.state.items.sort()
});
}
addNewItemToState() {
this.setState({
items: [...this.state.items, this.state.newItem],
newItem: ""
});
}
removeThisItem(bidule) {
const tmparray = this.state.items.filter(bazar => {
return bazar.id !== bidule;
});
console.log(tmparray);
this.setState({
items: tmparray
});
}
handleChange(e) {
this.setState({
newItem: e.target.value
});
}
render() {
return (
<div className="statefull">
<h2>Satefull component, build with a class</h2>
<ul>
{this.state.items.map(item => (
<li className="lambda" key={item.id}>
{item.name}
<button onClick={() => this.removeThisItem(item.id)}>
supprimer cet item
</button>
</li>
))}
</ul>
<input
type="text"
placeholder="Ajouter un element"
value={this.state.newItem}
onChange={this.handleChange}
/>
<button onClick={this.addNewItemToState}>Ajouter</button>
</div>
);
}
}
export default StatefullComponent;
Playground

How to update state in map function in reactjs

I am having 4 buttons each button have name id and selected boolean flag.
What I am trying to achieve is, on click of button, boolean button flag should be changed of that particular button. For this, I need to setState in map function for that particular button Id.
My issue is I am unable to setState in map function for that particular clicked button, its btnSelected should be changed
My aim is to create a multi-select deselect button.Its kind of interest selection for the user and based on that reflect the UI as well my array. Here is my code.
Thanks in anticipation.
import React, { Component } from "react";
import { Redirect } from "react-router-dom";
export default class Test extends Component {
constructor(props, context) {
super(props, context);
this.handleChange = this.handleChange.bind(this);
this.state = {
value: "",
numbers: [1, 2, 3, 4, 5],
posts: [
{
id: 1,
topic: "Animal",
btnSelected: false
},
{
id: 2,
topic: "Food",
btnSelected: false
},
{
id: 3,
topic: "Planet",
btnSelected: false
},
{ id: 4, topic: "Nature", btnSelected: false }
],
allInterest: []
};
}
handleChange(e) {
//console.log(e.target.value);
const name = e.target.name;
const value = e.target.value;
this.setState({ [name]: value });
}
getInterest(id) {
this.state.posts.map(post => {
if (id === post.id) {
//How to setState of post only btnSelected should change
}
});
console.log(this.state.allInterest);
if (this.state.allInterest.length > 0) {
console.log("Yes we exits");
} else {
console.log(id);
this.setState(
{
allInterest: this.state.allInterest.concat(id)
},
function() {
console.log(this.state);
}
);
}
}
render() {
return (
<div>
{this.state.posts.map((posts, index) => (
<li
key={"tab" + index}
class="btn btn-default"
onClick={() => this.getInterest(posts.id)}
>
{posts.topic}
<Glyphicon
glyph={posts.btnSelected === true ? "ok-sign" : "remove-circle"}
/>
</li>
))}
</div>
);
}
}
Here's how you do something like this:
class App extends Component {
state = {
posts: [{
name: 'cat',
selected: false,
}, {
name: 'dog',
selected: false
}]
}
handleClick = (e) => {
const { posts } = this.state;
const { id } = e.target;
posts[id].selected = !this.state.posts[id].selected
this.setState({ posts })
}
render() {
return (
<div>
<form>
{this.state.posts.map((p, i) => {
return (
<div>
<label>{p.name}</label>
<input type="radio" id={i} key={i} checked={p.selected} onClick={this.handleClick} />
</div>
)
})}
</form>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Working example here.
You can do this by passing the index from the map into each button's handleClick function, which would then return another function that can be triggered by an onClick event.
In contrast to Colin Ricardo's answer, this approach avoids adding an id prop onto each child of the map function that is only used for determining the index in the handleClick. I've modified Colin's example here to show the comparison. Notice the event parameter is no longer necessary.
class App extends Component {
state = {
posts: [{
name: 'cat',
selected: false,
}, {
name: 'dog',
selected: false
}]
}
handleClick = (index) => () => {
const { posts } = this.state;
posts[index].selected = !this.state.posts[index].selected
this.setState({ posts })
}
render() {
return (
<div>
<form>
{this.state.posts.map((p, i) => {
return (
<div>
<label>{p.name}</label>
<input type="checkbox" key={i} checked={p.selected} onClick={this.handleClick(i)} />
</div>
)
})}
</form>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Working example here

React JS: How to make this delete button work

I have a simple ToDo application written in React. I cannot figure out how to remove the ToDos.
I have two separate files App.js
import React, { Component } from 'react';
import './App.css';
import ToDo from './components/ToDo.js';
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos: [
{ id: 1, description: 'Walk the cat', isCompleted: true },
{ id: 2, description: 'Throw the dishes away', isCompleted: false },
{ id: 3, description: 'Buy new dishes', isCompleted: false}
],
newTodoDescription: ''
};
this.deleteTodo = this.deleteTodo.bind(this);
}
handleChange(e) {
this.setState({ newTodoDescription: e.target.value })
}
handleSubmit(e) {
e.preventDefault();
if (!this.state.newTodoDescription) { return }
const newTodo = { description: this.state.newTodoDescription, isCompleted: false };
this.setState({ todos: [...this.state.todos, newTodo], newTodoDescription: '' });
}
toggleComplete(index) {
const todos = this.state.todos.slice();
const todo = todos[index];
todo.isCompleted = todo.isCompleted ? false : true;
this.setState({ todos: todos });
}
deleteTodo(id) {
this.setState((prevState) => ({
items: prevState.items.filter(item => item.id !== id),
}))
}
render() {
return (
<div className="App">
<form onSubmit={ (e) => this.handleSubmit(e)}>
<input type="text"
value={ this.state.newTodoDescription }
onChange={ (e) => this.handleChange(e) }
/>
<input type="submit" />
</form>
<ul>
{ this.state.todos.map( (todo, index) =>
<ToDo key={ index }
description={ todo.description }
isCompleted={ todo.isCompleted }
toggleComplete={ () => this.toggleComplete(index) }
onDelete={ this.deleteTodo }
/>
)}
</ul>
</div>
);
}
}
export default App;
The second file is ToDo.js
import React, { Component } from 'react';
class ToDo extends Component {
render() {
return (
<li>
<input type="checkbox" checked={ this.props.isCompleted } onChange={ this.props.toggleComplete } />
<button onClick={() => this.props.deleteTodo(this.props.id)}>Delete</button>
<span>{ this.props.description }</span>
</li>
);
}
}
export default ToDo;
When I click on the button I am presented with an error: TypeError: _this2.props.deleteTodo is not a function
How can I get my current code to work?
The name of your prop is onDelete,
so in your component ToDo.js, you have to call your function like below
<button onClick={() => this.props.onDelete(this.props.id)}>Delete</button>
as answer to your comment
your state is define like
this.state = {
todos: [
{ id: 1, description: 'Walk the cat', isCompleted: true },
{ id: 2, description: 'Throw the dishes away', isCompleted: false },
{ id: 3, description: 'Buy new dishes', isCompleted: false}
],
newTodoDescription: ''
};
So, your function deleteTodo should be like
deleteTodo(id) {
this.setState((prevState) => ({
todos: prevState.todos.filter(item => item.id !== id),
}))
};
I'll suggest you should use the splice method.

Resources