React Passing props to another looped Component - reactjs

I'm trying to pass props of looped component(using array.map()) to other component, made as follows:
{this.posts.map((item, index) => {
return (
<Post
item={item}
key = {index}
/>
);
})
}
So there are many 'Post' sibling components being rendered, but each with different items(data) and keys. Now I wish to send the props(to share a state between the two components) of one specific Post component to another specific sibling Post component. That is, I wish to select a specific Post component (maybe with the key value? and send a state only to another specific post component).
How would I achieve this?
Thank you.

I would recommend binding a function that would update this.posts. That way your components will all update without having to take on potentially entangled states.
The idea is the function updatePosts will take a target index (key) child component to update and pass it the new state. In the code below, if you triggered updatePosts({firstName: 'Harry', lastName: 'Styles'}, 1), then the state would change from the first element being 'Foo Bar' to being 'Harry Styles', and the child components will rerender.
import React from 'react';
class PostMaster extends React.Component {
constructor(props) {
super(props);
this.state = {
//the array you use to render child components will be managed in a state like this
data: [
{
firstName: 'Foo',
lastName: 'Bar'
},
{
firstName: 'James',
lastName: 'Bond'
},
{
firstName: 'Harry',
lastName: 'Potter'
}
]
};
this.updatePosts = this.updatePosts.bind(this);
};
/**
* stateData (object) the data you want to go in a given index
* index (int) the child component you would like to update
/
updatePosts(stateData, index) {
let newData = this.state.data;
newData[index] = stateData;
this.setState({data: newData});
//you can always push instead of reassigning
};
render() {
return(
<div>
{this.state.data((item, index) => {
return (
<Post
item={item}
key={index}
update={this.updatePosts}
/>
);
})}
</div>
)
}
}

Related

Passing functions to child components in React - this.props.handleChange is not a function?

Trying to create a simple todo list and I figure out how to pass the function from the parent component down to the child component without it throwing an error
App Component
class App extends React.Component {
state = {
todo: todoData.map(todo => {
return <TodoItem handleChange={this.handleChange} todo={todo} key={todo.id} />
}),
count: 0,
}
handleChange = (id) => {
console.log(id)
}
render(){
return(
<div className="flex">
<Header />
<div className="todoList" >
{this.state.todo}
</div>
</div>
)
}
}
TodoItem component
class TodoItem extends React.Component {
render(){
console.log(this.props)
return(
<p className="todoItem" onClick={this.props.clickeds}>
<input type="checkbox" checked={this.props.todo.completed} onChange={() => this.props.handleChange(this.props.todo.id)} />
{this.props.todo.text}
</p>
)
}
}
I'm trying to mess with the onChange handler in the TodoItem component, but I keep getting the same error that this.props.handleChange is not a function
Todo just for reference
todoData = {
id: 2,
text: "Grocery Shopping",
completed: false
},
What am I doing wrong?
When I change the handleChange function to NOT an arrow function in the app component, it works. (handleChange(id)). If I change this function to an arrow function (handleChange = (id) => { } ) I run into this error.
I recommend you use ES6's class syntax for now (which I have added below) as this is how most React tutorials —including the official ones— are written.
The data structure you should keep in the state should be an Array of Todo Objects.
You don't need to keep the components inside the state, simply iterate them on render (and don't worry about its performance, React won't recreate the HTML dom by doing this, so the render will be very efficient).
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
todos : todoData,
count : 0,
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(id) {
const todos = this.state.todos.map(todo => (
(todo.id === id)
? Object.assign(todo, { completed: !todo.completed }
: todo
));
this.setState({ todos });
}
render() {
// ...
this.state.todos.map(todo => (
<TodoItem handleChange={this.handleChange} todo={todo} key={todo.id} />
);
}
}
// todoData should probably be an array in case you want to include more by default:
todoData = [
{
id: 2,
text: "Grocery Shopping",
completed: false
},
{
id: 2,
text: "Grocery Shopping",
completed: false
}
];
Be careful about mutating the state
The reason for the ugly and confusing map() and Object.assign() inside handleChange is because you cannot do a shallow copy, nor edit the array directly:
this.state.todos[i].completed = true // ! mutates the state, no good
Preferably Lodash's library or even better, an immutable library or memoized selectors would do the trick of setting a todo as completed in a much nicer fashion.
Without deep cloning or immutability, you would need to copy the object, clone it, then create a new array with the new object, and assign that array to the state.

How do I limit the user to only selecting one component?

I have the following code that simply constructs blocks for our products and the selected state allows the component to be selected and unselected. How can I figure out which of these components are selected and limit the user to only selecting one at a time. This is ReactJS code
import React from 'react';
export default class singleTile extends React.Component{
constructor(props){
super(props);
this.title = this.props.title;
this.desc = this.props.desc;
this.svg = this.props.svg;
this.id = this.props.id;
this.state = {
selected: false
}
}
selectIndustry = (event) => {
console.log(event.currentTarget.id);
if(this.state.selected === false){
this.setState({
selected:true
})
}
else{
this.setState({
selected:false
})
}
}
render(){
return(
<div id={this.id} onClick={this.selectIndustry}className={this.state.selected ? 'activated': ''}>
<div className="icon-container" >
<div>
{/*?xml version="1.0" encoding="UTF-8"?*/}
{ this.props.svg }
</div>
</div>
<div className="text-container">
<h2>{this.title}</h2>
<span>{this.desc}</span>
</div>
</div>
)
}
}
You need to manage the state of the SingleTile components in the parent component. What i would do is pass two props to the SingleTile components. A onClick prop which accepts a function and a isSelected prop that accepts a boolean. Your parent component would look something like this.
IndustrySelector.js
import React from 'react';
const tileData = [{ id: 1, title: 'foo' }, { id: 2, title: 'bar' }];
class IndustrySelector extends Component {
constructor(props) {
super(props);
this.state = { selectedIndustry: null };
}
selectIndustry(id) {
this.setState({ selectedIndustry: id });
}
isIndustrySelected(id) {
return id === this.state.selectedIndustry;
}
render() {
return (
<div>
{tileData.map((data, key) => (
<SingleTile
key={key}
{...data}
onClick={() => this.selectIndustry(data.id)}
isSelected={this.isIndustrySelected(data.id)}
/>
))}
</div>
);
}
}
The way this works is as follows.
1. Triggering the onClick handler
When a user clicks on an element in SingleTile which triggers the function from the onClick prop, this.selectIndustry in the parent component will be called with the id from the SingleTile component.
Please note that in this example, the id is remembered through a
closure. You could also pass the id as an argument to the function of
the onClick prop.
2. Setting the state in the parent component
When this.selectIndustry is called it changes the selectedIndustry key of the parent component state.
3. Updating the isSelected values form the SIngleTile components
React will automatically re-render the SingleTile components when the state of the parent component changes. By calling this.isIndustrySelected with the id of the SingleTile component, we compare the id with the id that we have stored in the state. This will thus only be equal for the SingleTile that has been clicked for the last time.
Can you post your parent component code?
It's not so important, but you can save some time by using this ES6 feature:
constructor(props){
super(props);
const {title, desc, svg, id, state} = this.props;
this.state = {
selected: false
}
}

React.js setState does not re-render child component

I am passing a list from the parent component's state to a child component's <CheckboxList /> props. The list should then show in the child component. After fetching the list elements from the database I use setState to update the list but the child component is not re-rendered with the new list.
class ParentComponent extends Component {
state = {
users: [{ username: 'before' }]
}
componentDidMount() {
const result = [];
db.collection("users").get().then((querySnapshot) => {
querySnapshot.forEach(function (doc) {
let user = doc.data();
result.push(user);
});
}).then(() => {
this.setState({
users: result
})
})
}
render() {
return (
<div>
<h1>List of users</h1>
<CheckboxList list={this.state.users} />
</div>
)
}
}
The list shows before and it does not update with content from the database. I checked and the values are fetched from the database, they are just not passed to the <CheckboxList /> after the setState. Can anybody help me?
The problem was that I was retrieving the list from the CheckboxList status and then using it through that. Now I have fixed it. I am retrieving it in the CheckboxList render(), storing it into a variable and then using it from there.

Re-render array of child components in React after editing the array

I have data in parent's component state like this:
data=[
{key:0,name="abc",value="123"},
{key:1,name="def",value="456"},
{key:2,name="ghi",value="789"}
]
In my react component I am calling child components as list in the return part of the parent component as shown below.
class MyApp extends React.Component{
constructor(props) {
super(props);
this.state = {
record={
name="",
timestamp="",
data =[
{key:0,name="abc",value="123"},
{key:1,name="def",value="456"},
{key:2,name="ghi",value="789"}
]
}
this.deleteFromStateArray=this.deleteFromStateArray.bind(this);
}
deleteFromStateArray(key){
let oldData = [...this.state.record.data];
let newData= [];
oldData.map(function (record, i) {
if (record.key != key) {
newData.push(record);
}
})
newData.map(function (record, i) {
newData[i].key = i + 1
})
this.setState({ record: Object.assign({}, this.state.record, { data: newData }), });
}
render() {
return(
{
this.state.data.map((record,index) =>
<Child key={record.key}
id={record.key}
name={record.name}
value={record.value}
onDelete={this.deleteFromStateArray} />
}
)
}
I am calling onDelete() in the child component like this
<button onClick={this.props.onDelete(this.state.id)} />
\\ I am initializing id as key in state inside constructor in child
My problem is when I am calling onDelete in the child class I am able to remove the obj with key=1 properly in the function but rerendering is not happening properly.
What I mean is state is getting set properly with only 2 items in data 1 with key=0 and other with key=2. But what i see in GUI is 2 child components 1 with key 0 and second one with key=1 which is cxurrently not present in the state data.
Can anybody help me with re-rendering the data properly?
I also want to change the key ordering after deleting from array in setState
React uses key to work out if elements in the collection need to be re-rendered. Keys should be unique and constant. In your method you are changing key property of your records and this probably leads to described error.
Also, you can replace all your code with simple filter call like this:
this.setState(oldState => ({
record: {
...oldState.record,
{
data: oldState.record.data.filter(item => item.key !== key)
}
}
});
It is also worth mentioning that you should keep your state as flat as possible to simplify required logic. In your case removing record and and leaving it as below would be a good idea:
this.state = {
name: "",
timestamp: "",
data: [
{key:0,name: "abc",value: "123"},
{key:1,name: "def",value: "456"},
{key:2,name: "ghi",value: "789"}
]
}
I'm not certain if this actually worked for you but the function declaration for deleteFromStateArray is inside the render function.
I think your component should look like this:
class MyApp extends React.Component{
constructor(props) {
super(props);
this.state = {
record={
name="",
timestamp="",
data =[
{key:0,name="abc",value="123"},
{key:1,name="def",value="456"},
{key:2,name="ghi",value="789"}
]
}
this.deleteFromStateArray=this.deleteFromStateArray.bind(this);
}
deleteFromStateArray(key){
let oldData = [...this.state.record.data];
let newData= [];
oldData.map(function (record, i) {
if (record.key != key) {
newData.push(record);
}
})
newData.map(function (record, i) {
newData[i].key = i + 1
})
this.setState({ record: Object.assign({}, this.state.record, { data: newData }), });
}
render() {
return(
{
this.state.record.data.map((record,index) =>
<Child key={record.key}
id={record.key}
onDelete={this.deleteFromStateArray} />
}
)
}
}
You can use a built-in set function provided by react with state. See below for an example:
import { useState } from 'react';
const [data, setData ] = useState([]);
fetch('https://localhost:5001/getdata')
.then((response) => response.json())
.then((data) => setData(data)) // Example setData usage
.catch((error) => alert)

How to pass props to recursive child component and preserve all props

I've run into an interesting problem. I have a parent component that has an array of objects that gets passed to a child component that is a TreeView, meaning it is recursive. I'm passing a function, and a couple of other props to the child, along with the array of objects that is handled recursively by the child. When logging the props in the render function of the child, on the first render all the props are there, but as the recursive function moves through each object in the array, it 'loses' all the other props that are not being handled recursively.
When the component first renders the props object is: prop1, prop2, arrayOfObjects
As it re-renders as recursion is happening, the props object in the child becomes: arrayOfObjects.
prop1, and prop2 have disappeared.
The end result is that I'm not able to call a function in the parent from the child, so I cannot update the state depending on which node in the tree is clicked. I'm not using redux, because this is a style guide - separate from our production app, that is meant to be for devs only, and simple so if possible I'd like to handle all the state from within the components.
There is one other issue - The array of objects is the folder structure of files in our styleguide, and I need to be able to click on a name in the list, and update the view with the contents of that file. This works fine when the file does not have any children, but when there are child nodes, if I click on the parent, the child is clicked. I've tried e.stopPropagation(), e.preventDefault() etc. but have not had any luck. Thanks in advance.
Parent:
import React, {Component} from 'react'
import StyleGuideStructure from '../../styleguide_structure.json'
import StyleTree from './style_tree'
class StyleGuide extends Component {
constructor(props) {
super(props)
let tree = StyleGuideStructure
this.state = {
tree: tree
}
This is the function I'd like to call from the child
setVisibleSection(nodeTitle) {
this.setState({
section: nodeTitle
})
}
render() {
return(
<TreeNode
className="class-name-here"
setVisibleSection={this.setVisibleSection.bind(this)}
node={this.state.tree}
/>
)
}
}
export default StyleGuide
This is essentially what I have in the child, as a fiddle here:
https://jsfiddle.net/ssorallen/XX8mw/
The only difference is that inside the toggle function, I'm trying to call setVisibleSection in the parent, but no dice.
Here is a photo of the console showing the props when the component initially renders, and then after recursion:
I don't think I really understand your 2nd issue. Could you post a fiddle showing the problem?
I think your first issue is that you need to pass the props down to the children. I tried to transcribe your example to your fiddle. You can see by clicking the nodes, the title switched to the node's name.
https://jsfiddle.net/hbjjq3zj/
/**
* Using React 15.3.0
*
* - 2016-08-12: Update to React 15.3.0, class syntax
* - 2016-02-16: Update to React 0.14.7, ReactDOM, Babel
* - 2015-04-28: Update to React 0.13.6
*/
class TreeNode extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: true,
};
}
toggle = () => {
this.setState({visible: !this.state.visible});
this.props.setVisibleSection(this.props.node.title)
};
render() {
var childNodes;
var classObj;
if (this.props.node.childNodes != null) {
childNodes = this.props.node.childNodes.map((node, index) => {
return <li key={index}><TreeNode {...this.props} node={node} /></li>
});
classObj = {
togglable: true,
"togglable-down": this.state.visible,
"togglable-up": !this.state.visible
};
}
var style;
if (!this.state.visible) {
style = {display: "none"};
}
return (
<div>
<h5 onClick={this.toggle} className={classNames(classObj)}>
{this.props.node.title}
</h5>
<ul style={style}>
{childNodes}
</ul>
</div>
);
}
}
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: true,
};
}
toggle = () => {
this.setState({visible: !this.state.visible});
};
setVisibleSection(nodeTitle) {
this.setState({
title: nodeTitle
})
}
render() {
return (
<div>
Title: {this.state.title}
<TreeNode
node={tree}
setVisibleSection={this.setVisibleSection.bind(this)}
/>
</div>
);
}
}
var tree = {
title: "howdy",
childNodes: [
{title: "bobby"},
{title: "suzie", childNodes: [
{title: "puppy", childNodes: [
{title: "dog house"}
]},
{title: "cherry tree"}
]}
]
};
ReactDOM.render(
<ParentComponent />,
document.getElementById("tree")
);
<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>

Resources