Checkbox does not work well after resetting - reactjs

I have a checkbox component, it works well when checking and unchecking. But if I reset, then select the checkbox again, it does not respond the click and only works after a few clicks. Sometimes it throws the error message "TypeError: this.setState is not a function". What is the problem here? Thanks.
App.js
const campusData = [
{ id: 1, value:'A',name: 'A' },
{ id: 2, value:'B',name: 'B' },
{ id: 3, value:'C',name: 'C' }
]
class App extends Component{
state={checkedCampusItems:[]};
onReset=()=>{
this.setState({checkedCampusItems:[]});
handleCampusChkChange=(id,name,value, checked)=> {
const checkedCampusItems = this.state.checkedCampusItems;
let index;
// check if the check box is checked or unchecked
if (checked) {
// add the numerical value of the checkbox to checkedItems array
checkedCampusItems.push(value);
} else {
index = checkedCampusItems.indexOf(value);
checkedCampusItems.splice(index, 1);
}
// update the state with the new array of options
this.setState({checkedCampusItems: checkedCampusItems });
console.log("checkedCampusItems array",checkedCampusItems);
}
render(){
return(<div><input type="reset" value="Reset Search" onClick={this.onReset}></input>
{campusData.map(item =>
<CampusChk id="campus" key={item.id} {...item} onChange={this.handleCampusChkChange} />
)}
</div>
)
}
}
export default App;
campusChk.js
import React from 'react';
const CampusChk = ({ id, name,value, onChange }) => {
return(
<div>
<input
type="checkbox"
onChange={(event) => onChange(id,name,value, event.target.checked) }
/>
<label>{name}</label>
</div>
);
}
export default CampusChk;

Change your reset function to update the state:
onReset=()=>{
this.setState({ checkedCampusItems: []})
}
You're also mutating state. This may not be causing the problem, but you should change your temporary array to this in your change handler:
const checkedCampusItems = [...this.state.checkedCampusItems];

Related

React trying to make a list of dynamic inputs

I have built this site
https://supsurvey.herokuapp.com/surveycreate/
now I am trying to move the fronted to React so I can learn React in the process.
with vanila js it was much easier to create elements dynamically.
I just did createElement and after that when I clicked "submit" button
I loop throw all the elements of Options and take each target.value input.
so I loop only 1 time in the end when I click Submit and that's it I have now a list of all the inputs.
in react every change in each input field calls the "OnChange" method and bubbling the e.targe.value to the parent and in the parent I have to copy the current array of the options and rewrite it every change in every field.
is there other way? because it seems crazy to work like that.
Options.jsx
```import React, { Component } from "react";
class Option extends Component {
constructor(props) {
super(props);
this.state = { inputValue: "", index: props.index };
}
myChangeHandler = event => {
this.setState({ inputValue: event.target.value });
this.props.onChange(this.state.index, event.target.value);
};
render() {
return (
<input
className="survey-answer-group"
type="text"
placeholder="Add Option..."
onChange={this.myChangeHandler}
/>
);
}
}
export default Option;
______________________________________________________________________________
Options.jsx````
```import React, { Component } from "react";
import Option from "./option";
class Options extends Component {
render() {
console.log(this.props);
return <div>{this.createOptions()}</div>;
}
createOptions = () => {
let options = [];
for (let index = 0; index < this.props.numOfOptions; index++) {
options.push(
<Option key={index} onChange={this.props.onChange} index={index} />
);
}
return options;
};
}
export default Options;```
______________________________________________________________________________
App.jsx
```import React from "react";
import OptionList from "./components/Options";
import AddButton from "./components/add-button";
import "./App.css";
class App extends React.Component {
state = {
numOfOptions: 2,
options: [{ id: 0, value: "" }, { id: 1, value: "" }]
};
handleChange = (index, value) => {
const options = [...this.state.options];
console.log("from App", value);
options[index].value = value;
this.setState({
options: options
});
console.log(this.state);
};
addOption = () => {
const options = [...this.state.options];
options.push({ id: this.state.numOfOptions + 1, value: "" });
this.setState({
numOfOptions: this.state.numOfOptions + 1,
options: options
});
};
submitButton = () => {};
render() {
return (
<div className="poll-create-grid">
<div id="poll-create-options">
<OptionList
onChange={this.handleChange}
numOfOptions={this.state.numOfOptions}
/>
</div>
<button
className="surveyCreate-main-btn-group"
onClick={this.addOption}
>
Add
</button>
<button
className="surveyCreate-main-btn-group"
onClick={this.submitButton}
>
Submit
</button>
</div>
);
}
}
export default App;
```
So firstly,
The issue is with the way your OptionList component is defined.
Would be nice to pass in the options from the state into the component rather than the number of options
<OptionList
onChange={this.handleChange}
options={this.state.options}
/>
The you basically just render the options in the OptionsList component (I'm assuming it's same as the Options one here
class Options extends Component {
...
render() {
return
(<div>{Array.isArray(this.props.options) &&
this.props.options.map((option) => <Option
key={option.id}
index={option.id}
onChange={this.props.onChange}
value={option.value}
/>)}
</div>);
}
...
}
You would want to use the value in the Option component as well.
this.props.onChange(this.state.index, event.target.value); No need using the state here to be honest
this.props.onChange(this.props.index, event.target.value); is fine

React JS: Unable to display array data onClick

In the console i can see the object inside the array on click every time.
But I am unable to display it
I tried map() instead of forEach, and it shows the item just once.
I need it to show each time i click the button
Can someone tell me what I am doing Wrong?
import React, { Component } from 'react'
export default class App extends Component {
state = {
isClicked : false
}
handleClick = () => {
this.setState({
isClicked : true
})
}
render() {
let myarray = [ ]
let myobject = {
color: 'blue',
shape: 'round'
}
myarray.push(myobject)
let looped = myarray.forEach((data) => <><h1>{data.color}</h1> <h1>{data.shape}</h1></>)
console.log(myarray)
return (
<div>
<button onClick={this.handleClick}>Submit</button>
{ this.state.isClicked ? looped : null}
</div>
)
}
}
I want the object to display on the page when I click the button
There are some issues in your snippet:
forEach() has no return value.
on handleClick you change state to isClicked: true once.
In order to fix your code and display your data on every click you should:
Use map() to map each object to React.Element.
Change the state on every button click, so in your case, on every click, you should reverse the boolean value of this.state.isClicked.
Here is a working example:
import React from 'react';
const myArray = [
{
color: 'blue',
shape: 'round'
}
];
const looped = myArray.map((data, i) => (
<div key={i}>
<h1>{data.color}</h1> <h1>{data.shape}</h1>
</div>
));
export default class App extends React.Component {
state = {
isClicked: false
};
handleClick = () => {
this.setState(prevState => {
console.log(prevState);
return { isClicked: !prevState.isClicked };
});
};
render() {
return (
<div>
<button onClick={this.handleClick}>Submit</button>
{this.state.isClicked && looped}
</div>
);
}
}

React - dynamically create select all checkboxes

There has been a number of questions on React checkboxes. This answer is pretty good, and it helps to modularize the idea of select-all checkboxes in React.
Now, I have a problem. I have a dictionary like this:
{
regionOne: {
subareaOne,
subareaTwo,
subareaThree,
subareaFour
},
regionTwo: {
subareaFive,
subareaSix
}
}
Here, each region is mapped to an arbitrary number of sub areas, which I do not know beforehand.
I want to create checkboxes such that each region and each subarea has a checkbox, and each region's checkbox acts as a select-all/de-select all for all the subareas it is mapped to. That is, something like this:
So, when you click on the checkbox for regionOne, the checkboxes for subareaOne, subareaTwo, subareaThree and subareaFour should all be checked as well, but not those in regionTwo.
I think I can adapt this answer, but its getInitialState function assumes that you know how many children checkboxes there are.
Any idea on how to do this in an elegant method? I am now considering initialising the checkboxes dynamically using mapping, but I am not sure...VanillaJS would have been much simpler >.<
I actually went ahead and implemented it anyway. There are two components involved, a parent CheckboxGroup component, and a child StatelessCheckbox component.
Here is the parent component:
import React from 'react';
import { StatelessCheckbox } from './StatelessCheckbox';
export class CheckboxGroup extends React.Component {
constructor(props) {
super(props);
this.state = {
checked: false,
boxes: {},
disabled: false
};
}
componentDidMount() {
const { boxes, boxId, disabled } = this.props;
let boxesState = {};
boxes.map(box => {
boxesState[box[boxId]] = false;
});
this.setState({ checked: false, boxes: boxesState, disabled: disabled });
}
handleSelectAll(event) {
const isChecked = event.target.checked;
let boxesState = {};
Object.keys(this.state.boxes).map(box => {
boxesState[box] = isChecked;
});
this.setState({ checked: isChecked, boxes: boxesState });
}
handleSelect(event) {
const isChecked = event.target.checked;
const boxId = event.target.value;
let newBoxes = {};
Object.assign(newBoxes, this.state.boxes);
newBoxes[boxId] = isChecked;
// Check parent checkbox if all children boxes are checked
const checkedBoxes = Object.keys(newBoxes).filter((box) => {
return newBoxes[box] === true;
});
const parentIsChecked = (checkedBoxes.length === Object.keys(newBoxes).length);
this.setState({ checked: parentIsChecked, boxes: newBoxes });
}
render() {
const {
passDataToParent=(() => { return false; }),
groupClassName='',
headClassName='',
headName='',
headBoxClass='',
headLabelClass='',
headLabelText='',
bodyClassName='',
bodyName='',
bodyBoxClass='',
bodyLabelClass='',
boxes,
boxId,
boxLabel
} = this.props;
return (
<div className="row">
<div className={ groupClassName }>
<div className={ headClassName }>
<StatelessCheckbox name={ headName } className={ headBoxClass }
labelClass={ headLabelClass } labelText={ headLabelText }
checked={ this.state.checked } value={ headName }
passedOnChange={ (e) => { this.handleSelectAll(e); } } />
</div>
<div className={`row ${ bodyClassName }`}>
{ boxes.map(box => (
<div key={ box[boxId] }>
<StatelessCheckbox name={ bodyName } className={ bodyBoxClass }
labelClass={ bodyLabelClass } labelText={ box[boxLabel] }
checked={ this.state.boxes[box[boxId]] } value={ box[boxId] }
passedOnChange={ (e) => { this.handleSelect(e); } } />
</div>
))}
</div>
</div>
</div>
);
}
}
and here is the child component:
import React from 'react';
/**
* Implements a React checkbox as a stateless component.
*/
export class StatelessCheckbox extends React.Component {
constructor(props) {
super(props);
}
render() {
const {
passedOnChange=(() => { return false; }),
className='',
name='',
labelClass='',
labelText='',
value='',
checked=false,
} = this.props;
return (
<label className={`for-checkbox ${ className }`} tabIndex="0">
<input onChange={(e) => passedOnChange(e) }
checked={ checked } type="checkbox"
name={ name } value={ value } />
<span className={`label ${ labelClass }`}>{ labelText }</span>
</label>
);
}
}
Things to note:
the child component is a stateless component in this case, used purely for rendering
the parent component (CheckboxGroup) maintains the state for all the child components as well so there is still a single source of truth as per React philosophy
boxes in properties is a list of the children checkboxes in each parent CheckboxGroup, and boxes in the parent state holds the state for each child checkbox
Something like that?
onSelect(id) {
let allElms = Object.assign({}, retriveElmentsSomehow());
let elm = findTheElm(id, allElms);
updateSelected(elm, !elm.selected);
// Then update state with new allElms
}
updateSelected(elm, selectedOrNot) {
elm.selected = selectedOrNot;
if (elm.children) {
elm.children.foreach(c => onSelect(c, selectedOrNot));
}
}
Where elements are in state or redux store.

React + Redux - Trigger select onChange() on initial load

I'm new to React and Redux, so pardon me if the answer is trivial, but after an extensive search I don't seem to find a good answer to this simple question. I have multiple cascading selects where data getting populated based on the previous selection. Everything works fine when the user changes selected option. However, I can't figure out how to trigger onChange event when the data initially loaded in the first select? Here's the simplified component:
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { locationActions } from '../../_actions';
import { Input, Col, Label, FormGroup } from 'reactstrap';
class HeaderSetup extends React.Component {
constructor(props) {
debugger;
super(props);
this.state = { location: 'Select an Option'};
}
componentWillReceiveProps(nextProps) {
if (nextProps.loading !== this.props.loading &&
nextProps.success !== this.props.success &&
!nextProps.loading && nextprops.success) {
this.setState({ location: '' });
}
}
onLocationChanged(e) {
console.log(e.target.value);
}
render() {
const { locations } = this.props;
return (
<FormGroup row>
<Label for="locations" sm={3}>Locations</Label>
<Col sm={8}>
{locations.items &&
<Input type="select" name="locations" id="locations"
onChange={this.onLocationChanged}
value={this.state.location}>
{locations.items.map((location, index) =>
<option key={location.id}>
{location.locationName}
</option>
)}
</Input>
}
</Col>
</FormGroup>
)
}
}
function mapStateToProps(state) {
debugger;
const { locations } = state;
return {
locations
};
}
export default connect(mapStateToProps)(HeaderSetup);
Do I just need to trigger it manually? If so, what's the best place/way to do that? Any help is greatly appreciated!
Since you are using controlled components they should always reflect the state. In your onChange callback you should just update the state and all the inputs should update accordingly.
If you put up a minimal working example showing this issue I might be able to provide more details.
Below is a simple working example of how to set this up:
class App extends React.Component {
state = {
locations: []
};
componentDidMount() {
setTimeout(() => { // simulate loading
this.setState({
loading: false,
locations: [
{
id: 1,
label: "Paris"
},
{
id: 2,
label: "Rome"
}
]
});
}, 3000);
}
render() {
return <MyForm locations={this.state.locations} initialLocation={2}/>;
}
}
class MyForm extends React.Component {
state = {
initialLocation: null,
location: ""
};
componentWillReceiveProps(nextProps) {
this.setState({
initialLocation: nextProps.initialLocation,
})
}
onChange = e => {
this.setState({
location: e.target.value
});
};
render() {
const { locations } = this.props;
return (
<label>
<div>Select a location:</div>
{locations.length > 0 && (
<select value={this.state.location || this.state.initialLocation} onChange={this.onChange}>
{locations.map(({ id, label }) => (
<option key={id} value={id}>
{label}
</option>
))}
</select>
)}
</label>
);
}
}
ReactDOM.render(<App />, document.body);
<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>

Redux form does not reset

i have a component which based upon props renders a form with different components.
class Feedback extends Component {
submitMyForm(data) {
const { onSubmit, reset } = this.props;
reset();
return onSubmit(data);
//
// do other success stuff
}
render() {
const { handleSubmit } = this.props;
let component;
if(this.props.data.feedbackType == "likert")
component = Likert;
else if(this.props.data.feedbackType == "single choice")
component = SingleChoice;
else if(this.props.data.feedbackType == "multiple choice")
component = MultipleChoice;
return (
<div>
<h1>Feedback zu Aufgabe {this.props.id}</h1>
<form onSubmit={handleSubmit(this.submitMyForm.bind(this))}>
<Field
name="feedback"
component={component}
heading={this.props.data.description}
items={this.props.data.options}
required={true}
/>
<button type="submit">Submit</button>
</form>
</div>
);
}
}
// Decorate the form component
Feedback = reduxForm({
form: 'feedback', // a unique name for this form,
validate,
enableReinitialize:true
})(Feedback);
function validate(formProps) {
const errors = {};
if (!formProps.feedback) {
errors.feedback = 'please select an option';
}
return errors;
}
export default Feedback;
import React, { PropTypes } from 'react';
const SingleChoice = ({ input, disabled, heading, required, className, items, name, meta: { touched, error } }) => (
<fieldset className={`form__field ${className || ''}`}>
<legend className="form__label">
{heading}{required ? (<span>*</span>) : null}
{ (touched && error) ? (
<span className="form__error"> {error}</span>
) : null }
</legend>
<div>
{ items.map((item, i) => (
<div className="form__segmented-control width-1/2#small" key={ i }>
<input
{...input}
name={ name }
type="radio"
value={ item.value }
disabled={ disabled }
className="segmented-control__input u-option-bg-current"
id={ `${name}-${item.value}` }
/>
<label className="segmented-control__label u-adjacent-current" htmlFor={ `${name}-${item.value}` }>
{item.label}
</label>
</div>
))
}
</div>
</fieldset>
);
SingleChoice.propTypes = {
input: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
className: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.shape({
label: PropTypes.string.isRequired,
value: PropTypes.any.isRequired,
})).isRequired,
heading: PropTypes.string,
meta: PropTypes.object,
required: PropTypes.bool,
disabled: PropTypes.bool,
};
export default SingleChoice;
The first time the form renders everything is fine. All radio buttons are unchecked and if i try to submit it i get an validation error as intended. But when my Feeback component receives new props and the form is updated. The old values still remain selected when the form component for the new props is the same as the one for the old props.
When the form component for the new props is different all values are not selected as intended, but i can submit the form without selecting anything, which should be prevented by validation.
I hope you got any suggestions, i am totally out of ideas at this point.
I searched for hours trying to find a resolution to this problem. The best way I could fix it was by using the plugin() API to teach the redux-form reducer to respond to the action dispatched when your submission succeeds. Exactly like the first step here How can I clear my form after my submission succeeds?
const reducers = {
// ... your other reducers here ...
form: formReducer.plugin({
nameOfTheForm: (state, action) => { // <- 'nameOfTheForm' is name of form
switch(action.type) {
case ACCOUNT_SAVE_SUCCESS:
const values = undefined;
const fields = {
fields: {
input_field_name: {
visited: false,
touched: false
}
// repeat for each input field
}
};
const newState = { ...state, values, fields };
return newState;
default:
return state;
}
}
})
}
You will have to change a couple things in your component.
onSubmit(values) {
this.props.postForm(values, () => {});
}
render(){
const { handleSubmit } = this.props;
}
return (
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}></form>
)
In your actions file:
export function postForm(values, callback) {
const request = axios.get(`${ROOT_URL}`, config).then(() => callback());
return {
type: ACCOUNT_SAVE_SUCCESS,
payload: request
};
}
The best way I found:
import 'initialize'...
import {initialize} from 'redux-form';
and then when you call the action, call another action right after passing an empty object to the 'initialize' function...
yourFunction = (data) => {
this.props.dispatch(yourAction(data))
.then(
result => {
if (result) {
this.props.dispatch(initialize('nameOfTheForm', {}));
}
}
);
when my Feeback component receives new props and the form is updated, the old values still remain selected when the form component for the new props is the same as the one for the old props.
This is because the values for the feedback form are stored in your Redux store.
You should implement componentWillReceiveProps and test whether your form should be reset or not.
class Feedback extends Component {
componentWillReceiveProps ( nextProps ) {
if ( nextProps.blabla !== this.props.blabla ) {
// oh cool, props changed, let's reset the form
// checkout more available form props at http://redux-form.com/6.4.3/docs/api/ReduxForm.md/
this.props.reset();
}
}
render () {
// your normal rendering function
}
}

Resources