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

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);

Related

Can I decouple boolean state in React component

Like below simple react component code:
class Test extends React.Component {
constructor(props){
super(props)
this.c1 = this.c1.bind(this);
this.c2 = this.c2.bind(this);
this.state = {
a:false,
b:false
}
}
c1(e) {
this.setState({a:true, b:false})
}
c2(e) {
this.setState({a:false, b:true})
}
render() {
return (
<div>
<div>
<input name="n" type="radio" onChange={this.c1} />
<input name="n" type="radio" onChange={this.c2} />
</div>
<div>
{
this.state.a && "aa"
}
{
this.state.b && "bb"
}
</div>
</div>
)
}
}
The code simply switch displaying 'aa' or 'bb' while click the radio button. But if I add a new radio button showing 'cc' to achieve the same function. I should:
Add new state like 'c'
Add new input HTML and implement its callback
setState 'c' in this callback
All of those is ok, But I have to change the 'c1','c2' function that make my code coupling like:
class Test extends React.Component {
constructor(props){
super(props)
this.c1 = this.c1.bind(this);
this.c2 = this.c2.bind(this);
this.c3 = this.c3.bind(this);
this.state = {
a:false,
b:false,
c:false,
}
}
c1(e) {
this.setState({a:true, b:false, c:false}) // add 'c:false' which coupled
}
c2(e) {
this.setState({a:false, b:true, c:false}) // add 'c:false' which coupled
}
c3(e) {
this.setState({a:false, b:false, c:true})
}
render() {
return (
<div>
<div>
<input name="n" type="radio" onChange={this.c1} />
<input name="n" type="radio" onChange={this.c2} />
<input name="n" type="radio" onChange={this.c3} />
</div>
<div>
{
this.state.a && "aa"
}
{
this.state.b && "bb"
}
{
this.state.c && "cc"
}
</div>
</div>
)
}
}
I think this situation is very common in React. So I want decoupling my code no matter how many radio buttons I add. I do not need to change the code just add code to satisfy the 'Open Closed Principle'.
Do you have any recommendation? Thanks in advance.
I think you can do like this
class Test extends React.Component {
constructor(props){
super(props)
this.change = this.change.bind(this);
this.state = {
a:false,
b:false,
c:false,
}
}
change = statename => e => {
this.setdefault();
this.setState({
[statename]: true
});
};
setdefault(){
this.setState({
a:false,
b:false,
c:false,
});
}
render() {
return (
<div>
<div>
<input name="n" type="radio" onChange={this.change("a")} />
<input name="n" type="radio" onChange={this.change("b")} />
<input name="n" type="radio" onChange={this.change("c")} />
</div>
<div>
{
this.state.a && "aa"
}
{
this.state.b && "bb"
}
{
this.state.c && "cc"
}
</div>
</div>
)
}
}
The way that you are storing the state as keyed boolean values doesn't feel right to me given that the properties are codependent rather than independent.
Right now, you have four options for state:
{ a:false, b:false, c:false } // initial state from constructor
{ a:true, b:false, c:false } // from c1
{ a:false, b:true, c:false } // from c2
{ a:false, b:false, c:true } // from c3
Based on your setState callbacks, no more than 1 property can be true at a time.
If each property was independent, you would have 8 options (2^3) and this setup would make sense.
As it is, I recommend that you instead just store which of the values is the one that is true. You can check if an individual option is true by seeing if it matches the stored selected value.
You want your Component to work no matter how many buttons you have, so let's pass the button options as props. In terms of design patterns, this is "dependency injection". We can create a SelectOne that doesn't need to know what its options are. Here I am just expecting the options to be a string like "aa" or "bb" but you can refactor this to take an object with properties label and value.
We want to loop through the array of options and render each one. Sometimes you see this extracted into a method of the component, like this.renderOption(i), but you can also do the mapping inline inside your render().
You aren't actually using the event e in your callbacks. You could use the event to get e.target.value, assuming that you are setting the value property on your input (see MDN docs regarding the HTML markup). You could also pass the value to the callback by creating an anonymous arrow function for onChange which calls it with the correct value. I'm doing that.
Here it as a class component, since that's what you were using previously.
class SelectOne extends React.Component {
constructor(props) {
super(props);
this.state = {
selected: undefined // this.state.selected will initially be undefined
}
}
render() {
return (
<div>
<div>
{this.props.options.map((optionName) => (
<label htmlFor={optionName}>
{optionName}
<input
type="radio"
id={optionName}
name="n"
value={optionName}
checked={this.state.selected === optionName}
onChange={() => this.setState({selected: optionName})}
/>
</label>
))}
</div>
{this.state.selected !== undefined && (
<h2>Selected: {this.state.selected}</h2>
)}
</div>
);
}
}
Here it is as a function Component. This is the newer syntax, so if you are just learning then I recommend learning with function components and hooks. Destructring the props makes it really easy to accept optional props and set their default value. I'm allowing the name property of the input to be passed in here, but defaulting to your "n" if not provided.
const SelectOne = ({options, name = "n"}) => {
// will be either a string or undefined
const [selected, setSelected] = React.useState(undefined);
return (
<div>
<div>
{options.map((optionName) => (
<label htmlFor={optionName}>
{optionName}
<input
type="radio"
id={optionName}
name={name} // from props
value={optionName}
checked={selected === optionName}
onChange={() => setSelected(optionName)}
/>
</label>
))}
</div>
{selected !== undefined && (
<h2>Selected: {selected}</h2>
)}
</div>
);
}
Either way, you would call it like this:
<SelectOne options={["aa", "bb", "cc"]} />
You could also create a specific components for a given set of options, which you can now call with no props.
const SelectABC = () => <SelectOne options={["aa", "bb", "cc"]} />;
<SelectABC/>

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);
}

this inside react class component works differently

I am new to React and if I try to get access to openm2() using this.openm2() in openm method then I got error
"Cannot read property 'openm2' of undefined"
class Car extends React.Component {
openm2() {
return "Hello from openm2";
}
openm(e) {
e.preventDefault();
this.openm2(); Here I get error
}
render() {
return (
<div>
<h1>
{this.props.type.map((item, index) => {
return <p key={index}>{item}</p>;
})}
</h1>
<form onSubmit={this.openm}>
<input type="text" name="type" />
<button>Remo all</button>
</form>
</div>
);
}
}
Change your openm function to arrow function, which binds this to function automatically.
openm = (e) => {
e.preventDefault();
this.openm2(); Here I get error
}
Or, you can bind this like,
<form onSubmit={this.openm.bind(this)}>
Or, you can bind this in constructor
constructor(props){
super(props);
this.openm = this.openm.bind(this)
}
You need to bind the current this reference, so you can use the arrow function for this. please check below code
class Car extends React.Component {
openm2 = () => {
return "Hello from openm2";
}
openm = (e) => {
e.preventDefault();
this.openm2();
}
render() {
return (
<div>
<h1>
{this.props.type.map((item, index) => {
return <p key={index}>{item}</p>;
})}
</h1>
<form onSubmit={this.openm}>
<input type="text" name="type" />
<button>Remo all</button>
</form>
</div>
);
}
}

How to write "if" condition in my loop getting syntax error and update a row in list

I am stuck between this here. I want to loop and check if the props index and map index match then to change value
But then if I do the following it throws an syntax error pointing at if Please let me know whats going wrong this is going beyond
import React, {Component} from 'react';
update.js
class UpdateItem extends Component{
constructor(props){
super(props);
this.state={editedVal:''};
//this.setState({editedVal:this.props.eVal});
this.handleSubmit=this.handleSubmit.bind(this);
this.handleChange=this.handleChange.bind(this);
}
handleChange(e)
{this.setState({
editedVal:e.target.value
});
//this.props.eVal=e.target.value;
}
handleSubmit(e){
e.preventDefault();
alert( this.state.editedVal);
alert(this.props.eIndx);
this.props.list.map((item,i)=>{if(this.props.eIndx===i)
{alert("s")}else{alert("sd")}});
}
render(){
return(
<div>
<form onSubmit={this.handleSubmit}>
<input type="text" value={this.state.editedVal?this.state.editedVal:this.props.eVal} onChange={this.handleChange}/>
<input type="submit" value="update"/>
</form>
</div>
);
}
}
export default UpdateItem;
addlist.js
class AdList extends Component{
constructor(props){
super(props);
this.state={value:'',disArrList:[],editVal:'',editIndx:''}
this.handleChange=this.handleChange.bind(this);
this.handleSubmit=this.handleSubmit.bind(this);
this.delItemRow=this.delItemRow.bind(this);
this.updateItemRow=this.updateItemRow.bind(this);
this.editItemValue=this.editItemValue.bind(this);
}
editItemValue(e)
{
alert("edit");
}
delItemRow(itemName)
{this.setState({disArrList:this.state.disArrList.filter(e1=>e1!==itemName)})
;
}
updateItemRow(itemName,itemId)
{
this.setState({editVal:itemName,editIndx:itemId});
alert(itemId +"==========="+itemName);
}
handleChange(e){
this.setState({value:e.target.value});
}
handleSubmit(e){
e.preventDefault();
//alert("submit"+this.state.value);
let mycolletion=this.state.disArrList.concat(this.state.value);
this.setState({disArrList:mycolletion});
}
render(){
return(
<div>
<div className="Todo">
<form onSubmit={this.handleSubmit}>
<input type="text" value={this.state.value} onChange={this.handleChange}/>
<input type="submit" value="Add" />
</form>
</div>
<div>
<DisplayList list={this.state.disArrList} removeItem={this.delItemRow} updateItem={this.updateItemRow}/>
</div>
<div>
<UpdateItem list={this.state.disArrList} editItem={this.editItemValue} eVal={this.state.editVal} eIndx={this.state.editIndx}/>
</div>
</div>
);
}
}
export default AdList;
ifs are statements, not expressions. You'll have to use a braced arrow function to use if.
this.props.list.map((item, i) => {
if (this.props.eIndx === i) {
alert("s");
} else {
alert("sd");
}
// map should always return something,
// or you'll end up with a list of `undefined`s.
//Otherwise use `forEach`.
return 'something';
});
Use .forEachinstance of Map function, .map function returns in result as array , and .forEach just looping.
For example:
this.props.list.forEach((item, i) => {
if (//your logic) {
// to do
} else {
//stuff
}
});
If You want get Array in result use .map and You need to return something on each loop:
this.props.list.map((item, i) => {
if (//your logic) {
return // to do;
} else {
return //stuff;
}
});
You can also use filter if you have only one condition and it will also return array.
this.props.list.filter((item, i) => this.props.eindex === i ? "do something" : "else dont")
worked like charm use splice method
this.props.list.splice(checkIndex,1,this.state.editedVal);

Simplifying repeated code in React

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>
);
}
});

Categories

Resources