How do I access dynamically generated/rendered children within a React component? - javascript

First, I've got a class component (for this example, let's call it DynamicComponent) that takes in no children, but dynamically generates HTML. Example render function for DynamicComponent:
render() {
return (
<div>
<input type="text" name="first" />
<input type="text" name="last" />
</div>
);
}
In a separate/parent component (I'll call it <Form>), I'm looping through child elements and searching for elements in order to register them on a form and perform validation on them. The issue here is that when it gets to the recursive part at the bottom of the function (child.type === 'DynamicComponent'), child.props.children is undefined (to be expected, since I don't pass any children as props to the DynamicComponent). How would I go about getting the dynamically rendered fields from within this parent component that's running this getChildInputs function? Simplified function (it's an accumulation function being passed into .reduce()):
getChildInputs = (acc, child) => {
// Find form elements and make note of their validation requirements
if (
typeof child.type === 'string' &&
(child.type === 'input' || child.type === 'textarea')
) {
acc[child.props.id] = {
valid: true,
touched: false,
rules: [],
invalidRules: [],
};
if (child.props.required) {
acc[child.props.id].rules.push('required');
}
if (child.props.type === 'email') {
acc[child.props.id].rules.push('email');
}
if (typeof child.props.match !== 'undefined') {
acc[child.props.id].rules.push('match');
acc[child.props.id].match = child.props.match;
}
if (typeof child.props.maxLength !== 'undefined') {
acc[child.props.id].rules.push('maxLength');
acc[child.props.id].maxLength = child.props.maxLength;
}
}
// Make it a recursive search
if (React.isValidElement(child)) {
React.Children.toArray(child.props.children).reduce(Form.getChildInputs, acc);
}
return acc;
}
Parent () component render function:
render() {
return (
<div>
<DynamicComponent />
<AnotherChildComponent>
<input type="text" name="input1" />
<input type="text" name="input2" />
</AnotherChildComponent>
</div>
);
}
So in this case, the getChildInputs function would reduce to an array with the input1 and input2 elements, but it wouldn't pickup the first and last elements. Is there a way for me to get the actual children of an element/component, not just rely on props.children?

I think you could use ref here. Create ref in your parent component and forward it to your child. After that you can access children of child component calling ref.current.children. Here is some code example, I hope you would be able to adapt it to your particular example:
class App extends React.Component {
constructor(props) {
super(props)
this.ref = createRef();
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
console.log(this.ref.current.children)
// would console log
// HTMLCollection {0: HTMLInputElement, 1: HTMLInputElement, length: 2, item: ƒ item(), namedItem: ƒ namedItem()…}
// 0:
// <input type="text" name="first"></input>
// 1:
// <input type="text" name="last"></input>
// length: 2
// item: ƒ item() {}
// namedItem: ƒ namedItem() {}
// <constructor>: "HTMLCollection"
}
render() {
return (
<div className="App">
<Dynamic ref={this.ref} />
<button onClick={this.handleClick}>A</button>
</div>
);
}
}
const Dynamic = React.forwardRef((props, ref) => {
return (
<div ref={ref}>
<input type="text" name="first" />
<input type="text" name="last" />
</div>
)
})

Related

React error Maximum update depth exceeded

I'm doing my first few experiments with React and in this component I am calling an external API to get a list of all NBA players, filter them by the teamId which was received as a component's prop and finally render the markup of the filtered players.
One consideration is that since I call the API and get a large list I keep it in the component's state so that new calls to this component would use that state instead of calling the API again. This is not production code and I don't own the API so I do this because I was getting a "Too many requests" message since I am continously trying stuff.
Anyway, when I try this code I get the already famous:
Maximum update depth exceeded. This can happen when a component
repeatedly calls setState inside componentWillUpdate or
componentDidUpdate. React limits the number of nested updates to
prevent infinite loops.
I've looked into the markup and I don't think I am making any method calls that would cause the render method to fire again and so forth, so I am at a loss as to why this is happening.
Thank you in advance for any help.
Here's the code in question:
class Players extends Component {
nbaPlayersUrl = "https://someUrl.com";
state = {
players: null,
selectedTeamPlayers: null
};
render() {
if (this.props.teamId === null) return null;
if (this.state.players !== null) {
var selectedTeamPlayers = this.filterPlayersByTeamId(this.state.players);
var markup = this.getMarkup(selectedTeamPlayers);
this.setState({selectedTeamPlayers: markup});
} else {
this.getPlayersList();
}
return (
this.state.selectedTeamPlayers
);
}
getPlayersList() {
let api = new ExternalApi();
let that = this;
api.get(this.nbaPlayersUrl).then(r => {
r.json().then(result => {
let players = result.data.map(p => ({
id: p.id,
firstName: p.first_name,
lastName: p.last_name,
position: p.position,
heightInches: p.height_inches,
heightFeet: p.height_feet,
weightPounds: p.weight_pounds,
teamId: p.team.id
}));
that.setState({players: players});
var selectedTeamPlayers = that.filterPlayersByTeamId(players);
var markup = that.getMarkup(selectedTeamPlayers);
that.setState({selectedTeamPlayers: markup});
});
});
}
filterPlayersByTeamId(players) {
return players.filter(p => {
return p.teamId === this.props.teamId;
});
}
getMarkup(players) {
var result = players.map(p => {
<li key={p.id}>
<div>
<label htmlFor="firstName">First Name</label> <input type="text" name="firstName" value={p.firstName} readOnly></input>
</div>
<div>
<label htmlFor="lastName">Last Name</label> <input type="text" name="lastName" value={p.lastName} readOnly></input>
</div>
<div>
<label htmlFor="position">Position</label> <input type="text" name="position" value={p.position} readOnly></input>
</div>
</li>
});
return (
<ul>
{result}
</ul>
);
}
}
export default Players;
#Sergio Romero - You CAN NOT set state in a render function, as that set state will call a new render, which will set state again and call a new render, and generates an infinite loop. Your loading of the data is in the render and setting state, which is also creating infinite loops. You need to completely re-write your render to only be a view of state and props (it should never manipulate or load data). I think what you want, is more like this:
class Players extends Component {
nbaPlayersUrl = "https://someUrl.com";
static propTypes = {
teamId: PropTypes.number
};
static defaultProps = {
teamId: null
};
constructor(props) {
super(props);
this.state = {
players: null
};
}
componentDidMount() {
this.getPlayerList();
}
filterPlayersByTeamId(players, teamId) {
return players.filter(p => {
return p.teamId === teamId;
});
}
getPlayersList = () => {
let api = new ExternalApi();
api.get(this.nbaPlayersUrl).then(r => {
r.json().then(result => {
let players = result.data.map(p => ({
id: p.id,
firstName: p.first_name,
lastName: p.last_name,
position: p.position,
heightInches: p.height_inches,
heightFeet: p.height_feet,
weightPounds: p.weight_pounds,
teamId: p.team.id
}));
this.setState({players});
});
});
};
render() {
if (!this.props.teamId || !this.state.players) return null;
const selectedTeamPlayers = this.filterPlayersByTeamId(this.state.players, this.props.teamId);
return (
<ul>
{
selectedTeamPlayers.map(player => {
<li key={player.id}>
<div>
<label htmlFor="firstName">First Name</label><input type="text" name="firstName" value={player.firstName} readOnly></input>
</div>
<div>
<label htmlFor="lastName">Last Name</label><input type="text" name="lastName" value={player.lastName} readOnly></input>
</div>
<div>
<label htmlFor="position">Position</label><input type="text" name="position" value={player.position} readOnly></input>
</div>
</li>
})
}
</ul>
);
}
}
export default Players;
if State and Props change the Component Will re-render.
in your render() function:
if (this.state.players !== null) {
var selectedTeamPlayers = this.filterPlayersByTeamId(this.state.players);
var markup = this.getMarkup(selectedTeamPlayers);
// this.setState({selectedTeamPlayers:
}
Try changing commented line, every time players not null the Component state is updated therefore the component Render function will run again.
As of the fact that we can not set state inside render function because it will cause side effect, you cannot call the getPlayersList() inside of the render function.
The solution mentioned by #Jason Bellomy is the proper way to solve this with calling getPlayerList inside of the componentDidMount, because it invoked immediately after a component is inserted into the tree, thus it's a place to set initial data for rendering a page.
class Players extends Component {
nbaPlayersUrl = "https://someUrl.com";
state = {
players: null,
selectedTeamPlayers: null,
};
componentDidMount(){
this.getPlayersList();
}
render() {
if (this.props.teamId === null) return null;
if (this.state.players !== null && this.state.selectedTeamPlayers !== null) {
return this.getMarkup(selectedTeamPlayers);
} else {
return (<span> Loading ... </span>);
}
}
getPlayersList() {
let api = new ExternalApi();
let that = this;
api.get(this.nbaPlayersUrl).then(r => {
r.json().then(result => {
let players = result.data.map(p => ({
id: p.id,
firstName: p.first_name,
lastName: p.last_name,
position: p.position,
heightInches: p.height_inches,
heightFeet: p.height_feet,
weightPounds: p.weight_pounds,
teamId: p.team.id
}));
var selectedTeamPlayers = that.filterPlayersByTeamId(players);
that.setState({
players: players,
selectedTeamPlayers: selectedTeamPlayers,
});
});
});
}
filterPlayersByTeamId(players) {
return players.filter(p => {
return p.teamId === this.props.teamId;
});
}
getMarkup(players) {
var result = players.map(p => {
<li key={p.id}>
<div>
<label htmlFor="firstName">First Name</label> <input type="text" name="firstName" value={p.firstName} readOnly></input>
</div>
<div>
<label htmlFor="lastName">Last Name</label> <input type="text" name="lastName" value={p.lastName} readOnly></input>
</div>
<div>
<label htmlFor="position">Position</label> <input type="text" name="position" value={p.position} readOnly></input>
</div>
</li>
});
return (
<ul>
{result}
</ul>
);
}
}
export default Players;

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

Getting value from react component

I have a component InputArea with state = {input: ''}
Then I map several of these components in a container and write them in state = {inputAreas: []}
Now, how can I get inputs in the container? Logging this.state.inputAreas[0] returns object like this:
{$$typeof: Symbol(react.element), type: ƒ, key: "1", ref: null, props:
{…}, …}
In elements it shows like this:
<input type="text" class="form-control" name="input" value="abc">
Using this.state.prefooterArea[0].value gives undefined.
I also tried passing input from component to container as props, but it says getInput is not a function. From what I understood it has something to do with the fact I used map in the container. I can't use redux in this project.
Code of component
class PrefooterAreaInput extends Component {
state = {
input: ''
}
textChangedHandler = (event) => {
let newState = {};
newState[event.target.name] = event.target.value;
this.setState(newState);
}
render() {
return (
<div>
<input
className="form-control"
type="text"
name="input"
value = {this.state.input}
onChange={this.textChangedHandler}
/>
</div>
)
}
}
Code of container
class DescriptionFrame extends Component {
state = {,
prefooterArea: [<PrefooterAreaInput key={1}/>]
};
addFooterInputHandler = event => {
event.preventDefault();
if (this.state.prefooterArea.length < prefooterInputFieldsMax) {
var newPrefooterArea = this.state.prefooterArea.map(
inputField => inputField
);
newPrefooterArea.push(
<PrefooterAreaInput key={this.state.prefooterArea.length + 1} />
);
this.setState({ prefooterArea: newPrefooterArea });
}
};
removeFooterInputHandler = event => {
event.preventDefault();
if (this.state.prefooterArea.length > 1) {
var newPrefooterArea = this.state.prefooterArea.map(
inputField => inputField
);
newPrefooterArea.splice(newPrefooterArea.length - 1);
this.setState({ prefooterArea: newPrefooterArea });
}
render() {
// want to get this.state.prefooterArea[0]'s value
return (
<div>
{this.state.prefooterArea}
<a
className="nav-link"
href=""
onClick={this.addFooterInputHandler}
>
Add More
</a>
<a
className="nav-link"
href=""
onClick={this.removeFooterInputHandler}
>
Remove Last
</a>
</div>
);
}
}
Figured it out. This caused problem.
prefooterArea: [<PrefooterAreaInput key={1}/>]
I should have added that initial PrefooterAreaInput with lifecycle method instead. With that I was able to pass state just fine.
Are you trying to achieve something like this ?
child component :
export default class InputBox extends React.Component {
render() {
return (
<input onChange={event => this.props.onChange(event.target.value)} />
);
}}
parent component :
import InputBox from './InputBox';
class FilterBar extends React.Component {
constructor(props) {
super(props);
this.state = {
inputs: "" //get input value from state this input
};
this.updateFilters = this.updateFilters.bind(this);
}
updateFilters(i) {
this.setState({ inputs: i }); // this will print whatever input you type
}
render() {
return (
<div>
<InputBox onChange={(i) => this.updateFilters(i)} />
</div>
);
}
}

React - how to pass multiple input values to child in on change event when values have different types (is not always e.target.value)?

I have multiple input fields and 1 react-select dropdown field. I created a method in my parent component that sets the state with the values from the input, passes it down to the child which should call the method. My problem is that react-select doesn't take the value but an object like this:
{value: 'xy', name:'x', label: 'y'}
so normally my function in my onChange event handler would look like this (when passing multiple values):
in parent:
testing(e) {
this.setState({
[e.target.name]: e.target.value
})
}
in child:
<input type="text" name="maxfare" onChange={this.onChange}/>
...
onChange(e){
var value = [e.target.name] = e.target.value;
this.props.onChange(value);
}
...
However, while my input fields take:
e.target.value
my select dropdown takes entire 'e' - not e.target.value. I tried to pass my onChange function in child component 2 arguments, calling my method in parent with 2, but that doesn't to work. Any help would be great! My code is below (the relevant parts- if I forgot something that you think is important, please let me know). Ps. I thought about having 2 onChange functions, passing once my value for select dropdown and a second one doing the rest, but then I would need to pass 2 onChange methods to the child and I believe thats not possible in react?! Thanks!!:
Parent:
...
onChangeT(selectValue, value) {
this.setState({
origin: selectValue,
maxfare: value
...
})
}
render(){
....
<Parent cities={this.state.citiesToSelect} origin={this.state.origin} maxfare={this.state.maxfare} onChange={this.onChangeT}/>
...
}
Child:
....
onChangeC(e){
var value = [e.target.name] = e.target.value;
this.props.onChange(e, value);
console.log("name", name)
}
....
<Select
onChange={this.onChangeC}
labelKey='name'
value={this.props.origin}
options={this.props.cities}
/>
<input type="text" name="maxfare" onChange={this.onChangeC}/>
We want to be able to do this in the parent
onChange = (name, value) => {
this.setState({[name]: value});
}
We fix the "wiring" of the children onChange to do exactly that, raise an onChange with a name and a value. Wrap react-select and provide a consistent interface to the parent.
Form example
import * as React from 'react';
import Input from './Input';
import Select from './Select';
export default class Form extends React.Component {
state = {
input: '',
select: '',
options: ['A', 'B', 'C']
};
onChange = (name: string, value: string) => {
this.setState({[name]: value});
}
render() {
return (
<form>
<Input
label="Surname"
name={'input'}
value={this.state.input}
onChange={this.onChange}
/>
<Select
label="Grade"
name={'select'}
value={this.state.select}
options={this.state.options}
onChange={this.onChange}
/>
</form>
);
}
}
Input example
import * as React from 'react';
export default class Input extends React.Component {
onChange = (e) => {
const {onChange, name} = this.props;
if (onChange) {
onChange(name, e.currentTarget.value);
}
}
render() {
return (
<div>
<label>{this.props.label}</label>
<input
type="text"
name={this.props.name}
value={this.props.value}
onChange={this.onChange}
/>
</div>
);
}
}
And a DOM native <Select /> example
import * as React from 'react';
export default class Select extends React.Component {
onChange = (e) => {
const {onChange, name} = this.props;
if (onChange) {
onChange(name, e.currentTarget.value);
}
}
render() {
return (
<div>
<label>{this.props.label}</label>
<select
name={this.props.name}
value={this.props.value}
onChange={this.onChange}
>
{this.props.options.map(o => <option key={o}>{o}</option>)}
</select>
</div>
);
}
}
The fact that react-select doesn't return a native event nor a similar object shape of a native event, is forcing you to normalize the shape of the object that returned from it. You can do that by wrapping the Select component of react-select with your own component and returning a custom object for your use-case.
In this example we are trying to normalize the behavior of our onChange event both for inputs and Select. We will first check if the object that returned is having a target key, if it does we know that this is a native event that we are handling and we will set the state according to the name of the input and its value (exactly how you did it in your example).
If we don't have a target key, then we may handle a different kind of event.
We will check if we get a selectedValue key (just a convention between yourself, you can change the key as you like), then we will set the state by its name and selectedValue that we received.
This will only work if you will pass the name upwards of course.
So the object that you need to return from the custom Select component should look something like this:
{name: this.props.name, selectedValue }
// where selectedValue is the object received from the real Select component
Here is a running example:
const options = [
{ value: 'one', label: 'One' },
{ value: 'two', label: 'Two' },
]
const moreOptions = [
{ value: 'mike', label: 'johnson' },
{ value: 'lynda', label: 'bog' },
]
class MySelect extends React.Component {
handleChange = selectedValue => {
const { name, onChange } = this.props;
onChange({ name, selectedValue });
}
render() {
const { options, value, ...rest } = this.props;
return (
<Select
{...rest}
value={value}
onChange={this.handleChange}
options={options}
/>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
option1: '',
option2: '',
value1: 1,
value2: '',
value3: 3,
}
}
handleChange = e => {
let nextState;
if (e.target) {
const { name, value } = e.target;
nextState = { [name]: value };
} else if (e.selectedValue) {
const { name, selectedValue } = e;
nextState = { [name]: selectedValue };
}
this.setState(nextState);
}
render() {
const { value1, value2, value3, option1, option2 } = this.state;
return (
<div>
<MySelect
value={option1.value}
onChange={this.handleChange}
options={options}
name="option1"
/>
<div>
<span>input1 </span>
<input value={value1} name="value1" onChange={this.handleChange} />
</div>
<div>
<span>input2 </span>
<input value={value2} name="value2" onChange={this.handleChange} />
</div>
<div>
<span>input3 </span>
<input value={value3} name="value3" onChange={this.handleChange} />
</div>
<MySelect
value={option2.value}
onChange={this.handleChange}
options={moreOptions}
name="option2"
/>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<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>
<script src="https://unpkg.com/prop-types#15.5.10/prop-types.js"></script>
<script src="https://unpkg.com/classnames#2.2.5/index.js"></script>
<script src="https://unpkg.com/react-input-autosize#2.0.0/dist/react-input-autosize.js"></script>
<script src="https://unpkg.com/react-select/dist/react-select.js"></script>
<link rel="stylesheet" href="https://unpkg.com/react-select/dist/react-select.css">
<div id="root"></div>

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

Categories

Resources