In my application, I need something like:
When a questions value is null then the checkbox should be shown as indeterminate, otherwise should be checked or not-checked.
But the problem is that when I update the questions, it shows me the error:
TypeError: Cannot set property 'indeterminate' of null
My questions object in state is like this:
questions: [{
id: 1,
title: 'First Question',
answers: [
{
id: 2,
title: 'Java',
value: ''
},
{
id: 3,
title: 'Python',
value: ''
},
{
id: 4,
title: '.NET',
value: true
}
]
}]
So it means that the third checkbox should be checked, and other two should be shown as indeterminate.
See picture below:
So when I click on the first one, it should become unchecked,and after clicking it again, its value should be true and should become checked. And their value will never be '' ever, except that it can be the first time.
Here's the question.jsx
import React, { Component } from 'react';
class Question extends Component {
state = {
questions: []
}
componentDidMount() {
const questions = [{
id: 1,
title: 'First Question',
answers: [
{
id: 2,
title: 'Java',
value: ''
},
{
id: 3,
title: 'Python',
value: ''
},
{
id: 4,
title: '.NET',
value: true
}
]
}, {
id: 2,
title: 'Second Question',
answers: [
{
id: 5,
title: 'MongoDB',
value: ''
},
{
id: 6,
title: 'MSSQL',
value: ''
},
{
id: 7,
title: 'MySQL',
value: ''
}
]
}, {
id: 3,
title: 'Third Question',
answers: [
{
id: 8,
title: 'ReactJs',
value: ''
},
{
id: 9,
title: 'Angular',
value: ''
},
{
id: 10,
title: 'VueJs',
value: ''
}
]
}]
this.setState({
questions
})
}
setIndeterminate = (elm, value) => {
if (value !== '') {
elm.checked = value;
elm.indeterminate = false;
}
else {
elm.checkbox = false;
elm.indeterminate = true;
}
}
handleOnChange = ({ currentTarget: checkbox }) => {
var questions = [...this.state.questions];
questions.map(p => {
p.answers.map(a => {
if (a.id == checkbox.id) {
a.value = (a.value === '') ? false : !a.value;
return;
}
})
})
this.setState({
questions
})
}
render() {
const { questions } = this.state
return (
<div>
{questions.map(question =>
<div key={question.id} className='question-wrapper'>
<div className="row">
<h6 className='text-left'>{question.title}</h6>
</div>
{question.answers.map((answer, i) =>
<div key={answer.id} className="form-group row">
<div className="form-check">
<input onChange={this.handleOnChange} ref={elm => this.setIndeterminate(elm, answer.value)} value={answer.value} className="form-check-input" type="checkbox" id={answer.id} name={answer.id} />
<label className="form-check-label" htmlFor={answer.id}>
{answer.title}
</label>
</div>
</div>
)}
</div>
)}
</div>
);
}
}
export default Question;
How is that possible of happening since as you can see I am already setting the value of intermediate to either true or false?
SOLUTION
I removed that setIndeterminate function, and did this inside ref in input element:
<input onChange={this.handleOnChange} ref={elm => {
if (elm) {
elm.checked = (answer.value !== '') ? answer.value : false;
elm.indeterminate = (answer.value === '') ? true : false;
}
}} value={answer.value} className="form-check-input" type="checkbox" id={answer.id} name={answer.id} />
I guess the problem whas that I needed to add that if (elm) to check that first.
I found this solution here (Thanks to ROBIN WIERUCH for this awesome article ) and works fine for me:
We want to extend the functionality of this checkbox for handling a tri state instead of a bi state. First, we need to transform our state from a boolean to an enum, because only this way we can create a tri state:
const CHECKBOX_STATES = {
Checked: 'Checked',
Indeterminate: 'Indeterminate',
Empty: 'Empty',
};
and now we can use it in pur component:
const Checkbox = ({ label, value, onChange }) => {
const checkboxRef = React.useRef();
React.useEffect(() => {
if (value === CHECKBOX_STATES.Checked) {
checkboxRef.current.checked = true;
checkboxRef.current.indeterminate = false;
} else if (value === CHECKBOX_STATES.Empty) {
checkboxRef.current.checked = false;
checkboxRef.current.indeterminate = false;
} else if (value === CHECKBOX_STATES.Indeterminate) {
checkboxRef.current.checked = false;
checkboxRef.current.indeterminate = true;
}
}, [value]);
return (
<label>
<input ref={checkboxRef} type="checkbox" onChange={onChange} />
{label}
</label>
);
};
Related
I have two selects and I want to populate the second select base in the selection of the first one in react. When I select a countrie I want a select2 be displayed with its states and the value on the second select be updated with the value chose.
I have the following code,
const MyForm = (props) => {
const COUNTRIES = [
{
displayValue: "Country1",
value: "C1"
},
{
displayValue: "Country2",
value: "C2"
}
]
const STATES = {
"": [ {
displayValue: "",
value: ""
}],
"C1": [{
displayValue: "State 1",
value: "S11"
},
{
displayValue: "State 2",
value: "S12"
}],
"C2": [{
displayValue: "State n1",
value: "C21"
},
{
displayValue: "STate n2",
value: "C22"
}]
}
let inputsForms = {
country: {
elementType: 'select',
elementConfig: {
type: 'select',
placeholder: '',
options: COUNTRIES,
firstOption: "-- Choose Country"
},
value: ''
},
states: {
elementType: 'select',
elementConfig: {
type: 'select',
placeholder: '',
options: [], // I need these options depend on the countrie selected STATES["C1"]
firstOption: "-- Choose States"
},
value: ''
}
}
const [myForm, setmyForm] = useState(inputsForms);
const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
};
};
const inputChangedHandler = (e, controlName) => {
const countrieValue = controlName ==="country"?e.target.value:"";
const stateOptions = myForm["states"].elementConfig;
stateOptions["options"] = STATES[countrieValue];
const updatedControls = updateObject(myForm, {
[controlName]: updateObject(myForm[controlName], {
value: e.target.value
})
});
setmyForm(updatedControls);
}
const ElementsArray = [];
for (let key in myForm) {
ElementsArray.push({
id: key,
config: myForm[key]
});
}
let form = (
<form>
{ElementsArray.map(el => (
<Input
key={el.id}
elementType={el.config.elementType}
elementConfig={el.config.elementConfig}
value={el.config.value}
changed={e => inputChangedHandler(e, el.id)}
firstOption={el.config.firstOption}
/>
))}
</form>
);
return(
<div>
{form}
</div>
);
}
export default MyForm;
The options charge on the select2, however when I select an option on the second select, the options dissappear and the value of the select2 is not updated.
Thanks.
As inputChangeHandler is getting called every input change, the data is resetting even there is change in the state. You could check for the contrieValue and set the state data so the data is not reset.
const inputChangedHandler = (e, controlName) => {
const countrieValue = controlName === "country" ? e.target.value : "";
if (countrieValue) {
const stateOptions = myForm["states"].elementConfig;
stateOptions["options"] = STATES[countrieValue];
const updatedControls = updateObject(myForm, {
[controlName]: updateObject(myForm[controlName], {
value: e.target.value
})
});
setmyForm(updatedControls);
}
}
You need to add a kind of conditional to update the state whenever the state is selected so that it does not affect the original country object.
const inputChangedHandler = (e, controlName) => {
if (countrolName === 'states') {
// your logic here
return;
}
const countrieValue = controlName === 'country' ? e.target.value : '';
const stateOptions = myForm['states'].elementConfig;
stateOptions['options'] = STATES[countrieValue];
const updatedControls = updateObject(myForm, {
[controlName]: updateObject(myForm[controlName], {
value: e.target.value,
}),
});
setmyForm(updatedControls);
};
How can I load Multi-Select dynamically?
I used react-select to implement MultiSelect.
My Efforts
In componentDidMount(), I fetched an array, which I want to display/load in my multi-select; then a response is stored in state.
Now, I tried to get value from that state, but I didn't get that value.
My Code
state= {Category: []}
// that category contain this values
//0: {categoryid: "1", categoryname: "Select Category"}
//1: {categoryid: "2", categoryname: "Abc"}
componentDidMount() {
fetch("http://myURL//file.php", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({})
})
.then(response => response.json())
.then(responseJson => {
this.setState({ Category: responseJson });
// If server response message same as Data Matched
console.log(this.state.Category);
window.parent.location = window.parent.location.href;
})
.catch(error => {
console.error(error);
});
}
//this code is not working, display nothing
<Select
closeMenuOnSelect={false}
components={animatedComponents}
isMulti
>
{this.state.Category.map((e, key) => {
return (
<option key={key} value={e.categoryid}>
{e.categoryname}
</option>
);
})}
</Select>
Please help me with this problem
react-select has options props.
<Select
closeMenuOnSelect={false}
components={animatedComponents}
options={this.state.Category.map(e => ({ label: e.categoryname, value: e.categoryid}))}
isMulti
onChange={newValue => this.setState({ selected: newValue })}
/>
How can I select values of this multi-select based on another select
component?
You can store selected values for both selects in state and filter options based on selected value.
I added quick sample with 2 dependent selects - Hospital (can have few doctors) and Doctor (can work in few hospitals).
When you select some Doctor - Hospital selection is updated and vice-versa.
Preview this code
import React, { useState } from "react";
import { render } from "react-dom";
import Select from "react-select";
const data = {
doctors: [
{
id: 1,
name: "Andrew",
hospitals: [{ id: 1, title: "Test Hospital" }, { id: 2, title: "Test2" }]
},
{
id: 2,
name: "Another",
hospitals: [{ id: 1, title: "Test Hospital" }, { id: 3, title: "Test3" }]
}
],
hospitals: [
{ id: 1, title: "Test Hospital" },
{ id: 2, title: "Test2" },
{ id: 3, title: "Test3" }
]
};
function App() {
const [selectedDoctor, setSelectedDoctor] = useState(null);
const [selectedHospital, setSelectedHospital] = useState(null);
const hospitalOption = item => ({ value: item.id, label: item.title });
const hospitalOptions = () => {
if (selectedDoctor) {
return data.doctors
.filter(doctor => doctor.id === selectedDoctor.value)[0]
.hospitals.map(hospitalOption);
} else {
return data.hospitals.map(hospitalOption);
}
};
const doctorOption = item => ({
value: item.id,
label: `Doctor ${item.name}`
});
const doctorOptions = () => {
if (selectedHospital) {
return data.doctors
.filter(
doctor =>
doctor.hospitals.filter(
hospital => hospital.id === selectedHospital.value
).length
)
.map(doctorOption);
} else {
return data.doctors.map(doctorOption);
}
};
const reset = () => {
setSelectedDoctor(null);
setSelectedHospital(null);
};
return (
<div className="App">
<h3>React-Select multi select sample</h3>
<Select
id="hospital"
value={selectedHospital}
onChange={setSelectedHospital}
options={hospitalOptions()}
selectedDoctor={selectedDoctor}
/>
<Select
id="doctor"
value={selectedDoctor}
options={doctorOptions()}
onChange={setSelectedDoctor}
selectedHospital={selectedHospital}
/>
<pre selectedDoctor={selectedDoctor} selectedHospital={selectedHospital}>
Selected Doctor: {JSON.stringify(selectedDoctor || {}, null, 2)}
<br />
Available Doctors: {JSON.stringify(doctorOptions() || {}, null, 2)}
</pre>
<pre selectedDoctor={selectedDoctor} selectedHospital={selectedHospital}>
Selected Hospital: {JSON.stringify(selectedHospital || {}, null, 2)}
<br />
Available Hospitals: {JSON.stringify(hospitalOptions() || {}, null, 2)}
</pre>
<button onClick={reset}>Reset</button>
</div>
);
}
render(<App />, document.getElementById("root"));
Imagine this variable:
let myArray = [
{
name: "animal",
value: "",
},
{
name: "fruit",
value: "",
},
(...)
];
myArray is set in stone - it is hard-coded and its length wont change, but it is a lengthy array of 10 elements. A user will only update myArray objects values via html input. Based on above, can myArray be considered as a state in Svelte?
Is below example the correct way of changing myArray state in Svelte?
(...)
myArray.forEach(element => {
if (element.name === name) element.value = value;
});
I have a button state that its disabled attribute depends on all elements in myArray having some value. Can I use Sveltes $: btnIsDisabled reactive statements to achieve that and how?
<button type="submit" disabled={btnIsDisabled}>
Submit me
</button>
I'm assuming you plan on using your array as the component-state. And that you have an input corresponding to each field.
Try something like this: https://codesandbox.io/s/magical-fog-tfq3q
class App extends React.Component {
state = {
favorites: [
{ name: "animal", value: "" },
{ name: "city", value: "" },
{ name: "song", value: "" },
{ name: "place", value: "" },
{ name: "food", value: "" },
{ name: "sport", value: "" }
],
emptyFields: null
};
handleOnChange = event => {
const { favorites } = this.state;
let updatedArr = favorites.map(favorite => {
if (favorite.name === event.target.name) {
return {
...favorite,
value: event.target.value
};
} else {
return favorite;
}
});
let emptyFields = updatedArr.filter(favorite => {
return favorite.value.length === 0;
});
this.setState({
...this.state,
favorites: updatedArr,
emptyFields: emptyFields
});
};
createFavoriteInputs = () => {
const { favorites } = this.state;
return favorites.map(favorite => {
return (
<div key={favorite.name}>
<label>{favorite.name} :</label>
<input
value={favorite.value}
name={favorite.name}
onChange={this.handleOnChange}
/>
</div>
);
});
};
render() {
const { emptyFields } = this.state;
return (
<div>
{this.createFavoriteInputs()}
<button
disabled={!emptyFields || emptyFields.length > 0 ? true : false}
>
Submit
</button>
{!emptyFields ||
(emptyFields.length > 0 && (
<div>
The following fields are required:
<ul>
{this.state.emptyFields.map(field => {
return <li key={field.name}>{field.name}</li>;
})}
</ul>
</div>
))}
</div>
);
}
}
So now with the emptyFields state, we have a button that is disabled if there are any emptyFields.
handleOnChange() helps us navigate the right state-value to update in our array, creating a new array in our state whenever we make an update to one of the inputs on the form.
I have multipage form and I want to set checked fields if I go back, I figured out how to do it but it doesn't work as I expected.
I find the elements I need to check, assign the attribute but nothing happens on UI, checked property is there in Chrome devtools
Here is the code:
if (Object.keys(this.props.data).length > 0) {
const elements = this.formRef.current.elements;
const IDs = ['1', '2', '3', '4', '5', '6']
IDs.map(id => {
if (elements[id]) {
if (Array.from(elements[id]).length > 1) {
let radio = Array.from(elements[id]).find(el => el.value == this.props.data.subData[id])
let r = ReactDOM.findDOMNode(radio);
r.checked = true;
} else {
if (id === 1) {
this.setState({
dropdownValue: this.props.data.subData[id]
})
}
let r = ReactDOM.findDOMNode(elements[id]);
r.value = this.props.data.subData[id]
}
}
})
}
}
Here is a component:
<Radio
label='...'
desc='...'
id='...'
name='...'
options={[
{ id: '...', label: '...', value: '...', name: '...' },
{ id: '...', label: '...', value: '...', name: '...' }]}
/>
That returns this:
<div className='form-input-group'>
<label>{label} <span>{desc}</span></label>
{options.map((option, index) => (
<RadioItem
key={index}
option={option}
defaultValue={this.state.value}
onChange={this.handleChange}
/>
))}
</div>
I'm hoping to render a button if this.state.currentlySelected >= 1
The operator can be found be found below
import React from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import update from 'immutability-helper'
import { nextSlide } from '../../../../actions'
import Slide from '../../../../components/Slide'
import Topic from '../../../../components/Topic'
import css from './index.scss'
// Universal slide styles
import { leftPanel, rightPanel, panelGuide } from '../../index.scss'
class TopicSlide extends React.Component {
constructor(props) {
super(props)
this.state = {
topics: {
data: [
{ id: 0, name: 'Expressions, equations and functions' },
{ id: 1, name: 'Real numbers' },
{ id: 2, name: 'Solving linear equations' },
{ id: 3, name: 'Linear functions' },
{ id: 4, name: 'Factoring and polynomials' },
{ id: 5, name: 'Linear equations' },
{ id: 6, name: 'Linear inequalitites' },
{ id: 7, name: 'Systems of linear equations and inequalities' },
{ id: 8, name: 'Exponents and exponential functions' },
{ id: 9, name: 'Quadratic equations' },
{ id: 10, name: 'Radical expressions' },
{ id: 11, name: 'Rational expressions' },
],
maxSelectable: 1,
currentlySelected: 0, // count of how many are selected
},
}
}
selectTopic = (id) => {
if (this.state.topics.currentlySelected < this.state.topics.maxSelectable) {
this.setState((state) => {
const newTopics = update(state.topics, {
data: { [id]: { selected: { $set: true } } },
})
newTopics.currentlySelected = state.topics.currentlySelected + 1
return { topics: newTopics }
})
}
}
unselectTopic = (id) => {
this.setState((state) => {
const newTopics = update(state.topics, {
data: { [id]: { selected: { $set: false } } },
})
newTopics.currentlySelected = state.topics.currentlySelected - 1
return { topics: newTopics }
})
}
render() {
return (
<Slide className={css.topicslide}>
<div className={leftPanel}>
<div className={panelGuide}>
<h2 className={css.topicslide__info__header}>
Begin by selecting <i>Factoring and Polynomials</i> as your topic.
</h2>
{
this.state.currentlySelected >= 1
&& (
<button
className={css.topicslide__info__continue}
onClick={() => this.props.dispatch(nextSlide())}
>
Continue
</button>
)
}
</div>
</div>
<div className={rightPanel}>
<div className={css.topicslide__topics}>
<h3 className={css.topicslide__topics__header}>Algebra 1 Topics</h3>
<ul className={css.topicslide__topics__list}>
{this.state.topics.data.map(topic => (
<li key={topic.id}>
<Topic
id={topic.id}
selected={!!topic.selected}
onSelect={this.selectTopic}
onUnselect={this.unselectTopic}
>
{topic.name}
</Topic>
</li>
))}
</ul>
</div>
</div>
</Slide>
)
}
}
TopicSlide.propTypes = {
dispatch: PropTypes.func.isRequired,
}
export default connect()(TopicSlide)
Nothing renders when this.state.currentlySelected increases to 1
No errors are thrown however, and I suspect it has something to do with my use of this.state
Am I correct for assuming this?
According to your code, the currentlySelected property belongs to state.topics, not to the state root itself. So
{
this.state.currentlySelected >= 1
&& (
<button
className={css.topicslide__info__continue}
onClick={() => this.props.dispatch(nextSlide())}
>
Continue
</button>
)
}
should be
{
this.state.topics.currentlySelected >= 1
&& (
<button
className={css.topicslide__info__continue}
onClick={() => this.props.dispatch(nextSlide())}
>
Continue
</button>
)
}
It will be better if it's like this
{this.state.topics.currentlySelected >= 1 ? (
<button
className={css.topicslide__info__continue}
onClick={() => this.props.dispatch(nextSlide())}
>
Continue
</button>
) : null}