Simplifying repeated code in React - javascript

It there a best practice to simplify something like the following in React?
getInitialState: function () {
return {
checkbox1: false,
checkbox2: false,
checkbox3: false,
...
};
},
selectCheckbox1: function () {
this.setState({
checkbox1: !this.state.checkbox1
});
},
selectCheckbox2: function () {
this.setState({
checkbox2: !this.state.checkbox2
});
},
selectCheckbox3: function () {
this.setState({
checkbox3: !this.state.checkbox3
});
},
...
render: function () {
return (
<div>
<input type="checkbox" checked={this.state.checkbox1} onChange={this.selectCheckbox1} />
<input type="checkbox" checked={this.state.checkbox2} onChange={this.selectCheckbox2} />
<input type="checkbox" checked={this.state.checkbox3} onChange={this.selectCheckbox3} />
...
</div>
);
}
For example, I can use an array for the state instead of individual fields and create a generic function that takes an index parameter to distinguish which checkbox to update:
const Checkboxes = React.createClass({
getInitialState: function () {
return {
checkboxes: [false, false, false, ...]
};
},
selectCheckbox: function (index) {
let checkboxes = this.state.checkboxes.slice();
checkboxes[index] = !checkboxes[index];
this.setState({
checkboxes: checkboxes
});
},
render: function () {
return (
<div>
<input type="checkbox" checked={this.state.checkboxes[0]} onChange={() => this.selectCheckbox(0)} />
<input type="checkbox" checked={this.state.checkboxes[1]} onChange={() => this.selectCheckbox(1)} />
<input type="checkbox" checked={this.state.checkboxes[2]} onChange={() => this.selectCheckbox(2)} />
...
</div>
);
}
});
I am new to React and JavaScript so I don't know exactly the tradeoffs happening behind the scenes here. Is the second an improvement over the first? Which is preferred and why?

Second one is definitely better approach then the first one. You can safely go with that.
Apart from that, you can also render the check box without repeating them each time in render function by using array map.
render: function () {
return (
<div>
{this.state.checkboxes.map((c, i) => {
return (
<input key={i} type="checkbox" checked={c} onChange={() => this.selectCheckbox(i)} />
);
})}
</div>
);
}

I would do something like this:
class App extends Component {
constructor(props) {
super(props);
this.state = {
checkboxes: {
cheese: false,
lettuce: false,
tomatoe: false
}
};
}
handleChange = e => {
const checkboxId = e.target.id;
this.setState({
checkboxes: {
...this.state.checkboxes,
[checkboxId]: !this.state.checkboxes[checkboxId]
}
});
};
render() {
return (
<div>
{Object.entries(this.state.checkboxes).map(([key, val]) => {
return (
<div key={key}>
<input
type="checkbox"
checked={val}
id={key}
onChange={this.handleChange}
/>
<label>
{key}
</label>
</div>
);
})}
</div>
);
}
}
This makes things more explicit and easier to follow and has the added benefit of not creating a new anonymous function each time you render.

I prefer having the checkboxes named (in an object, not an array), like in your first example. But that could vary by use case. Having them unnamed as an array does, as Prakash-sharma points out, provide the benefit of being able to map over them.
This is how I would do it to reduce the duplication of the callback function declarations (without storing the checkbox values in an array):
const Checkboxes = React.createClass({
getInitialState: function () {
return {
checkbox1: false,
checkbox2: false,
checkbox3: false
};
},
selectCheckbox: function (checkboxNr) {
// return a callback with the checkboxNr set
return () => {
this.setState({
[checkboxNr]: !this.state[checkboxNr]
});
}
},
render: function () {
const {checkboxes1, checkboxes2, checkboxes3} = this.state;
return (
<div>
<input type="checkbox" checked={checkboxes1} onChange={ this.selectCheckbox("checkbox1") } />
<input type="checkbox" checked={checkboxes2} onChange={ this.selectCheckbox("checkbox2") } />
<input type="checkbox" checked={checkboxes3} onChange={ this.selectCheckbox("checkbox3") } />
</div>
);
}
});

Related

Conditional rendering on select

I am pretty new to the wonderful world of React.
I have two inputs passing data through from an API that renders a list of options. And I want to send the selected inputs from those options back to the parent in the input fields to display for another search.
I have tried passing state down to them and render them them optionally with both a ternary and an if else statement in the "SearchCityList" component in several ways but I either get both lists rendered and they would have to choose between one list that is doubled to put in each input field or it only puts the selected value in one input. Would appreciate any & all suggestions Thanks!
class Form extends Component {
state = {
showComponent: false,
showComponent2: false,
};
// open/close control over SearchCity component box
openSearch = () => {
this.setState({ showComponent: true });
};
openSearch2 = () => {
this.setState({ showComponent2: true });
};
closeSearch = () => {
this.setState({
showComponent: false,
showComponent2: false
});
};
// Passed down cb function to get selected city search in selectCity component
GoingTo = (flights) => {
this.setState({ GoingTo: [flights] });
};
LeavingFrom = (flights) => {
this.setState({ LeavingFrom: [flights] });
};
render() {
return (
<div>
<form className="form-fields container">
<div className="inputs">
<h1>Search for a flight!</h1>
<div className="depart">
<input
onClick={this.openSearch}
className="flight-search"
placeholder="Leaving From"
value={this.state.LeavingFrom}
></input>
<input type="date"></input>
</div>
<div className="Returning">
<input
onClick={this.openSearch2}
className="flight-search"
placeholder="Going To "
value={this.state.GoingTo}
></input>
<input type="date" placeholder="Returning"></input>
</div>
</div>
<button>Check Flights!</button>
</form>
{this.state.showComponent || this.state.showComponent2 ? (
<SearchCity
openSearch={this.openSearch}
openSearch2={this.openSearch2}
flightSearch={this.state.flightSearch}
closeSearch={this.closeSearch}
GoingTo={this.GoingTo}
LeavingFrom={this.LeavingFrom}
onSearchSubmission={this.onSearchSubmission}
closeSearch={this.closeSearch}
/>
) : null}
</div>
);
}
}
export default Form;
class SearchCity extends Component {
state = {
LeavingFrom: "",
GoingTo: "",
search: "",
flightSearch: [],
};
// Search submission / api call
onSearchSubmission = async (search) => {
const response = await Axios.get(
{
headers: {
"
useQueryString: true,
},
}
);
// set New state with array of searched flight data sent to searchCity component
const flightSearch = this.setState({ flightSearch: response.data.Places });
};
// Callback function to send search/input to parent "Form" component
submitSearch = (e) => {
e.preventDefault();
this.onSearchSubmission(this.state.search);
};
// closeSearch callback function sent from Form component to close pop up search box when X is pressed
closeSearch = () => {
this.props.closeSearch();
};
render() {
return (
<div className="container search-list">
<form onChange={this.submitSearch}>
<i className="fas fa-times close-btn" onClick={this.closeSearch}></i>
<input
onChange={(e) => this.setState({ search: e.target.value })} //query-search api
value={this.state.search}
className="search-input"
type="text"
placeholder="Search Locations"
></input>
<div className="search-scroll">
<SearchCityList
openSearch={this.props.openSearch}
openSearch2={this.props.openSearch2}
LeavingFrom={this.props.LeavingFrom}
GoingTo={this.props.GoingTo}
flightSearch={this.state.flightSearch}
/>
</div>
</form>
</div>
);
}
}
export default SearchCity;
function SearchCityList({ flightSearch, LeavingFrom, GoingTo }) {
const renderList = flightSearch.map((flights) => {
return (
<div>
<SelectCityLeaving LeavingFrom={LeavingFrom} flights={flights} />
<SelectCityGoing GoingTo={GoingTo} flights={flights} />
</div>
);
});
return <div>{renderList}</div>;
}
export default SearchCityList;
First of all, when dealing with state, make sure you initialize in the constructor and also ensure you bind your handlers to this component instance as this will refer to something else in the handlers if you don't and you won't be able to call this.setState().
constructor(props) {
super(props); // important
state = {
// your state
};
// make sure to bind the handlers so `this` refers to the
// component like so
this.openSearch = this.openSearch.bind(this);
}

Always get previous state in React app

In relation to my previous question, I'm doing this to store the right state:
if (value === 'checkpoint')
{
if (checked1)
{
this.setState({checked1 : false})
localStorage.setObject('checked1', false)
}
else
{
this.setState({checked1 : true})
localStorage.setObject('checked1', true)
}
}
Now I have multiple checkboxes (let's say I have four). One for ALL checkboxes, and the other 3 for categories. I want my app to detect when the three category checkboxes are checked, and check the ALL checkbox automatically. When any of the category checkboxes are unchecked, the ALL checkbox will uncheck itself.
I tried this code:
if (checked1 && checked2 && checked3) {
this.setState({checkedAll: true})
} else {
this.setState({checkedAll: false})
}
But the other 3 checkbox (checked1, checked2, checked3) will always get a previous state.
How do I get the right state so that my checkedAll functions correctly?
You should rather avoid using setState() in componentDidUpdate() as it sometimes leads to bugs in your code and is considered not a good practice (e.g. https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-update-set-state.md so if you use airbnb rules to configure your eslint you'll also run into some linting warnings).
Can't you make all four checkboxes controlled inputs like so:
<input
type="checkbox"
id="ch1"
value="1"
checked={this.state.ch1}
onChange={this.onCheckboxChange}
/> Ch1
<input
type="checkbox"
id="ch2"
value="1"
checked={this.state.ch2}
onChange={this.onCheckboxChange}
/> Ch2
<input
type="checkbox"
id="ch3"
value="1"
checked={this.state.ch3}
onChange={this.onCheckboxChange}
/> Ch3
<input
type="checkbox"
id="chall"
value="1"
checked={
this.state.ch1
&& this.state.ch2
&& this.state.ch3
}
onChange={this.onCheckboxChange}
/> Ch all
And then in the onCheckboxChange (or whatever the name is) just do something like:
const { id } = e.target;
if (id === 'chall') {
if (e.target.checked) {
this.setState({
ch1: true,
ch2: true,
ch3: true,
});
return;
}
this.setState({
ch1: false,
ch2: false,
ch3: false,
});
}
this.setState({
[id]: e.target.checked,
});
I think it's more about where in the lifecycle you place this code. componentDidUpdate seems like a resonable candidate. For example:
componentDidUpdate () {
const { checked1, checked2, checked3 } = this.state
const checkedAll = checked1 && checked2 && checked3
if (checkedAll === this.state.checkedAll) {
return
}
this.setState({ checkedAll })
}
In other words, as soon as the boxes all update their state, checkAll naturally will too.
If you use this.setState({new state}, //perform some action}), whatever action is being performed will receive the most current value of state.
Copy and paste this component and view the console to see it is receiving the correct state
import React, { Component } from 'react'
class Menu extends Component {
state = {
checkAll: false,
check1: false,
check2: false,
check3: false
}
handleChange = () => {
const all = (this.salmon.checked && this.tuna.checked && this.snapper.checked)
if (all) {
this.setState({
checkAll: true,
check1: this.salmon.checked,
check2: this.tuna.checked,
check3: this.snapper.checked
}, () => {
this.all.checked = true
console.log('Most current state', JSON.stringify(this.state))
})
} else {
this.setState({
checkAll: false,
check1: this.salmon.checked,
check2: this.tuna.checked,
check3: this.snapper.checked
}, () => {
this.all.checked = false
console.log('Most current state', JSON.stringify(this.state))
})
}
}
handleAll = () => {
this.setState({
checkAll: true,
check1: true,
check2: true,
check3: true
}, () => {
this.salmon.checked = true
this.tuna.checked = true
this.snapper.checked = true
console.log('Most current state', JSON.stringify(this.state))
})
}
render() {
return (
<div>
<h1>Select Options</h1>
<form>
<span>
Select All:
<input
onChange={this.handleAll}
type="checkbox"
value={this.state.checkAll}
ref={all => this.all = all}
/>
</span>
<ul>
<li>
<input
onChange={this.handleChange}
type="checkbox"
value={this.state.check1}
ref={salmon => this.salmon = salmon}
/>
Salmon
</li>
<li>
<input
onChange={this.handleChange}
type="checkbox"
value={this.state.check2}
ref={tuna => this.tuna = tuna}
/>
Tuna
</li>
<li>
<input
onChange={this.handleChange}
type="checkbox"
value={this.state.check3}
ref={snapper => this.snapper = snapper}
/>
Snapper
</li>
</ul>
</form>
</div>
)
}
}
export default Menu

Passing functions and variables as arguments to map() in Javascript/React

I want to pass a prop called verified through each map function and I am having difficulty setting it up.
UPDATE:
Passing verified to renderContinents works, but when add a parameter to renderCountry like this:
{continent.countries.map(renderCountry(null, verified))}
My output is blank. Shouldn't this work though?
Updated code:
const renderCities = cities => {
return (
<div>
<label>
<input
onChange={onChange}
type="checkbox"/>
{cities.name}
</label>
</div>
);
};
const renderCountries = ({country, verified}) => {
console.log("came to country");
return (
<div className="city-location">
<label>
<input
onChange={onChange}
type="checkbox"/>
{country.name}
</label>
{country.cities.map(renderCities)}
</div>
);
};
function onChange(e) {
console.log('checkbox verified:', (e.target.verified));
}
class AreasComponent extends Component {
constructor(props) {
super(props);
this.state = {
};
this.renderContinents = this.renderContinents.bind(this);
}
componentWillMount() {
this.props.fetchAllAreas();
}
renderContinents(verified, continent) {
console.log("came to continent");
return(
<div className="continent-location">
<label>
<input
onChange={onChange}
type="checkbox"/>
{continent.name}
</label>
{continent.countries.map(renderCountries(null, verified))}
</div>
)
}
render() {
if (!this.props.verified || !this.props.areas) {
return <div>Loading Areas...</div>
}
return(
<div>
{this.props.areas.map(this.renderContinents.bind(this, this.props.verified))}
</div>
);
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({ fetchAllAreas, checkArea}, dispatch);
}
function mapStateToProps(state) {
return { areas: state.areas.all,
verified:state.areas.verified
};
}
export default connect(mapStateToProps, mapDispatchToProps)(AreasComponent);
My other problem is the onChange(e) function. It's global so it works when I click any checkbox, but I want to make it so that when onChange is clicked, it can take in a parameter and dispatch the action checkArea, which, to me means it has to be bound and should also be fed as a parameter. I tried this:
{this.props.areas.map(this.renderContinents.bind(this, this.props.verified, this.props.checkArea))}
but it returns a blank result. Is it possible to send a function into a map () parameter and is there a way to get renderCountry/renderCity to work with parameters?
When you .bind parameters, those parameters become the first value(s) passed to the function. You should have noticed that when looking at the console.log output.
I.e. when you do
var bar = foo.bind(this, x, y);
bar(z);
you get the values in this order:
function foo(x, y, z) {}
You have switch the order of parameters in your function:
renderContinent(checked, continent) {
// ...
}
However, you can just keep the code you have. You don't have to pass the value to renderContinents.
In order to pass it to renderContinents etc, either .bind it or put the call inside another function:
continent.countries.map(renderCountries.bind(null, verified))
// or
continent.countries.map(country => renderCountries(country, verified))
In fact, the simplest way for renderCountry/renderCity to call onChange() with checkArea action is to put them inside AreasComponent (i.e. as member functions). So they can access both onChange and checkArea.
class AreasComponent extends Component {
constructor(props) {
super(props);
this.state = {};
this.onChange = this.onChange.bind(this);
}
componentWillMount() {
this.props.fetchAllAreas();
}
onChange(e, type) {
console.log('checkbox verified:', this.props.verified);
// call checkArea() here with your parameters
this.props.checkArea(type);
}
renderCities(cities) {
return (
<div>
<label>
<input
onChange={e => this.onChange(e, 'city')}
type="checkbox"/>
{cities.name}
</label>
</div>
);
};
renderCountries(country) {
console.log("came to country");
return (
<div className="city-location">
<label>
<input
onChange={e => this.onChange(e, 'country')}
type="checkbox"/>
{country.name}
</label>
{
country.cities.map(this.renderCities)
}
</div>
);
};
renderContinents(continent) {
console.log("came to continent");
return(
<div className="continent-location">
<label>
<input
onChange={e => this.onChange(e, 'continent')}
type="checkbox"/>
{continent.name}
</label>
{
continent.countries.map(this.renderCountries)
}
</div>
)
}
render() {
if (!this.props.verified || !this.props.areas) {
return <div>Loading Areas...</div>
}
return(
<div>
{
this.props.areas.map(this.renderContinents)
}
</div>
);
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({ fetchAllAreas, checkArea}, dispatch);
}
function mapStateToProps(state) {
return {
areas: state.areas.all,
verified: state.areas.verified
};
}
export default connect(mapStateToProps, mapDispatchToProps)(AreasComponent);

react: get custom checkbox's checked attribute

I want to so some customization with checkbox, it can look like this:
so I wrap my custom checkbox into a React Component:
require('../../less/ck-checkbox.less');
var React = require('react');
var CkCheckbox = React.createClass({
propTypes: {
name: React.PropTypes.string,
text: React.PropTypes.string,
defaultChecked: React.PropTypes.bool,
onChange: React.PropTypes.func
},
getDefaultProps: function() {
return {
name: 'checkbox',
text: '',
defaultChecked: false,
onChange: function () {}
};
},
render: function() {
return (
<div className="ck-checkbox">
<label>
<input
type="checkbox"
name={this.props.name}
ref="c"
defaultChecked={this.props.defaultChecked}
onChange={this.props.onChange.bind(this, this.refs.c.checked)}
/>
<span className="icons">
<span className="icon-checked-o icon-true"></span>
<span className="icon-circle-o icon-false"></span>
</span>
{this.props.text.length > 0 ?
<span className="ck-checkbox-text">{this.props.text}</span> : null
}
</label>
</div>
);
}
});
module.exports = CkCheckbox;
and my container is like this:
var React = require('react');
var CkCheckbox = require('./CkCheckbox.js');
var Test = React.createClass({
render: function() {
return (
<div>
<CkCheckbox onChange={this._handleChange}/>
</div>
);
},
_handleChange: function(checked, e) {
console.log(checked)
}
});
module.exports = Test;
you can see that, I tried to bind this.refs.c.checked in the onChange callback, but it doesn't work.
so, how can I get the checked state of my custom checkbox in Test component in the _handleChange function?
In this case you don't need use refs because you can get checked property from e argument
// CkCheckbox component
<input type="checkbox"
name={this.props.name}
defaultChecked={this.props.defaultChecked}
onChange={ this.props.onChange } />
// Test component
_handleChange: function(e) {
var checked = e.target.checked;
console.log(checked)
}
Example
or if you want pass only checked property you can do it like this
// CkCheckbox component
handleChange: function (e) {
this.props.onChange(e.target.checked);
},
<input type="checkbox"
name={this.props.name}
defaultChecked={this.props.defaultChecked}
onChange={ this.handleChange } />
// in Test component
_handleChange: function(checked) {
console.log(checked)
}
Example
This is a simple example you can use to get the custom value or the value of your checked box, and also to check if the box is checked.
export default class SearchBoxContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
boxAll: false
};
}
handleChange = event => {
this.setState({ boxAll: event.target.checked }, () => {
console.log("This returned true or false", this.state.boxAll);
});
};
render() {
return (
<div className="search-container">
<SearchBox />
<div className="filter-country">
<h1>Filter</h1>
<div className="filter-country-container">
<div>
<input
type="checkbox"
id="checkBoxUS"
name="US"
value="US"
onChange={this.handleChange}
/>
<label htmlFor="US">All Jobs</label>
</div>
</div>
</div>
</div>
);
}
}
It is important to know that in this example, I used the "setState()" callback just for demonstration purposes, but you can pass the value to other components by props or wherever method you prefer. Also, when the checkbox is checked the value will return "true"

Working with Radio Buttons on Flux

Just started my first app in React and I want to know if there is a React way to work with Radio Buttons, I have a form with 4 radio buttons, I need to take 2 of the options selected and send that info to a backend.
class RadioBtns extends Component {
constructor (props) {
super(props);
this.state = {
greet : '',
hello : '',
};
}
render () {
return (
<div>
<div>
<form>
<input type="radio" value="first" name="greet" onChange={this._onChangeGreet}/> Option 1
<input type="radio" value="second" name="greet" onChange={this._onChangeGreet}/> Option 2
<input type="radio" value="three" name="hello" onChange={this._onChangeHello}/> Option 3
<input type="radio" value="four" name="hello" onChange={this._onChangeHello}/> Option 4
</form>
<hr />
<button type="submit" onClick={this._submitSettings}>YES!</button>
</div>
</div>
);
}
_onChangeGreet = ({ target }) => {
this.setState({
greet : target.value,
});
}
_onChangeHello = ({ target }) => {
this.setState({
hello : target.value,
});
}
_submitSettings = () => {
console.log('submit');
}
}
export default RadioBtns;
how do I send this states with the values to the stores ?
and here I have the action
#createActions(flux)
class RadioBtnsActions {
constructor () {
this.generateActions('optionSelected');
}
}
export default RadioBtnsActions;
and in the Store
import flux from 'flux';
import RadioBtnsActions from 'actions/RadioBtnsActions';
#createStore(flux)
class RadioBtnsStore {
constructor () {
this.state = {
radioSelected : false,
};
}
#bind(RadioBtnsActions.optionSelected)
optionSelected (option) {
this.setState({
radioSelected : option,
});
}
}
export default RadioBtnsStore;
Here's what we did in our project (simplified, use your imagination):
First you create a RadioButton component that renders the actual input:
render(){
<div>
<input id={this.props.id} type="radio"
name={this.props.name} value={this.props.value}
checked={this.props.checked} onChange={this.onChange}/>
<label htmlFor={this.props.id}>{this.props.label}</label>
</div>
},
onChange: function(ev){
this.props.onChange(ev.target.checked, this.props.value);
}
Then you use that to implement a RadioButtonGroup component:
render: function(){
var name = this.name, value = this.props.value, onChange = this.onSingleRadioChange;
var options = _.map(this.props.options, function(option){
var id = name + '-' + option.value;
return <RadioButton key={option.value} id={id} name={name} value={option.value} label={option.label} checked={option.value == value} onChange={onChange} />
});
return <div>{options}</div>
},
onSingleRadioChange: function(checked, value){
if(checked)
this.props.onChange(value);
}
You can use it like this:
<RadioButtonGroup name='greet' options={[{value: 'first', label: 'First'}, {value: 'second', label: 'Second'}]} onChange={val => { Actions.radioGroupChanged('greet', val);}} />
Where Actions.radioGroupChanged is the action that your store is listening on.
Don't forget to use labels for better UX.
Edit: here's a rough draft of the store, although we use Reflux, so it's a different API that what you have:
var store = Reflux.createStore({
radioGroups: {greet: 'first', hello: 'three'}, //state of the radio button groups lives here
init(){
this.listenTo(Actions.radioGroupChanged, this.onRadioGroupChanged);
},
onRadioGroupChanged(group, value){
this.radioGroups[group] = value;
this.trigger(); //this notifies the component that the store changed;
}
});
The component then listens to the store and updates its own state:
componentDidMount(){
this.listenTo(store, () => { this.setState({radios: store.groups}) });
}
render(){
return <RadioButtonGroup name='greet' value={this.state.radios.greet} .../>
}

Categories

Resources