How to check/uncheck a list of checkboxes in react - javascript

I have a room page and in that page I have a list of sensors attached to that room, those sensors can be selected using a checkbox, like so:
<div className="checkboxRowContent">
{sensors.map(s => {
return (
<div className="checkboxElementWrapper" key={s.id}>
<label htmlFor={`sensor${s.id}`}>
<div className="checkboxLabel">
<Link to={`/sensors/edit/${s.id}`}>{s.name}</Link>
</div>
<input
type="checkbox"
id={`sensor${s.id}`}
name="sensorId"
value={s.id}
checked={s.roomId === values.id}
onChange={handleCheckbox}
/>
<span className="checkbox" />
</label>
</div>
);
})}
</div>
the problem is - this approach prohibits me from unchecking the checkbox (so if in db that sensor is attached to that room - that's it). How could I rewrite this so that I can check/uncheck this checkbox?

in the class you must have state for that,
a sample would be somewhat like this
export default class yourComponent extends React.Component {
state = {
checkedBoxes: []
}
handleCheckbox = (e, s) => {
const checkedBoxes = [...this.state.checkedBoxes];
if(e.target.checked) {
checkedBoxes.push(s)
} else {
const index = checkedBoxes.findIndex((ch) => ch.roomId === s.roomId);
checkedBoxes.splice(index, 1);
}
this.setState({checkedBoxes});
}
render() {
return(
<div className="checkboxRowContent">
{sensors.map(s => {
return (
<div className="checkboxElementWrapper" key={s.id}>
<label htmlFor={`sensor${s.id}`}>
<div className="checkboxLabel">
<Link to={`/sensors/edit/${s.id}`}>{s.name}</Link>
</div>
<input
type="checkbox"
id={`sensor${s.id}`}
name="sensorId"
checked={checkedBoxes.find((ch) => ch.roomId === s.roomId)}
onChange={(e) => handleCheckbox(e, s)}
/>
<span className="checkbox" />
</label>
</div>
);
})}
</div>
)
}
}
A state, checkedBoxes for getting all selected checkboxes.
A handler handleCheckbox for handling checkbox clicks,

You have handleCheckBox and a controlled component. We don't see what you do in the event handler but when it's controlled, you can check it by altering your sensors array (if in state/props) so s.roomId === values.id will be true.
If you don't want it to be controlled, you can probably use defaultChecked which will let you work with it in a different way.
see https://reactjs.org/docs/forms.html#controlled-components

import React, {Component} from 'react';
import axios from 'axios';
const Books = props=>(
<div className='form-group'>
<label>{props.book}
<input type='checkbox' name={props.name} className='form-check' onChange={props.onChange} />
</label>
</div>
)
class Total extends Component{
constructor(props){
super(props);
this.onChangeCheck = this.onChangeCheck.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.state={
checkBoxes: [],
books:[]
}
}
componentDidMount() {
axios.get('http://localhost:3000/api/book/').then(resolve=>{
console.log(resolve.data.data);
this.setState({
books:resolve.data.data
}).catch(err=>{
console.log(err)
})
})
}
onChangeCheck(e){
console.log(e.target.name)
if(e.target.checked){
const array = this.state.checkBoxes;
array.push(e.target.name)
this.setState({
checkBoxes:array
})
}else{
const array = this.state.checkBoxes;
const index = array.indexOf(e.target.name);
console.log(index)
array.splice(index,1);
console.log(array);
this.setState({
checkBoxes:array
})
}
}
onSubmit(e){
e.preventDefault();
axios.put("http://localhost:8080/books/getTotal/",this.state.checkBoxes).then(resolve=>{
console.log(resolve)
alert(`Total price of books ${resolve.data}`);
}).catch(err=>{
console.log(err);
})
}
render(){
return(
<div className='card'>
<div className='card-header'>
</div>
<div className='card-body'>
<form className='form' onSubmit={this.onSubmit}>
<div className='form-group'>
{
this.state.books.map(object=>(
<Books name={object._id} book={object.name} onChange={this.onChangeCheck} />
)
)
}
</div>
<div className='form-group'>
<button type='submit' className='btn btn-success'>Get Total</button>
</div>
</form>
</div>
</div>
)
}
}
export default Total;

Related

Reactjs problems with radio buttons when fetch data

I want to build menu for my website with food cards. I fetch data(name of food, recipe, price) from my rest api and then i show this data on my react app. In this food card I have three radio buttons for mini, middle and maxi prices. When I change button on one card it changes on all cards. First image, when price 35 and
Second image, when I change price on first card, but it changes on all cards
this is my code:
constructor(props){
super(props);
this.state = {
shavermas : [],
price : ''
};
}
componentDidMount(){
this.findAllPosts();
}
findAllPosts(){
axios.get("http://localhost:8080/api/shaverma/all")
.then(response => response.data)
.then((data) => {
this.setState({shavermas: data})
});
}
onChange = e =>{
this.setState({price : e.target.value})
}
render(){
let {price} = this.state;
const {shavermas} = this.state;
return(
<>
{shavermas.map((shaverma, index) => (
<div className="food-cart">
<div className="product-img-div">
<img
src={shavermaPhoto}
className="d-inline-block product-img"
alt="shaverma"
/>
</div>
<div className="food-cart-body">
<div>
<h3>Шаверма <span>{shaverma.foodName}</span></h3>
<p>{shaverma.recipe}</p>
<form className="radio-buttons">
<div className="radio">
<label className="btn-radio">
<input type="radio" value={shaverma.priceMini} onChange={this.onChange} checked={price.charAt(0) == '' ? shaverma.priceMini : price == shaverma.priceMini}/>
<span>Mini</span>
</label>
</div>
<div className="radio">
<label className="btn-radio">
<input type="radio" value={shaverma.priceMiddle} onChange={this.onChange} checked={price == shaverma.priceMiddle}/>
<span>Middle</span>
</label>
</div>
<div className="radio">
<label className="btn-radio">
<input type="radio" value={shaverma.priceMaxi} onChange={this.onChange} checked={price == shaverma.priceMaxi} />
<span>Maxi</span>
</label>
</div>
</form>
<div className="food-cart-footer">
<strong>{price.charAt(0) === '' ? shaverma.priceMini : price}₴</strong>
<p>Хочу!</p>
</div>
</div>
</div>
</div>
))}
</>
)
}
You are using common Price state for all cards, you have to use price property for individual card,
Use it like this :
onChange = (e,index) =>{
let newShavermas = this.state.shavermas ;
newShavermas[index].price=e.target.value;
this.setState({price : e.target.value})
}
and while fetching the result include price property in each record
findAllPosts(){
axios.get("http://localhost:8080/api/shaverma/all")
.then(response => response.data)
.then((data) => {
let dataVal = data.map(ele=>ele.Price='');
this.setState({shavermas: dataVal })
});
}
and in return call onChange like this :
return(
<>
{shavermas.map((shaverma, index) => (
<div className="food-cart">
<div className="product-img-div">
<img
src={shavermaPhoto}
className="d-inline-block product-img"
alt="shaverma"
/>
</div>
<div className="food-cart-body">
<div>
<h3>Шаверма <span>{shaverma.foodName}</span></h3>
<p>{shaverma.recipe}</p>
<form className="radio-buttons">
<div className="radio">
<label className="btn-radio">
<input type="radio" value={shaverma.priceMini} onChange={(e)=>this.onChange(e,index)} checked={shaverma.price.charAt(0) == '' ? shaverma.priceMini : price == shaverma.priceMini}/>
<span>Mini</span>
</label>
</div>
<div className="radio">
<label className="btn-radio">
<input type="radio" value={shaverma.priceMiddle} onChange={(e)=>this.onChange(e,index)} checked={shaverma.price == shaverma.priceMiddle}/>
<span>Middle</span>
</label>
</div>
<div className="radio">
<label className="btn-radio">
<input type="radio" value={shaverma.priceMaxi} onChange={(e)=>this.onChange(e,index)} checked={shaverma.price == shaverma.priceMaxi} />
<span>Maxi</span>
</label>
</div>
</form>
<div className="food-cart-footer">
<strong>{shaverma.price.charAt(0) === '' ? shaverma.priceMini : shaverma.price}₴</strong>
<p>Хочу!</p>
</div>
</div>
</div>
</div>
))}
</>
)
This is because all of you cart items are looking at the same state value!
onChange = e =>{
this.setState({price : e.target.value}) < --- changes price to all cards
}
To solve this, you will need to have a price inside each shaverma then change it alone.
I would suggest starting by creating a FootCart component with its own state.
Something along the line of:
class FootCart implements React.Component {
...
render() {
return (
<div className="food-cart">
...
</div>
}
}
class Cards implements React.Component {
...
render(){
return (
<>
{shavermas.map((shaverma, index) => (<FootCart props/>)}
</>
}
}
Good Luck!

innerHTML works only once in my react app

What I am trying to do is clear my container div when the user hits the search button so all the existing search results are removed.
The problem is I am using innerHTML to clear the container div but it only works for the first time.
If you search for something for the second time then you will see no results are rendered.
I've just started learning reactjs.
Here is my code (I have removed the API key and the id just FYI).
import React from "react";
import Recipe from "./Recipe";
import "./styles.css";
export default class App extends React.Component {
constructor() {
super();
this.state = {
searchQuery: "Icecream",
searchData: [],
error: false,
loader: false
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({
[e.target.name]: e.target.value
});
}
handleSubmit(e) {
e.preventDefault();
document.querySelector(".control.field").classList.add("is-loading");
this.setState({
error: false,
loader: true
});
document.querySelector(".columns").innerHTML = "test";
fetch(
`https://api.edamam.com/search?q=${
this.state.searchQuery
}&app_id=id&app_key=key`
)
.then(response => response.json())
.then(data => {
this.setState({
searchData: data.hits
});
})
.catch(err => {
this.setState({
error: true
});
})
.finally(() => {
document.querySelector(".control.field").classList.remove("is-loading");
this.setState({
loader: false
});
});
}
renderIngredients = arr => arr.map((el, index) => <li key={index}>{el}</li>);
render() {
let result;
result = this.state.searchData.map((el, index) => (
<Recipe
key={index}
image={el.recipe.image}
title={el.recipe.label}
ingredients={this.renderIngredients(el.recipe.ingredientLines)}
/>
));
return (
<div>
<form className="px-3 py-3" onSubmit={this.handleSubmit}>
<div className="field is-grouped">
<p className="control field is-expanded">
<input
className="input is-rounded"
type="text"
placeholder="Find a recipe"
name="searchQuery"
value={this.state.searchQuery}
onChange={this.handleChange}
/>
</p>
<p className="control">
<button className="button is-primary">Search</button>
</p>
</div>
</form>
{this.state.loader ? (
<div className="spinner-box">
<div className="three-quarter-spinner" />
</div>
) : (
""
)}
{this.state.error ? (
<div className="px-3 py-3">
<div className="notification is-danger">
Some error occured. Unable to fetch
</div>
</div>
) : (
<div className="columns is-multiline px-3 py-3 is-mobile">
{result}
</div>
)}
</div>
);
}
}
Recipe component :
import React from "react";
export default class Recipe extends React.Component {
render() {
return (
<div className="column is-one-quarter-desktop is-half-mobile">
<div className="card">
<div className="card-image">
<figure className="image is-4by3">
<img src={this.props.image} alt="" />
</figure>
</div>
<div className="card-content">
<p className="title is-6">{this.props.title}</p>
<ul>{this.props.ingredients}</ul>
</div>
</div>
</div>
);
}
}
What do you think about using new state for that?
What I mean is that:
this.state = {
...,
focusSearch: false
}
const handleFocus = () => {
this.setState({ ...this.state, focusSearch: true});
}
<input
className="input is-rounded"
type="text"
placeholder="Find a recipe"
name="searchQuery"
value={this.state.searchQuery}
onFocus={this.handleFocus}
onChange={this.handleChange}
/>
render() {
return (
{
focusSearch ? <div></div> : <></>;
}
)
}
I figured it out this morning. It can be done easily using conditional rending and ternary operator.
Thank you, everyone.

How do I validate textfields in a dynamic array

I have one mother component and one child component. In the child component there are two textfields (name and email) and an add button. When the user presses on add a new set of the same textfields will be rendered. I save those textfields in my mother component in an array. I want to have a validate function that checks of the values are valid. If not i want to give the error props a true value so that the user can see that field is wrong. The only problem i have is figuring out how to give right textfield in the array the error value. Because now every textfield will get the error value.
Child Component:
import React from 'react';
import TextField from '#material-ui/core/TextField';
import AddIcon from '#material-ui/icons/Add';
import Minus from '#material-ui/icons/Delete';
import Button from '#material-ui/core/Button';
class People extends React.Component {
constructor(props){
super(props);
}
render(){
const {classes} = this.props;
return (
<div>
{this.props.people.map((person, index) => (
<div key={person}>
<div className="container">
<div className="row">
<div className="col s2">
<Button
mini
variant="fab"
color="primary"
aria-label="minus"
onClick={e =>
this.props.removeRow(index)}
data-toggle="tooltip"
>
<Minus/>
</Button>
</div>
<div>
<div className="col s5">
<TextField
name="name"
id="standard-dense"
label="Teamlid naam"
margin="dense"
value={person.name}
error={e =>
this.props.validate(e, index)}
onChange={e =>
this.props.onChange(e, index)}
/>
</div>
<div className="col s5">
<TextField
name="email"
id="standard-dense"
label="Teamlid email"
margin="dense"
value={person.email}
error={e =>
this.props.validate(e, index)}
onChange={e =>
this.props.onChange(e, index)}
/>
</div>
</div>
</div>
</div>
</div>
))}
<div className="container">
<div className="row">
<div className="col s2">
<Button
mini
variant="fab"
color="primary"
aria-label="Add"
onClick={this.props.addRow}
data-toggle="tooltip"
className="btn btn-xs btn-primary"
data-original-title="">
<AddIcon/>
</Button>
</div>
<div className="col s5">
</div>
<div className="col s5">
</div>
</div>
</div>
</div>
);
}
};
export default People;
Mother Component:
import React, {Component} from 'react';
import People from './People';
class Form extends React.Component{
constructor(props){
super(props);
this.state = {
people: []
}
}
addRow = () => {
this.setState(previousState => {
return {
people: [...previousState.people, {name: "", email: "", error:false}]
};
});
};
removeRow = index => {
this.setState(previousState => {
const people = [...previousState.people];
people.splice(index, 1);
return { people };
});
};
//onChange functie van de teamleden rijen
onChange = (event, index) => {
const { name, value } = event.target;
this.setState(previousState => {
const people = [...previousState.people];
people[index] = { ...people[index], [name]: value };
return { people };
});
};
validate = (event,index) => {
event.preventDefault();
if(!event.target.value){
return true;
}else{
return false;
}
};
render(){
const {classes} = this.props;
return(
<form>
<People
addRow={this.addRow}
removeRow={this.removeRow}
onChange={this.onChange}
people={this.state.people}
validate={this.validate}
/>
<button onClick={this.validate}>Validate</button>
</form>
);
}
}
export default Form;
I know that the validate function probably needs the index of the wrong textfield. But I don't know how to properly implement it.

Dynamically adding button to div ReactJS?

Ok so here's my code:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
var uuid = require("uuid-v4");
// Generate a new UUID
var myUUID = uuid();
// Validate a UUID as proper V4 format
uuid.isUUID(myUUID); // true
var questionNum = 0;
class App extends Component {
constructor(props) {
super(props);
this.state = {
key: uuid(),
title: "",
author: "",
questions: [],
answers: []
};
this.handleChange = this.handleChange.bind(this);
this.addQuestion = this.addQuestion.bind(this);
this.removeItem = this.removeItem.bind(this)
}
componentDidMount() {
// componentDidMount() is a React lifecycle method
this.addQuestion();
}
handleChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
removeItem (index) {
questionNum--;
this.setState(({ questions }) => {
const mQuestions = [ ...questions ]
mQuestions.splice(index, 1)
return { questions: mQuestions }
})
this.setState(({ answers }) => {
const mAnswers = [ ...answers]
mAnswers.splice(index, 4)
return { answers: mAnswers}
})
console.log(
"answers",
this.state.answers,
"questions",
this.state.questions,
questionNum,
this.state.title,
this.state.author
);
}
addQuestion() {
questionNum++;
this.setState(previousState => {
const questions = [
...previousState.questions,
<input
type="text"
onChange={this.handleChange}
name="question"
key={uuid()}
/>
];
const answers = [
...previousState.answers,
];
for (var i = 0; i < 4; i++) {
answers.push(
<input
type="checkbox"
name={uuid()}>
<input
type="text"
onChange={this.handleChange}
name={uuid()}
/>
</input>
);
}
return { questions, answers };
});
console.log(
"answers",
this.state.answers,
"questions",
this.state.questions,
questionNum,
this.state.title,
this.state.author
);
}
render() {
return (
<div className="App">
<div>
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Quiz Form 3.0</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
<div className="formDiv">
<form>
<div className="Intro">
Give your Quiz a title:{" "}
<input
type="text"
value={this.state.title}
onChange={this.handleChange}
name="title"
/>
<br />
Who's the Author?{" "}
<input
type="text"
value={this.state.author}
onChange={this.handleChange}
name="author"
/>
<br />
<br />
</div>
<div className="questions">
<div className="questions">
Now let's add some questions... <br />
<ol>
{this.state.questions.map(question => {
return (
<li>
<div key={uuid()}>
Question
{question}<br />
Answer Choices<br />
{Array.from({ length: 4 }, () => (
<input type="text" key={uuid()} onChange={this.handleChange} />
))}
</div>
</li>
);
})}
</ol>
</div>
{
// This is what it would look like for the structure
// I proposed earlier.
// this.state.questions.map((question) {
// return (
// <div>{question.quesion}</div>
// {
// question.answers.map((answer) => {
// return (<div>{answer}</div>);
// })
// }
// );
// })
// This would output all questions and answers.
}
</div>
</form>
<button id="addQuestionButton" onClick={this.addQuestion}>Add Question</button>
{ this.state.questions.map((question, index) => {
return <button key={uuid()} onClick={ () => this.removeItem(index) }>Remove Question</button>
}) }
</div>
</div>
);
}
}
export default App;
Ok so here's a link to a quick video demonstrating what it does as of now. In the video you can see the Remove Question buttons that are created (at the bottom of the form) each time a question is added. I would like to have each question's Remove Question button be next to it/in the same div. I'm not entirely sure how I would go about doing this. Any thoughts?
UPDATE: Ok so I have put the buttons inside of the same div with the actual question, but I realized that i am adding a button for each object in the array. Which means that when a question is added a button to remove it is added to every question on the form. I need to make it so it does not .map this. I'm not entirely sure what other function I will do for this, maybe I don't even need a function. I will try my best to work it out. Here's the updated code (some of it):
<div className="questions">
Now let's add some questions... <br />
<ol>
{this.state.questions.map(question => {
return (
<li>
<div key={uuid()}>
Question
{question}<br />
{
this.state.questions.map((question, index) => {
return <button key={uuid()} onClick={ () => this.removeItem(index) }>Remove Question</button>
})
}
Answer Choices<br />
{Array.from({ length: 4 }, () => (
<div>
<input type="checkbox" />
<input type="text" key={uuid()} onChange={this.handleChange} />
</div>
))}
</div>
</li>
);
})}
</ol>
</div>
Something like this...
<ol>
{this.state.questions.map((question, index) => {
return (
<li>
<div key={uuid()}>
Question
{question}<br />
<button onClick={ () => this.removeItem(index) }>
Remove Question
</button>
Answer Choices<br />
{Array.from({ length: 4 }, () => (
<div key={uuid()}>
<input type="checkbox" />
<input type="text" onChange={this.handleChange} />
</div>
))}
</div>
</li>
);
})}
</ol>

add input field when button is clicked

I have a form for adding recipe where there is ingredients button. A recipe can have many ingredients. When that button is clicked an input field for adding ingredients should be appear beneath ingredient button. What i tried to do is?
import Input from './Input.jsx';
export default class Sell extends Component {
constructor()
{
super();
this.state = {
inputValues : {}
}
this.onHandleSubmit =this.onHandleSubmit.bind(this);
}
onChange( name, { target : { value } })
{
const inputValues = this.state.inputValues;
inputValues[name] = value;
this.setState({ inputValues })
}
onHandleSubmit()
{
console.log('clicked');
const name = `ingrediant-${Object.keys(this.state.inputValues).length}`;
let inputbox = <Input name={ name }
onChange={ this.onChange.bind(this, name )} />
}
onSubmit(event){
event.preventDefault();
}
render() {
return (
<div className="row">
<h2 className="flow-text text-center">Add Recipe</h2>
<form onSubmit={this.onSubmit} className="col offset-s4 s4">
<div className="row">
<div className="input-field col s12">
<input ref="name" id="name" type="text" className="validate flow-text" />
<label htmlFor="name">Name of Recipe</label>
</div>
</div>
<div className="input-field col s12">
<a onClick={this.onHandleSubmit} className="waves-effect waves-light btn btn-block"><i className="material-icons right">add</i>Ingredients</a>
</div>
</div>
{this.state.inputValues}
<div className="row">
<button className="waves-effect waves-light btn btn-block">Submit</button>
</div>
</form>
</div>
);
}
}
How can i create new dynamic(different ref for newly created input box) input field for ingredients when button is clicked?
In your case I would make the input a separate component like
class Input extends Component
{
render()
{
const { name, onChange } = this.props;
return(<div className="row">
<div className="input-field col s12">
<input id={name}
type="text"
className="validate flow-text"
onChange={ this.props.onChange } />
<label htmlFor={name}>Name of Recipe</label>
</div>
</div>)
}
}
And pass an onChange callback from the Sell component, the Sell component will maintain the values of the inputs, and when you want to send the values to the server you have it on the state. Something like:
class Sell extends Component
{
constructor()
{
super();
this.state = {
inputValues = {},
inputs : []
}
}
onChange( name, { target : { value } })
{
const inputValues = this.state.inputValues;
inputValues[name] = value;
this.setState({ inputValues })
}
onHandleSubmit()
{
const name = `incrediant-${this.state.inputs.length}`;
let inputbox = <Input name={ name }
key={this.state.inputs.length}
onChange={ this.onChange.bind(this, name )} />
const inputs = this.state.inputs;
inputs.push( inputbox );
this.setState( { inputs } );
}
...
....
render()
{
...
...
{
this.state.inputs.map( i => i );
}
}
}
In that case you dont have to keep refs

Categories

Resources