Accessing state of children from parent component? - reactjs

I don't think I fully understand how the Parent/Child relationship works in React. I have two components, Column and Space. When a Column is rendered, it creates some Spaces. I thought that meant that the Column would be the parent to those Spaces, but either I'm wrong or I'm using some part of React.Children.count(this.props.children) incorrectly - it always tells me that any given Column has 0 children.
Simplified models:
var Column = React.createClass({
getInitialState: function() {
return {childCount: '', magicNumber: this.props.magicNumber};
},
componentDidMount: function() {
var newCount = React.Children.count(this.props.children);
this.setState({childCount: newCount});
},
render: function() {
return (
<div className='Column'>
{
Array.from({ length: this.state.magicNumber }, (x,y) => <Space key={y}/>)
}
</div>
);
}
});
var Space = React.createClass({
render: function() {
return (
<div className="Space">
space here
</div>
);
}
});
It seems like no matter where I put React.children.count(this.props.children) in a Column, it tells me there are 0 children. I would expect the Column generated in the example code to have five children, since five Spaces are created within it.
I figured maybe I was trying to count the Children before they were fully loaded, so I tried writing a click handler like this:
//in Column
setChildCount: function () {
var newCount = React.Children.count(this.props.children);
this.setState({childCount: newCount});
}
Then adding a button like this in my Column:
...
render: function() {
return (
<div className='Column'>
{
Array.from({ length: this.state.magicNumber }, (x,y) => <Space key={y}/>)
}
<button onClick={this.setChildCount} />
{this.state.childCount}
</div>
);
But my childCount is always and forever 0. Can anyone see where I'm going wrong?
Edit: Ultimately, I want to get a count of all children elements who have X attribute in their state set to Y value, but I am clearly a step or three away from that.

The Column component doesn't have any children on that code. Childrens are components which are wrapped by the parent component. So imagine:
<Column>
<Space/>
<Space/>
<Column/>
In this case the parent Column has two children Space
On your code:
<div className='Column'>
{
Array.from({ length: this.state.magicNumber }, (x,y) => <Space key={y}/>)
}
</div>
You are creating new components inside a divnot inside the component Column

You are rendering Space components as part of the Column component. The parent/child relationship captured by this.props.children looks like this:
var Column = React.createClass({
render: function() {
<div className="column">
{this.props.children}
</div>
}
});
ReactDOM.render(
<Column>
<Space /> //little space children
<Space />
</Column>
);
To get at your specific problem, you don't need anything like this.props.children because you have everything right there in your render method.
So the answer to your question is: apply the same logic as when you render them.

Related

React prop array length returning 0 [duplicate]

This question already has an answer here:
Can't iterate over my array/object. Javascript, React Native [duplicate]
(1 answer)
Closed 5 years ago.
I have an array prop on a component called jobs that will show in the console, but always returns length:0
The Image you will see definitely has three elements in the array, but when I attempt to access the array length through console.log(this.props.jobs.length);
Why do the elements of this array log out, but I can't access the elements in code?
Per request from #finalfreq, see full code below:
const DepartmentContainer = React.createClass({
getInitialState:function(){
return{sortedJobs:{}, loaded:false};
},
componentDidMount:function(){
//console.log(this.state.sortedJobs);
var departments = this.props.departments;
departments.map(function(name, index){
this.state.sortedJobs[name] = [];
}, this)
var _this = this;
axios.get('{api call returning json}')
.then(function (response) {
for(let i=0;i<response.data.jobs.length;i++){
for(let j=0;j<response.data.jobs[i].metadata[0].value.length;j++){
_this.state.sortedJobs[response.data.jobs[i].metadata[0].value[j]].push(response.data.jobs[i]);
}
}
})
.catch(function (error) {
console.log(error);
});
//console.log(Object.keys(_this.state.sortedJobs).length);
this.setState({sortedJobs: _this.state.sortedJobs});
this.setState({loaded:true});
},
render:function(){
var departments = this.state.sortedJobs;
return(
<div>
{this.state.loaded ?
<div className="row grid-12">
{Object.keys(departments).map(function(label, department){
//console.log(departments[label]);
return <Department key={label} title={label} jobs={departments[label]}/>
})}
</div>
:
<div>Test</div>
}
</div>
);
}
});
const Department = React.createClass({
getInitialState:function(){
return{hidden:false, hasJobs:false};
},
componentWillMount:function(){
const jobs = this.props.jobs;
if(jobs.length>0){
this.setState({hasJobs:true});
}
},
componentDidMount:function(){
console.log(this.state.hasJobs);
console.log(this.props.jobs.length);
},
renderNormal:function(){
return(
<div className="grid-4 department-block" data-dep-filter={this.state.hidden} data-loc-filter="false" data-department={this.props.title}><h3 className="text-uppercase">{this.props.title}</h3>
<div className="posting-list margin-bottom">
<h3 className="text-uppercase">{this.props.title}</h3>
</div>
</div>
)
},
renderEmpty:function(){
return(
<div className="grid-4 department-block" data-dep-filter={this.state.hidden} data-loc-filter="false" data-department={this.props.title}><h3 class="text-uppercase">{this.props.title}</h3>
<div className="posting-list margin-bottom">
<div class="no-posts job-post">
<p><em>0 Current Openings</em></p>
</div>
</div>
</div>
);
},
render: function(e){
if (this.hasJobs) {
return (
this.renderNormal()
)
}else{
return(
this.renderEmpty()
)
}
}
});
In the Department:componentWillMount function I want to check the jobs array passed to it from the DepartmentContainer:render function and set the state on said Department to either hasJobs true/false
#finalfreq has the right idea. This is an artifact of how the JavaScript console works (for Chrome at least). Values are generally only displayed/retrieved when you expand them, which can lead to some counter intuitive behavior. You must be adding elements to the array after you are logging it to the console.
Check it out:
I make an array and push something into it. Then I log it to console. Then I push a second element.
Now when I expand the previous logged Array, you'll see it now has the most up-to-date values. Except "Array[1]" doesn't update to "Array[2]"... and if you push another value into the array, the previously logged values won't change even if you collapse and expand them again.
The moral of the story is... don't rely on console.log too much hehe. But if you do, learn its quirks.

ReactJS parent/child list items not rendering properly after an item is removed

Example: https://jsfiddle.net/wbellman/ghuw2ers/6/
In an application I am working on, I have a parent container (List, in my example) that contains a list of children (Hero, in my example). The list is governed by an outside object. For simplicity I declared the object directly in the JS. (In my real application the data store is properly namespaced and so forth.)
The problem I have is in the list I have three elements, if I remove an item from the middle, the rendered list appears to remove the last element. However the outside object reflects the proper list.
For example:
My list has the elements: cap, thor, hulk
If you remove "thor", "cap" and "thor" are rendered
The heroList reflects "cap" and "hulk" as it should
I am relatively new to ReactJs, so there is a good chance my premise is fundamentally flawed.
Note: The example reflects a much more complex application. It's structured similarly for purposes of demonstration. I am aware you could make a single component, but it would not be practical in the actual app.
Any help would be appreciated.
Here is the code from JSFiddle:
var heroList = [
{ name: "cap" },
{ name: "thor"},
{ name: "hulk"}
];
var List = React.createClass({
getInitialState() {
console.log("heros", heroList);
return {
heros: heroList
};
},
onChange(e){
this.setState({heros: heroList});
},
removeHero(i,heros){
var hero = heros[i];
console.log("removing hero...", hero);
heroList = _.filter(heroList, function(h){ return h.name !== hero.name;});
this.setState({heros:heroList});
},
render() {
var heros = this.state.heros;
var createHero = (hero,index) => {
return <Hero hero={hero} key={index} onRemove={this.removeHero.bind(this,index,heros)}/>;
};
console.log("list", heros);
return (
<ul>
{heros.map(createHero)}
</ul>
)
}
});
var Hero = React.createClass({
getInitialState() {
return {
hero: this.props.hero
}
},
render() {
var hero = this.state.hero;
return (
<li>Hello {hero.name} | <button type="button" onClick={this.props.onRemove}>-</button></li>
);
}
});
ReactDOM.render(
<List />,
document.getElementById('container')
);
Additional: I was having problems copying the code from JSFiddle, anything I broke by accident should work in the JSFiddle listed at the top of this question.
Edit:
Based on the commentary from madox2, nicole, nuway and Damien Leroux, here's what I ended up doing:
https://jsfiddle.net/wbellman/ghuw2ers/10/
I wish there was a way to give everyone credit, you were all a big help.
Changing your Hero class to this fixed the issue of displaying the wrong hero name for me:
var Hero = React.createClass({
render() {
return (
<li>Hello {this.props.hero.name} | <button type="button" onClick={this.props.onRemove}>-</button></li>
);
}
});
i.e. I removed the local state from the class and used the prop directly.
Generally speaking, try to use the local store only when you really need it. Try to think of your components as stateless, i.e. they get something through the props and display it, that's it.
Along these lines, you should consider passing the hero list through the props to your List component as well.
if you really have problems with managing your data you should use Flux or Redux.
in this code:
heroList = _.filter(heroList, function(h){ return h.name !== hero.name;});
i just dont get why you filer the heroList instead of this.state.heros? every time you add or remove a hero, the heroList in your current scope shouldnt be kept in state? the global heroList is just the initial state.
The problem is with the keys used. Since the key is taken from the index, that key has already been used and thus the hero with that key is shown.
change it to key={Math.random() * 100} and it will work

ReactJS - Listing all keys at once

I'm a beginner of ReactJS and I'm stuck trying to figure out why map only returns a single prop at a time.
In file1.jsx, I have an array that contains 3 objects:
var MainPanelOneData = [{"id":1,"shotviews":15080},{"id":2,"likes":12000},{"id":3,"comments":5100}];
File1.jsx also has a render function to extrapolate the data inside the array:
render: function() {
var ListMainPanelOne = MainPanelOneData.map(function(data) {
return <MainPanelOne key={data.key} shotviews={data.shotviews} likes={data.likes} comments={data.comments} />
});
In file2.jsx, I have this code to render the data object from file1.jsx:
render: function() {
return (
<div>
<span>{this.props.shotviews} shot views</span>
<span>{this.props.likes} likes</span>
<span>{this.props.comments} comments</span>
</div>
)
}
The result shows this:
15080 shot views likes comments
shot views12000 likes comments
shot views likes5100 comments
I'm guessing it repeats like this because it searches through one key at a time? If that's the case, how do I display all keys at the same time? Use indexing?
well your array of data doesnt have all the keys. each one of your objects in PanelOneData has ONE key and is missing the other two; additionally none of them have key called key. so youre making three MainPanelOne components, each with a single prop. the result of that map is this
[
<MainPanelOne key={null} shotviews={15080} likes={null} comments={null} />,
<MainPanelOne key={null} shotviews={null} likes={12000} comments={null} />,
<MainPanelOne key={null} shotviews={null} likes={null} comments={5100} />
]
which is an accurate display of what youre seeing
To get one line you might do something like this.
render: function() {
var ListMainPanelOne = MainPanelOneData.map(function(data) {
return <span key={data.id}> {data.shotviews} {data.likes} {data.comments} </span>
});

How to get the Ref of input after a click

I'm using an array of string categories to create input checkboxes. Checking the input box tells me you want to include that category and unchecking an input box tells me you don't want to include that category.
Problem is, I don't know what the ref of the object that is being clicked. How do I figure out the ref?
I'd like to pass this ref back up to my parent class so that it can find the index of the 'ref' in my array of categories, and splice that out. The result being that my filteredCategories array will remove the item when it is there and add it if it isn't
var HeaderCategories = React.createClass({
handleChange:function(e){
// I know what was clicked through e.target
// but I'd like to know the ref of what was clicked
// so that I can do somthing like the below:
this.props.filterTable(this.refs[e.target.ref])
},
render: function(){
var categories = this.props.allCategories.map(function(category){
return (
<label key={category}>{category}
<input type="checkbox" ref={category} onChange={this.handleChange}/>
</label>
)}.bind(this))
return (
<div className="categories __header" >{categories}</div>
);
}});
var Table = React.createClass({
allCategories: ["apples","oranges","bananas"]
componentDidMount:function(){
this.setState({filteredCategories:this.allCategories})
},
getInitialState:function(){
return {filteredCategories:this.allCategories}
},
filterTable:function(category){
// If I have the ref then I can use a splice and remove it from my filtered Categories
//this is pseudo code, havent checked if it works
var index = filteredCategories.indexOf(category)
var filteredCategories.splice(index,filteredCategories.length)
this.setState(filteredCategories:filteredCategories)
},
render:function(){
return <Header filterTable={this.filterTable} allCategories={this.allCategories}>
}
})
There is not a good way to get a reference to a component from the event param of the onClick callback. You best option is to curry either the category index or name to the callback like this (in this example, I'm passing back just the index)
handleChange:function(e, categoryIndex){
// `categoryIndex` was curried in the click handler
// of each checkbox in the render function below so
// the filterTable callback should accept the index.
this.props.filterTable(categoryIndex);
},
render: function(){
var categories = this.props.allCategories.map(function(category, index){
return (
<label key={category}>{category}
<input type="checkbox" ref={category} onChange={this.handleChange.bind(this, index)}/>
</label>
)}, this); // map's second param is thisArg
return (
<div className="categories __header" >{categories}</div>
);

How to "override" the render function?

I'm just getting started with React. I have a project that includes many tables, some pretty complex, but all generally output a table from a set of data. Some have parent-child relationships (with toggleable child rows), and each has a slightly different format for the row output (e.g. some have buttons that open modals). Normally, I would use jQuery DataTables for this, which is easy and flexible for something like this. I'm struggling to figure out how to do it sustainably (scaleably?) in React, though.
I've written a basic table component where it accepts a set of items via props and spits out a table and handles child rows via internal state. Today I'll convert that to use two components: the table and a separate one for the rows. I really don't want to have to write X or 2X different components though (where X is the number of tables in the project), but I'm not sure how to make it reusable. All of the tables should have some things in common like style, filter capability, paging, etc., which I would try to put at the Table component level and reuse.
The best solution so far that I've thought about doing is passing in a preRender function via props and using that to create the actual row JSX, and having the render function just assemble all of those snippets into one output (which is basically what I do already but with Array.map. I could then provide the preRender via a prop (if that works), like this:
var Table = React.createClass({
render: function() { // mixin?
var rows = [];
for (var i=0; i<this.props.items.length; i++) {
rows.push(this.props.preRender(this.props.items[i]));
}
return rows; // plus html boilerplate...
}
});
var Parent = React.createClass({
render: function() {
var foodItems = this.state.foodItems;
var drinkItems = this.state.drinkItems;
var foodRender = function(i) { return (<tr>{i} <a href?>Buy me</a></tr>); }
var drinkRender = function(i) { return (<tr>{i} <button>Drink me</button></tr>); }
return (
<Table items={foodItems} preRender={foodRender}/>
<Table items={drinkItems} preRender={drinkRender}/>
);
}
});
Another thing I thought of was somehow passing in a different Row component to the Table component, if that's possible. I guess the root problems are:
The table-level stuff is very similar or identical across tables but may have parts needing customization per-table.
The rows do things like open popovers, so they will have a state (or need to circle around w/props).
Is there a better way to do this sort of logic/dependency injection, or will I probably just need to make a ton of slightly-different controls.
I'd start with something like this (using ES6 syntax for brevity):
const FoodItem = React.createClass({
render() {
return (
<tr><td>{this.props.item} Buy me</td></tr>
);
}
});
const DrinkItem = React.createClass({
render() {
return (
<tr><td>{this.props.item} <button>Drink me</button></td></tr>
);
}
});
const Table = React.createClass({
render() {
const {items, itemComponent: ItemComponent} = this.props;
return (
<table>
{items.map(item => <ItemComponent item={item} />)}
</table>
);
}
});
const Parent = React.createClass({
render() {
return (
<div>
<Table items={this.state.foodItems} itemComponent={FoodItem}/>
<Table items={this.state.drinkItems} itemComponent={DrinkItem}/>
</div>
);
}
});
An alternative pattern would be this (which is best depends on your requirements):
const FoodItem = React.createClass({
render() {
return (
<tr><td>{this.props.item} Buy me</td></tr>
);
}
});
const DrinkItem = React.createClass({
render() {
return (
<tr><td>{this.props.item} <button>Drink me</button></td></tr>
);
}
});
const Table = React.createClass({
render() {
// I'm assuming you ultimately want your table to do something cleverer than just rendering a table element
return (
<table>{this.props.children}</table>
);
}
});
const Parent = React.createClass({
render() {
return (
<div>
<Table>{this.state.foodItems.map(item => <FoodItem item={item} />)}</Table>
<Table>{this.state.drinkItems.map(item => <DrinkItem item={item} />)}</Table>
</div>
);
}
});

Resources