Create a custom radio button using React JS - reactjs

I'm trying to create a custom radio button. The issue that i'm facing is that i'm unable to uncheck the radio button when another radio button is clicked. Currently it behaves like a checkbox.
import {React, ReactDOM} from '../../shared/lib/react';
export default class RadioButton extends React.Component {
constructor(props) {
super(props);
this.state = {
checkedRadioBtn: false
};
this.toggleRadioBtn = this.toggleRadioBtn.bind(this);
};
toggleRadioBtn(){
this.setState({checkedRadioBtn: !this.state.checkedRadioBtn});
};
render() {
return (
<div className="radio-btn-group">
<div onClick={this.toggleRadioBtn} className={this.state.checkedRadioBtn ? "radiobtn checked" : "radiobtn unchecked"} data-value={this.props.value}></div>
<label>{this.props.text}</label>
</div>
);
}
};

You need to have container for group of radio buttons. That container will maintain the state for all the radio buttons and handle check/uncheck for each option. Here is the sample code for that,
import React from 'react'
import ReactDOM from 'react-dom'
class RadioBtn extends React.Component{
constructor(props) {
super(props);
}
handleClick(){
this.props.handler(this.props.index);
}
render() {
return (
<div className="radio-btn-group" onClick={this.handleClick.bind(this)}>
<div className={this.props.isChecked ? "radiobtn checked" : "radiobtn unchecked"} data-value={this.props.value}></div>
<label>{this.props.text}</label>
</div>
);
}
}
class RadioGrp extends React.Component{
constructor() {
super();
this.state = {
selectedIndex: null,
selectedValue: null,
options: ["option 0","option 1","option 2","option 3"]
};
}
toggleRadioBtn(index){
this.setState({
selectedIndex: index,
selectedValue: this.state.options[index],
options: this.state.options
});
}
render() {
const { options } = this.state;
const allOptions = options.map((option, i) => {
return <RadioBtn key={i} isChecked={(this.state.selectedIndex == i)} text={option} value={option} index={i} handler={this.toggleRadioBtn.bind(this)} />
});
return (
<div>{allOptions}</div>
);
}
}
var app = document.getElementById('app');
ReactDOM.render(<RadioGrp />, app);

Since you're using a div for a custom checkbox that doesn't behave like a normal checkbox you should be checking value against the selected value.
toggleRadioBtn(e){
this.setState({checkedRadioBtn: e.target.value});
};
Another question that I have is that you are assuming a single checkbox here so I have to assume you have a calling component that returns multiple instances. If that is the case then you need to pass your onClick down so you can pass the value back up to the parent. Then pass the selected value back down.
This is an example that I have in my application.
var languges = this.props.languages;
var languageToSelect = this.state.selectedLanguage;
var handleChange = this.handleChange;
var languageRows = Object.keys(languges).map(function(key) {
var language = languges[key];
return <LanguageBlock
key={ key }
language={ language }
languageCode={ key }
checked={ languageToSelect === key }
handleChange={ handleChange }
/>;
});
In my use case I have multiple languages and onChange I pass the selected language back up then on rerender the selected language will be updated so the radio options will reflect the change.
handleChange: function handleChange(event) {
this.setState({ selectedLanguage: event.target.value });
},
The handle change just sets state for the new value. The language block itself is just a simple component so no need to make it a class/component.
const LanguageBlock = ({ checked, language, languageCode, handleChange }) => {
return (
<div className="truncification">
<input type="radio" name="lang" id={ 'lang_' + languageCode }
value={ languageCode } checked={ checked } onChange={ (evt) => { handleChange(evt); } } />
<label htmlFor={ 'lang_' + languageCode }>{ language }</label>
</div>
);
};

Related

React not rerendering the component when prop changes

I have a picklist component that render some children based on the selected option. The problem comes when I render the same component in two options but with different props, because in that case, the component is not rerendered with the new props.
Let me clarify the problem: I have a picklist, I select option "A", then a text component is rendered below the picklist, I type "error" in that text field, then select option "B" in the picklist, then the other text field component disappear and another text field component is rendered just below the picklist. The last component should have been rendered empty, but the problem is that it contains the word "error".
Here's a minimized version of the code reproducing the error:
import React from "react";
class TextField extends React.Component {
constructor(props) {
super(props);
this.state = { value: props.value };
}
_onChange = (event) => {
this.setState({ value: event.currentTarget.value });
};
_handleBlur = (event) => {
this.setState({ value: event.currentTarget.value.trim() });
};
render() {
const { value } = this.state;
return (
<div>
<label>TextField</label>
<input
type="text"
onChange={this._onChange}
onBlur={this._handleBlur}
value={value}
/>
</div>
);
}
}
class Picklist extends React.Component {
constructor(props) {
super(props);
this.state = { selectedOption: "" };
this.options = ["Blank", "A", "B"];
}
// eslint-disable-next-line consistent-return
_handleSelectorCallback = (newOption) => {
this.setState({ selectedOption: newOption.currentTarget.value });
};
_renderChildren(selectedOption) {
if (!selectedOption) {
return null;
}
if (selectedOption === "A") {
return <TextField value="optionA"/>;
}
if (selectedOption === "B") {
return <TextField value="optionB"/>;
}
}
_renderOptions() {
const { selectedOption } = this.state;
return this.options.map((option) => (
<option value={option} selected={selectedOption === option}>
{option}
</option>
));
}
render() {
const { selectedOption } = this.state;
return (
<React.Fragment>
<div>
<label>Demo of the Error!</label>
<select
onChange={this._handleSelectorCallback}
value={selectedOption}
>
{this._renderOptions()}
</select>
</div>
{this._renderChildren(selectedOption)}
</React.Fragment>
);
}
}
export default Picklist;
Ignore how bad the component is written, is just to reproduce the error I'm having. Any idea why the component is not being rerendered with a new value?
I think what's happening is, when you switch from one <TextField> to the other, React is trying to be efficient by just passing different props to the same instance. You can tell React they are different and should be rerendered by adding a key:
_renderChildren(selectedOption) {
if (!selectedOption) {
return null;
}
if (selectedOption === "A") {
return <TextField key="A" value="optionA" />;
}
if (selectedOption === "B") {
return <TextField key="B" value="optionB" />;
}
}
alternatively, you could make your TextField a controlled component, which means it has no internal state, and the value/onChange fn are passed in as props. I edited your codesandbox to follow this pattern: https://codesandbox.io/s/empty-wind-43jq4?file=/src/App.js
I tried your sandbox. Your TextField component does not get unmounted so it's constructor gets called just once. Every change you make that has to do with that component, goes to it's componentDidUpdate hook.
So this is what you are missing:
componentDidUpdate() {
if (this.state.value !== this.props.value) {
this.setState({ value: this.props.value });
}
}

React - selected value from list is undefined

I have a React component SelectObj which gets data from my api and passes it to a component DynamicSelect. The DynamicSelect creates a selection list. When the user selects the option, the selected value should be returned to SelectObj.
DynamicSelect.js:
export class DynamicSelect extends Component {
constructor(props) {
super(props);
}
handleChange = event => {
let selectedValue = event.value;
this.props.onSelectChange(selectedValue);
};
render() {
let arrayOfData = this.props.arrayOfData;
let options = arrayOfData.map(data => (
<option key={data.id} value={data.objective_name}>
{data.objective_name}
</option>
));
//console.log(options);
return (
<select
name="customSearch"
className="custom-search-select"
onChange={this.handleChange}
>
<option>Select item</option>
{options}
{console.log(options)}
</select>
);
}
}
export default DynamicSelect;
SelectObj.js
import { getObjectives } from "../../actions/assessments";
import { DynamicSelect } from "./DynamicSelect";
export class SelectObj extends Component {
static PropTypes = {
objectives: PropTypes.array.isRequired,
getObjectives: PropTypes.array.isRequired
};
constructor(props) {
super(props);
this.state = {
selectedValue: "Nothing selected"
};
}
handleSelectChange = selectedValue => {
this.setState({
selectedValue: selectedValue
});
};
componentDidMount() {
this.props.getObjectives();
}
render() {
let arrayOfData = this.props.objectives;
return (
<Fragment>
<DynamicSelect
arrayOfData={arrayOfData}
onSelectChange={this.handleSelectChange}
/>
{console.log(this.state.selectedValue)}
<div>Selected Value: {this.state.selectedValue}</div>
</Fragment>
);
}
}
const mapStateToProps = state => ({
objectives: state.objectives.objectives
});
export default connect(mapStateToProps, { getObjectives })(SelectObj);
The console.log in DynamicSelect prints out the options that I expect, whereas the console.log in SelectObj is undefined. How do I get this selected value from DynamicSelect?
I thought I maybe have to bind my handleChange with this.handleChange = this.handleChange.bind.(this) but that didn't fix this problem (maybe I still should be binding the handleChange?).
<DynamicSelect
arrayOfData={arrayOfData}
onSelectChange={(e) => this.handleSelectChange(e)}
/>
Try to pass your function like this and see whether the result is getting updated.

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 - 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.

Only change text on a new click, instead of real time in React

I am trying to keep my output text only show when I the new search button is clicked.
Right now, I have a results bar that show the search term that a user is looking for, initially is hidden, but when a search is entered you see the results bar with the term.
the problem is as users enter a new search, the text on the result bar changes, and what I would like to do, is to only have the text change when they click the search button again.
seems simple in premise, but I am having a hard time figuring it out.
class App extends Component {
constructor(props){
super(props);
this.state = {
searchedTerm: '',
}
}
handleChange = event => {
this.setState({
...this.state,
searchedTerm: event.target.value,
})
}
<ProductList
list={list}
term={searchedTerm}
hideResultsBar={hideResultsBar} />
//different component
export default props => {
const { hideResultsBar, term, list } = props;
return(
<div>
{
hideResultsBar ? null : <SearchResultBar term={term} />
}
Instead of using an onChange event, you'll need to move your setState logic into a submit handler when you click the search icon or press enter in the search field. You'll also need to assign a ref to the search input so that you can grab the value from it:
handleSearchSubmit = () => {
this.setState({
searchedTerm: this.searchInput.value,
})
}
render() {
return (
<div>
<input type="text" ref={ ref => this.searchInput = ref } />
</div>
)
}
You could add another property to your state for the showing and hiding of the results and whenever the text is changed it hides the results, and clicking the button shows them, e.g:
class App extends Component {
constructor(props){
super(props);
this.state = {
searchedTerm: '',
hideResultsBar: true
}
}
handleChange = event => {
//you don't need to set all items with ...this.state
//like you would with redux
this.setState({
searchedTerm: event.target.value,
hideResultsBar: true
});
}
handleSearchClick = () => {
this.setState({ hideResultsBar: false });
}
}
<ProductList
list={list}
term={searchedTerm}
hideResultsBar={hideResultsBar}
/>
export default props => {
const { term, list, hideResultsBar } = props;
return(
<div>
{
hideResultsBar ? null : <SearchResultBar term={term} />
}
</div>
);
};

Resources