How to set the handler for multiple checkboxes in react? - javascript

I've created multiple checkboxes in my small project. I'm facing a problem in retrieving the info of the checkbox while giving a checkmark. if I give a checkmark on toys then the value should be toys or if I give checkmark on both toys and lights i should get both toys and lights in the console.
Here is code:
import React from "react";
import { render } from "react-dom";
import { Segment, Form, Checkbox, Button } from "semantic-ui-react";
import axios from "axios";
export default class ShowForm extends React.Component {
constructor(props) {
super(props);
this.state = {
event: ""
};
}
handleCheck = event => {
console.log("Hello", event);
};
render() {
return (
<div>
<Segment>
<Form>
<Checkbox label="Cake" onChange={this.handleCheck} />
<br />
<Checkbox label="Lights" onChange={this.handleCheck} />
<br />
<Checkbox label="Flowers" onChange={this.handleCheck} />
<br />
<Checkbox label="Toys" onChange={this.handleCheck} />
<br />
<Button onClick={this.handleSubmit}> Submit </Button>
</Form>
</Segment>
</div>
);
}
}
Here is whole code: "https://codesandbox.io/s/zealous-paper-p9zb5"
Can anyone please help me in this issue?

You need an array to store all the checked status.
handleCheck = id => () => { // Get indentify from each element
const { checkedList } = this.state;
const result = checkedList.includes(id) // Check if checked at this moment
? checkedList.filter(x => x !== id) // If checked, remove
: [...checkedList, id]; // If not, add
this.setState({ checkedList: result }, () => { // setState
console.log(this.state.checkedList); // setState is async, log in callback
});
};
And if you want, you can make the Checkbox component a common one so you don't need to bind the label in three places in each of it.
<Checkbox
label="Cake"
onChange={this.handleCheck("Cake")}
checked={checkedList.includes("Cake")} // If included, checked
/>
Another minimum reproducible demo
Try it in-text:
const list = [...Array(10).keys()].map(x => ({ id: x }));
const App = () => {
const [selected, setSelected] = React.useState([]);
const onChangeHandler = id => () => {
selected.includes(id)
? setSelected(selected.filter(x => x !== id))
: setSelected([...selected, id]);
};
const onRemove = () => {
setSelected([]);
};
return (
<div className="App">
{list.map(item => (
<input
type="checkbox"
key={item.id}
checked={selected.includes(item.id)}
onChange={onChangeHandler(item.id)}
/>
))}
<br />
<button onClick={onRemove}>Remove all</button>
<div>{selected.join(",")}</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

Pretty sure, you can ES6 named properties and arrow functions to create a clean flow
Consider a state:
this.state={
isToy: false,
isCake: false,
isFlower: false,
isLight: false
}
Add a property of Name. These names should ideally match the ones in state.
<Form>
<Checkbox name="isCake" label="Cake" checked={this.state.isCake} onChange={this.handleCheck} />
<Checkbox name="isLight" label="Lights" checked={this.state.isLight} onChange={this.handleCheck} />
<Checkbox name="isFlower" label="Flowers" checked={this.state.isFlower} onChange={this.handleCheck} />
<Checkbox name="isToy" label="Toys" checked={this.state.isToy} onChange={this.handleCheck} />
<Button onClick={this.handleSubmit}> Submit </Button>
</Form>
Then write the handleCheck arrow function, using ES6 name properties feature to access state property:
handleCheck = (event) => {
let name = event.target.name
this.setState{[name]: !this.state[name]}
}
References:
React Controlled Forms: https://reactjs.org/docs/forms.html
Semantic UI Checkboxes: https://react.semantic-ui.com/modules/checkbox/#usage-remote-control

Related

React hooks - update state from an input in a child component which is called on a button click

I’m trying to update a state from an input, which works fine when the field is is in the same component, but doesn’t work when I pass it into a child component, seemingly no matter what I pass to it!
const Create = () => {
const [question, setQuestion] = useState('');
const [components, setComponents] = useState([""]);
const handleQInputChange = event => {
setQuestion(event.target.value)
}
function addComponent() {
setComponents([...components, "Question"])
}
return (
<Button onClick={addComponent} text="Next question"/>
<ol>
{components.map((item, i) => (
<li>
<CreateQuestion question={question} onChange=
{handleQInputChange}/>
</li>
))}
</ol>
)}
and then CreateQuestion component looks like:
const CreateQuestion = (props) => {
function handleQInputChange( event ) {
props.onChange(event.target.value)
}
return (
<div className="Component">
<label>Question</label>
<input
name="question"
id="question"
value={props.value}
onChange={props.handleQInputChange}
type="text"
placeholder="Question"
/>
<br />
I've followed at least 10 guides on how to pass the data back and forth, so it may have become a little convoluted. If I put the Question input directly into the parent component the state updates, so I suspect it's just me not passing props correctly but completely stuck!
Thank you
You are doing a lot of wrong things:
Using wrong props.
Passing event.target.value which is a string, to props.onChange which is a
function that accepts a type Event.
Declaring the controlled input state on the parent, while you need the state local to the input, since you have multiple inputs and I don't think you want to share the same state among them.
function App() {
const [questions, setQuestions] = useState([]);
const [components, setComponents] = useState(['']);
function addComponent() {
setComponents([...components, 'Question']);
}
function addQuestion(question) {
setQuestions((qs) => [...qs, question]);
}
return (
<>
<Button onClick={addComponent} text="Next question" />
<ol>
{components.map((item, i) => (
<li>
<CreateQuestion idx={i} addQuestion={addQuestion} />
</li>
))}
</ol>
<ul>
<h5>Submitted Questions:</h5>
{questions.map((question, i) => (
<li>
<span style={{ marginRight: '10px' }}>
Question n.{question.id}
</span>
<span>{question.body}</span>
</li>
))}
</ul>
</>
);
}
const CreateQuestion = ({ addQuestion, idx }) => {
const [question, setQuestion] = useState('');
const handleChange = (event) => {
setQuestion(event.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
addQuestion({ id: idx + 1, body: question });
};
return (
<div className="Component">
<form onSubmit={handleSubmit}>
<label></label>
<input
name="question"
id="question"
value={question}
onChange={handleChange}
type="text"
placeholder="Question"
/>
<button>ADD QUESTION</button>
</form>
</div>
);
};
I refactored your code in this demo https://stackblitz.com/edit/react-h7m1cu check if it helps you.
I fixed the main issues, moved the input state down to the CreateQuestion Component, added a function and a state to the Parent Component that holds all the questions added when you submit the input, this way you can handle data in your Parent, if for example you want to send it to your server.
Please change your CreateQuestion component like below
const CreateQuestion = (props) => {
return (
<div className="Component">
<label>Question</label>
<input
name="question"
id="question"
value={props.value}
onChange={props.onChange}
type="text"
placeholder="Question"
/>
</div>
);
}
The problem is
You use an attribute like
onChange={props.handleQInputChange}
But you do not have handleQInputChange when you call your component as an attribute
<CreateQuestion question={question} onChange=
{handleQInputChange}/>

React: Can we pass 2 forms from 2 different child components to a parent, and submit them with a button which is inside the parent component?

I am trying to somehow pass the data from 2 forms and 2 different components to the parent component and then somehow console.log all of this data with a button that is inside the parent component. Then I will simply send these data to a JSON file or a dummy database.
When I press the submit button of course nothing is triggered right now because I simply don't know how to pass the function from the children to the parent. I have tried many ways, but I would appreciate it if you could show me a way to lift the state and combine the forms.
For the input, in order to pass refs, I have used React.forwardRef()
It would be easy to just have 1 big component with 1 form and then the button inside this component, but since it is a fun project, I want to learn how to implement this functionality in case I will use it in the future. You can find a screenshot on this link:
[]
[1]: https://i.stack.imgur.com/myV0N.jpg
Here we go:
1. Parent component
const BookingComponent = () => {
return (
<div>
<CRContainer className="booking-crcontainer">
<CRColumn>
<PickUpCarComponent />
</CRColumn>
<CRColumn>
<CustomerInfo />
</CRColumn>
</CRContainer>
<CRContainer className="booking">
<Button type="submit" btnText="hello there" />
</CRContainer>
</div>
);
};
export default BookingComponent;
2. Child 1
const CustomerInfo = (props) => {
const firstlRef = useRef();
const lastNameRef = useRef();
const onTrigger = (e) => {
e.preventDefault();
//console.log(first1Ref.current.value)
console.log("heaheaheah");
};
return (
<>
<Subtitle stitle={SubtitleLabels.customerInfo} />
<div className="customer-info-container">
<form onSubmit={onTrigger}>
<div>
<LabeledInput
labelText={CustomerInfoLabels.firstName}
type="text"
inputPlaceholder={GeneralLabels.placeholder}
ref={firstlRef}
></LabeledInput>
<LabeledInput
labelText={CustomerInfoLabels.lastName}
type="text"
inputPlaceholder={GeneralLabels.placeholder}
ref={lastNameRef}
></LabeledInput>
</div> ...................
3. Child 2
Haven't put the refs here yet.
const PickUpCarComponent = () => {
return (
<div>
<Subtitle stitle={SubtitleLabels.pickUp} />
<form>
<div className="booking-inner-container">
<div>
<LabeledInput labelText={"Pick-up date*"} type="date"></LabeledInput>
<LabeledInput labelText={"Pick-up time*"} type="time"></LabeledInput>
</div>
<DropDown type="CarGroup" labeltext="Car Group*" attribute="name" />
<DropDown type="RentalOffice" labeltext="Region*" attribute="region" />
</div>
</form>
</div>
);
};
export default PickUpCarComponent;
4. Input Component
const LabeledInput = React.forwardRef((props, ref) => {
const { labelText, type, inputPlaceholder, onChange, className } = props;
return (
<div className={`input-container ${className}`}>
<label htmlFor="labelText">{labelText}</label>
<input
type={type}
placeholder={inputPlaceholder}
onChange={onChange}
ref={ref}
/>
</div>
);
});
export default LabeledInput;
you can use context to pass form handlers to child component then in the child component you can useContext and get value and handlers of parent form and use them.
const FormContext = React.createContext({});
const BookingComponent = () => {
const [values, setValues] = useState();
const handleChange = useCallback((e) => {
//handle child event in parent and save child state in
//parent to use later in submit button
}, []); //set dependency if it's needed
const contextValue = useMemo(() => ({ handleChange }), [handleChange]);
return (
<FormContext.Provider value={contextValue}>
<div>
<CRContainer className="booking-crcontainer">
<CRColumn>
<PickUpCarComponent />
</CRColumn>
<CRColumn>
<CustomerInfo />
</CRColumn>
</CRContainer>
<CRContainer className="booking">
<Button type="submit" btnText="hello there" />
</CRContainer>
</div>
</FormContext.Provider>
);
};
const LabeledInput = (props) => {
const formContext = useContext(FormContext);
const { labelText, type, inputPlaceholder, className } = props;
return (
<div className={`input-container ${className}`}>
<label htmlFor="labelText">{labelText}</label>
<input
type={type}
placeholder={inputPlaceholder}
onChange={formContext.handleChange}
ref={ref}
/>
</div>
);
};

How to display fields from FieldArray in a different component (one component up) with redux-form

I use <FieldArray> from redux-form (slightly older version, react 16.3.0 and redux-form 7.2.1) and it works if I use it like in the docs. But I want the button to display in one component and the fields in a different component:
<App>
<Cmp1/>
<Cmp2/>
// this is where I want the fields
</App>
The button should be in <Cmp1/> while the actual fields should be displayed in <App/>.
<App/>:
class App extends React.Component{
constructor(props){
this.state = {
fields: []
}
}
onFieldArrayChange(field){
const {fields} = this.state;
const newFields = fields.push(field);
this.setState({...this.state, fields: newFields});
}
render(){
return (
<div>
My App
<div>{this.state.fields}</div>
<Cmp1 onFieldArrayChange={this.onFieldArrayChange.bind(this)}
</div>
)
}
}
I tried the following, <Cmp1/>:
class Cmp1 extends React.Component{
renderMyFieldArray({ fields, meta: { error, submitFailed } }){
return(
<button type="button" onClick={() => this.props.onFieldArrayChange(<MyField
key={fields.length}
name={`myName[${fields.length}]`}
index={fields.length})
>Add Field</button>
)
}
render(){
return(
<FieldArray name="myName" component={this.renderMyFieldArray.bind(this} />
)
}
}
This works, however, the input is really slow. When I try to put in a string it cuts off after each letter and I have to click on the field again.
I also tried passing the fields up like so in<Cmp1/>:
<button type="button" onClick={() => this.props.onFieldArrayChange(fields)}>
Add Filter
</button>
And in <App/> I set the state to the fields then:
onFieldArrayChange(field){
const {fields} = this.state;
const newFields = fields.push({});
this.setState({...this.state, fields: newFields});
}
and then in the render function I map over the fields like so:
{this.state.fields.map((name, index)=>{
return (
<MyField
key={index}
name={name}
index={index}
/>
);
})}
In that case the first field does not get displayed. I have to click twice to get 1 field displayed.
How do I achieve this?
I couldn't really find a solution, but I solved it in a different way.
I put the <FieldArray name="myName" component={this.myRenderFunc.bind(this)} /> into <App/>
with myRenderFunc:
myRenderFunc({ fields, meta: { error, submitFailed } }) {
const handleClick = () => {
fields.push({});
this.setState({...this.state, fields});
}
return (
<button type="button" onClick={handleClick.bind(this)}>
Add Filter
</button>
);
}
I save the fields in the state and then pass them down into my component:
<Comp1 fields={this.state.fields}/>.

How to filter data, using checkboxes, without using redux

I am using React to display book titles that I want filtered by category. I want the titles filtered once a checkbox next to the category name is clicked. I am not using a submit button.
I am somewhat new to React and read the documentation about "lifting state," but I haven't been able to get that to work. I have not yet read the Hooks or Context API documentation. Perhaps that's the solution, but it seems what I'm doing isn't complex enough for that...maybe not?
class Checkbox extends Component {
state = {
checked: false
}
handleClick = (e) => {
this.setState(() => ({ checked: !this.state.checked }))
}
render() {
const name = this.props.name;
return (
<label className="form__group">
<input type="checkbox" checked={this.state.checked} onChange={this.handleClick} className="form__input" />
<span className="form__faux-input"></span>
<span className="form__label">{name}</span>
</label>
)
}
}
function Sidebar({ categories }) {
return (
<div className="sidebar">
<div className="controls">
<div className="filter">
<h2 className="filter__heading">Filter By Category</h2>
<form className="filter-form">
{!categories
? <Spinner />
: categories.map((item) => (
<Checkbox key={item} name={item} />
))
}
<div className="form__group">
<button className="btn btn--rectangle btn--green">
<span className="btn-wrapper">Reset</span>
</button>
</div>
</form>
</div>
</div>
</div>
);
}
class App extends Component {
state = {
books: null,
categories: null
}
async componentDidMount() {
const { books, categories } = await getBooks();
this.setState(() => ({
books: books,
categories: categories
}));
}
render() {
const { books } = this.state;
const { categories } = this.state;
return (
<div className="App">
<Header />
<main className="main">
<div className="uiContainer">
<Sidebar
categories={categories}
/>
{!books
? <Spinner />
: <Card books={books} />
}
</div>
</main>
</div>
);
}
}
I dont 100% understand the question, but if you want to make a section like
[x] cats
[x] dogs
[ ] rabbits // dont show rabbits
Then you can keep the selection and the result part in one react element, if you dont understand the 'lifting state up' section
the state should contain an array of objects like this:
[{
allow: true,
title: 'cat'
},
{allow: false, title: 'rabbit'}]
To update the list use something like this:
this.state.map(({title, allow}) => (
<div>
<TickBox onClick={() => this.toggleAnimal(title)} value={allow}/>
<p>{animalName}</p>
</div>
)
toggleAnimal function should find the animal using the title, and update the state
Then you can filter out all the not allowed animals
this.state
.filter(animal => animal.allowed)
.map(a => <p>{a.title}</p>)
lifting state up
At this point you have 1 component, and the render function looks like this:
<h1>Please select the animals</h1>
{
animals.map(_ => <div><tickbox onClick={() => this.handleToggle(title)} /><title></div>)
}
<h1>Here are the filtered animals</h1>
{
animals.filter(a => a.allow).map(animal => animal.title).map(/* to JSX */)
}
It would be prettier and more responsive if the root component would look like this:
render () {
<SelectAnimals toggle={handleToggle} animals={this.state} />
<ShowFilteredAnimals animals={this.state} />
}
handleToggle (title) {
this.setState(...)
}
As so can see, the SelectAnimals gets a function as an argument, it can communicate with it's parent, by calling props.toggle (with the title as argument)
So SelectAnimals would look like this:
props.animals.map(animal => (
<div>
<TickBox onClick=(() => {props.toggle(animal.title)}) /> // HERE
<p>{animal.title}</p>
</div>
))
So when the tick-box fires a click event, it calls an arrow func. that calls props.toggle function with the title
In the parent of SelectAnimals, the parent element binds a handler function to SelectAnimals.toggle like this:
handleToggle (title) { // the child element called this function, it just got copied
}
PS: I made some renames in my code the handleToggle function can be the same as toggleAnimals
The parent component App needs to be able to tell Card what the selected category is, assuming Card is where the list renders.
To do that, you can:
1) create a callback function inside <App>:
_setCurrentCategory(selection) {
this.setState({currentCategory: selection})
}
2) pass it to <Checkbox /> as a prop and use it in an onChange:
class Checkbox extends Component {
render() {
const {name, setCurrentCategoryCallback } = this.props
return (
<label className="form__group">
<input
type="checkbox"
onChange={() => setCurrentCategoryCallback(name)}
className="form__input"
/>
<span className="form__faux-input"></span>
<span className="form__label">{name}</span>
</label>
)
}
}
.. this will change the state in the parent so that you can then
2) then pass the state from <App /> to <Card />:
<Card
currentCategory={this.state.currentCategory}
books={books}
/>
^^ assuming that this is where the filtered list will render. Inside the Card component, you can filter/order then render the list as you please since it now has both the list of books, and the currently selected category.
This is very loosely coded, but hopefully you get the idea!
also, when deconstructing you don't need to do this:
const { books } = this.state;
const { categories } = this.state;
you can instead do this: const { books, categories} = this.state since they are both coming from state :)

How to disable button in React.js

I have this component:
import React from 'react';
export default class AddItem extends React.Component {
add() {
this.props.onButtonClick(this.input.value);
this.input.value = '';
}
render() {
return (
<div className="add-item">
<input type="text" className="add-item__input" ref={(input) => this.input = input} placeholder={this.props.placeholder} />
<button disabled={!this.input.value} className="add-item__button" onClick={this.add.bind(this)}>Add</button>
</div>
);
}
}
I want the button to be disabled when input value is empty. But the code above doesn't work. It says:
add-item.component.js:78 Uncaught TypeError: Cannot read property 'value' of undefined
pointing to disabled={!this.input.value}. What can I be doing wrong here? I'm guessing that perhaps ref isn't created yet when render method is executed. If, so what is the workararound?
Using refs is not best practice because it reads the DOM directly, it's better to use React's state instead. Also, your button doesn't change because the component is not re-rendered and stays in its initial state.
You can use setState together with an onChange event listener to render the component again every time the input field changes:
// Input field listens to change, updates React's state and re-renders the component.
<input onChange={e => this.setState({ value: e.target.value })} value={this.state.value} />
// Button is disabled when input state is empty.
<button disabled={!this.state.value} />
Here's a working example:
class AddItem extends React.Component {
constructor() {
super();
this.state = { value: '' };
this.onChange = this.onChange.bind(this);
this.add = this.add.bind(this);
}
add() {
this.props.onButtonClick(this.state.value);
this.setState({ value: '' });
}
onChange(e) {
this.setState({ value: e.target.value });
}
render() {
return (
<div className="add-item">
<input
type="text"
className="add-item__input"
value={this.state.value}
onChange={this.onChange}
placeholder={this.props.placeholder}
/>
<button
disabled={!this.state.value}
className="add-item__button"
onClick={this.add}
>
Add
</button>
</div>
);
}
}
ReactDOM.render(
<AddItem placeholder="Value" onButtonClick={v => console.log(v)} />,
document.getElementById('View')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='View'></div>
In HTML,
<button disabled/>
<button disabled="true">
<button disabled="false">
<button disabled="21">
All of them boils down to disabled="true" that is because it returns true for a non-empty string.
Hence, in order to return false, pass a empty string in a conditional statement like this.input.value ? "true" : "".
render() {
return (
<div className="add-item">
<input
type="text"
className="add-item__input"
ref={(input) => this.input = input}
placeholder={this.props.placeholder}
/>
<button
disabled={this.input.value ? "true" : ""}
className="add-item__button"
onClick={this.add.bind(this)}
>
Add
</button>
</div>
);
}
Here is a functional component variety using react hooks.
The example code I provided should be generic enough for modification with the specific use-case or for anyone searching "How to disable a button in React" who landed here.
import React, { useState } from "react";
const YourComponent = () => {
const [isDisabled, setDisabled] = useState(false);
const handleSubmit = () => {
console.log('Your button was clicked and is now disabled');
setDisabled(true);
}
return (
<button type="button" onClick={handleSubmit} disabled={isDisabled}>
Submit
</button>
);
}
export default YourComponent;
There are few typical methods how we control components render in React.
But, I haven't used any of these in here, I just used the ref's to namespace underlying children to the component.
class AddItem extends React.Component {
change(e) {
if ("" != e.target.value) {
this.button.disabled = false;
} else {
this.button.disabled = true;
}
}
add(e) {
console.log(this.input.value);
this.input.value = '';
this.button.disabled = true;
}
render() {
return (
<div className="add-item">
<input type="text" className = "add-item__input" ref = {(input) => this.input=input} onChange = {this.change.bind(this)} />
<button className="add-item__button"
onClick= {this.add.bind(this)}
ref={(button) => this.button=button}>Add
</button>
</div>
);
}
}
ReactDOM.render(<AddItem / > , document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
You shouldn't be setting the value of the input through refs.
Take a look at the documentation for controlled form components here - https://facebook.github.io/react/docs/forms.html#controlled-components
In a nutshell
<input value={this.state.value} onChange={(e) => this.setState({value: e.target.value})} />
Then you will be able to control the disabled state by using disabled={!this.state.value}
very simple solution for this is by using useRef hook
const buttonRef = useRef();
const disableButton = () =>{
buttonRef.current.disabled = true; // this disables the button
}
<button
className="btn btn-primary mt-2"
ref={buttonRef}
onClick={disableButton}
>
Add
</button>
Similarly you can enable the button by using buttonRef.current.disabled = false
this.input is undefined until the ref callback is called. Try setting this.input to some initial value in your constructor.
From the React docs on refs, emphasis mine:
the callback will be executed immediately after the component is mounted or unmounted
I have had a similar problem, turns out we don't need hooks to do these, we can make an conditional render and it will still work fine.
<Button
type="submit"
disabled={
name === "" || email === "" || password === ""
}
fullWidth
variant="contained"
color="primary"
className={classes.submit}>
SignUP
</Button>

Categories

Resources