Props Parent component to a parent - child component - reactjs

How can I can call a function that is in the parent component (App) from a child component (Card)?
Card component renders one item (CHILD)
const Card = (props) => {
return (
<div style={{margin: '1em'}}>
<img alt="Profile" width="75" src={props.avatar_url} />
<div style={{display: 'inline-block', marginLeft: 10}}>
<div style={{fontSize: '1.25em', fontWeight: 'bold'}}>
{props.name}
</div>
<div>{props.company}</div>
<button
//when I click here should trigger App's delete func who deletes the select item.
onClick = { () => alert()}
className="btn btn-danger btn-sm">Delete</button>
</div>
</div>
);
};
CardList component renders a list of item (CHILD-PARENT)
const CardList = (props) => {
return (
//Props.nameProp = Value {...Card} spred operator
<div>
{props.cards.map(card => <Card key={card.id} {...card} />)}
</div>
);
}
Parent component:
class App extends React.Component {
state = {
cards: [
]
};
addNewCard = (cardInfo) => {
this.setState(prevState => ({
cards: prevState.cards.concat(cardInfo)
}))
};
deleteCard = (selectedCard) => {
this.setState(prevState => ({
//array.filter creates a new array with elements who pass the foo
cards: prevState.cards.filter(card => card !== selectedCard)
}));
}
render(){
const {
cards,
} = this.state;
return(
<div className="container" style={{marginTop: 15}}>
<Form onSubmit={this.addNewCard} />
<div className="container" style={{padding: 20}}>
{
cards.length > 0 ?
<CardList cards={cards} />
:
<p>Your list is empty</p>
}
</div>
</div>
);
}
}
EXPLANATION
I want to delete a item who is in the state list but that list is in the parent component, how can I do that?

You can pass down the deleteCard method as a prop to CardList, and from CardList to each Card component and call it there.
Example
const Card = props => (
<button onClick={() => props.deleteCard(props.card)}>{props.card}</button>
);
const CardList = props => (
<div>
{props.cards.map(card => (
<Card card={card} deleteCard={props.deleteCard} />
))}
</div>
);
class App extends React.Component {
state = {
cards: [1, 2, 3]
};
deleteCard = card => {
this.setState(({ cards }) => ({
cards: cards.filter(element => element !== card)
}));
};
render() {
const { cards } = this.state;
return <CardList cards={cards} deleteCard={this.deleteCard} />;
}
}
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>
<div id="root"></div>

Related

How to call onClick event handler in a recursive component in react.js?

I am trying to implement onClick event handle to get the details of the card. However, when clicking on it I am getting the details of some other card, not the card which I am trying to click. The Card component is recursive as I am creating a tree. Attaching the image for the reference.
const Card = (props: any) => {
const handleClick = (item: any) => {
console.log("This is value: ", item)
}
const [selectedOption, setSelectedOption] = useState(null);
return (
<ul>
{props.data.map((item: any, index: any) => (
<React.Fragment key={item.name}>
<li>
<div className="card">
<div className="card-body">
<p>{item.name}</p>
</div>
<div onClick={() => handleClick(item)}>
<Select
defaultValue={selectedOption}
onChange={handleChange}
className="select"
options={props.users}
/>
</div>
<div></div>
</div>
{item.children?.length && <Card data={item.children} users={[]} />}
</li>
</React.Fragment>
))}
</ul>
);
};
const AccountabilityChartComponent = () => {
return (
<div className="grid">
<div className="org-tree">
<Card
users={users}
data={hierarchy}
/>
</div>
</div>
);
};
export default AccountabilityChartComponent;
Currying the onClick handler is a useful technique. It's particularly convenient because the item and the click event are colocated within the same function -
function App({ items = [] }) {
const onClick = item => event => { // <--
console.log(event.target, item)
}
return <div>
{items.map((item, key) =>
<button key={key} onClick={onClick(item)} children={item.name} />
)}
</div>
}
const items = [
{ name: "apple", price: 3 },
{ name: "pear", price: 4 },
{ name: "kiwi", price: 5 }
]
ReactDOM.render(<App items={items} />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>

how can I add any event to a specific part of component ? react

I have list of data that render it with map - I need to add an event just in one of the item from that list.
const UserModal = (props) => {
const {user,setUser} = props ;
const list = [,{id:3,text:'گفت وگو ها',icon:<BsChat />},{id:5,text:'خروج',icon:<BiExit />},];
/this is my list for making navigation bar
return (
<div className={style.main}>
<div style={{bordeBottom:'1px solid black'}}>
<BiUser />
<p>{user.username}</p>
</div>
{ //this is where I render a list to show and make component
list.map((item)=>
<div key={item.id}>
{item.icon}
<p>{item.text}</p>
</div>)
}
</div>
);
};
export default UserModal;
this my code and for example I need to add an event on specific object that has id=5 in that list .
how can I do that
I don't know if there is some sort of built-in solution for this, but here is a simple workaround:
I changed a few things for simplicity's sake
The important part is the if statement with checks if item ID is 5 then if so adds a div with the desired event
function App() {
const list = [
,
{ id: 3, text: "comonent 3" },
{ id: 5, text: "comonent 5 (target)" }
];
return (
<>
<h1>Hello world<h1/>
{list.map((item) => (
<div key={item.id} style={{ backgroundColor: "red" }}>
<p>{item.text}</p>
{item.id == 5 ? (
<div
onClick={() => {
alert("This component has a event");
}}
>
{" "}
event
</div>
) : (
<></>
)}
</div>
))}
</>
);
}
const UserModal = (props) => {
const {user,setUser} = props ;
const myEvent = () => alert('event fired');
const list = [,{id:3,text:'گفت وگو ها',icon:<BsChat /> , event : myEvent},{id:5,text:'خروج',icon:<BiExit />},];
/this is my list for making navigation bar
return (
<div className={style.main}>
<div style={{bordeBottom:'1px solid black'}}>
<BiUser />
<p>{user.username}</p>
</div>
{ //this is where I render a list to show and make component
list.map((item)=>
<div key={item.id}>
{item.icon}
<p onClick={item.event}>{item.text}</p>
</div>)
}
</div>
);
};
export default UserModal;
list.map((item, i)=> (
item.id == 5 ?
<div onClick={handleClick} key={i}></div>
:
<div key={i}></div>
)

Unable to pass props from parent to child and save it in state of child component

I'm trying to develop a website for fetching GitHub data, but I'm having problem in updating the component that shows data Formdata component. It doesn't seem to be updating form some reasons.
App:
export default class App extends Component {
constructor(props){
super(props);
this.state = {
uname:'',
udata:'',
};
this.handleInput = this.handleInput.bind(this);
this.getUser = this.getUser.bind(this);
}
getUser(){
fetch(`https://api.github.com/users/${this.state.uname}`)
.then(response => response.json())
.then(data => this.setState({udata:data}))
.catch(error => console.error(error));
}
handleInput(event){
this.setState({
uname:event.target.value
});
}
render() {
return (
<div>
<Header></Header>
<Form handleInput={this.handleInput} uname={this.state.uname} getUser={this.getUser}></Form>
<Formdata udata={this.state.udata}></Formdata>
</div>
)
}
}
Form:
export default function Form(props) {
const {getUser, handleInput, uname} = props;
return (
<div className="form">
<input className="textbar" placeholder="Search for username" value={uname} onChange={handleInput} name="uname"></input>
<button className="button" onClick={getUser} >Search</button>
</div>
)
}
Formdata:
export default class Formdata extends Component {
constructor(props){
super(props);
this.state = {
follower:'',
following:'',
public_repos:'',
visit_page:'',
avatar:''
}
this.updateUser = this.updateUser.bind(this);
};
componentDidMount(props){
this.updateUser();
}
updateUser(){
this.setState({follower:this.props.udata.followers});
this.setState({following:this.props.udata.following});
this.setState({public_repos:this.props.udata.public_repos});
this.setState({visit_page:this.props.udata.url});
this.setState({avatar:this.props.udata.avatar_url});
console.log(this.props.udata);
}
render() {
return (
<div>
<img className="imge" src= {this.state.avatar} alt=" "></img>
<div className="details">
<div className="compon">Followers: {this.state.followers}</div>
<div className="compon">Following: {this.state.following}</div>
<div className="compon">public repos" {this.state.public_repos}</div>
</div>
<div className="urls">Page:{this.state.visit_page}</div>
</div>
)
}
}
I can't figure out how to update component Formdata on clicking search button in Form component.
Full Working App: StackBlitz
import React, { Component, useEffect } from "react";
import "./style.css";
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
uname: "",
udata: ""
};
this.handleInput = this.handleInput.bind(this);
this.getUser = this.getUser.bind(this);
}
getUser() {
fetch(`https://api.github.com/users/${this.state.uname}`)
.then(response => response.json())
.then(data =>
this.setState({ udata: data }, () => {
console.log(this.state.udata);
})
)
.catch(error => console.error(error));
}
handleInput(event) {
this.setState(
{
uname: event.target.value
},
() => {
console.log(this.state.uname);
}
);
}
render() {
return (
<div>
<Form
handleInput={this.handleInput}
uname={this.state.uname}
getUser={this.getUser}
/>
<Formdata udata={this.state.udata} />
</div>
);
}
}
const Form = props => {
const { getUser, handleInput, uname } = props;
return (
<div className="form">
<input
className="textbar"
placeholder="Search for username"
value={uname}
onChange={handleInput}
name="uname"
/>
<button className="button" onClick={getUser}>
Search
</button>
</div>
);
};
const Formdata = ({ udata }) => {
useEffect(() => {
console.log(JSON.stringify(udata.login));
}, [udata]);
return (
<div style={styles.card}>
{udata.login ? (
<div style={styles.cardImg}>
<div>
<img
style={styles.img}
className="imge"
src={udata?.avatar_url}
alt=" "
/>
</div>
<div className="details">
<div className="compon">Followers: {udata?.followers}</div>
<div className="compon">Following: {udata?.following}</div>
<div className="compon">Public repos: {udata?.public_repos}</div>
<div className="urls">Page: {udata?.url}</div>
</div>
</div>
) : (
<div>
<p>No Data Available</p>
</div>
)}
</div>
);
};
const styles = {
card: {
display: "flex",
flex: 1,
backgroundColor: "rgba(21,21,21,0.2)",
padding: 10,
marginTop: 10,
borderRadius: 5
},
cardImg: {
display: "flex",
flex: 1,
flexDirection: "row",
flexWrap: "wrap",
overflow: "hidden",
textOverflow: "ellipsis",
color: "rgba(0,0,0,0.7)"
},
img: {
marginRight: 10,
width: 100,
height: 100,
borderRadius: 10,
overflow: "hidden"
}
};
Do not copy props into state, use the props directly in your JSX:
div>
<img className="imge" src= {this.props.udata.avatar} alt=" "></img>
<div className="details">
<div className="compon">Followers: {this.props.udata.followers}</div>
<div className="compon">Following: {this.props.udata.following}</div>
<div className="compon">public repos" {this.props.udata.public_repos}</div>
</div>
<div className="urls">Page:{this.props.udata.visit_page}</div>
</div>
If you copy props into state, you are creating redundant copy of props and it is difficult to keep props and state in sync. And it is a React anti-pattern.
Just make sure this.props.udata is not undefined, it is ok if it is empty object {}. If it is undefined, put a check / conditional rendering.
anti-pattern-unconditionally-copying-props-to-state
Formdata.updateUser() isn't being called at any point. You probably just need to call it in componentDidMount():
export default class Formdata extends Component {
...
componentDidMount(props){
this.updateUser();
}
updateUser(){
this.setState({follower:this.props.udata.followers});
this.setState({following:this.props.udata.following});
this.setState({public_repos:this.props.udata.public_repos});
this.setState({visit_page:this.props.udata.url});
this.setState({avatar:this.props.udata.avatar_url});
console.log(this.props.udata);
}
...
}

Re-render component based on object updating

I have the following pattern
class List {
list: string[] = [];
showList() {
return this.list.map(element => <div>{element}</div>);
}
showOptions() {
return (
<div>
<div onClick={() => this.addToList('value1')}>Value #1</div>
<div onClick={() => this.addToList('value2')}>Value #2</div>
<div onClick={() => this.addToList('value3')}>Value #3</div>
<div onClick={() => this.addToList('value4')}>Value #4</div>
</div>
);
}
addToList(value: string) {
this.list.push(value);
}
}
class App extends Component {
myList: List;
constructor(props: any) {
super(props);
this.myList = new List();
}
render() {
<div>
Hey this is my app
{this.myList.showOptions()}
<div>{this.myList.showList()}</div>
</div>
}
}
It shows my options fine, and elements are added to the list when I click on it. However, the showList function is never called again from App, thus not showing any update.
How can I tell the main component to rerenders when List is updated ? I'm not sure my design pattern is good. My goal is to manage what my class displays inside itself, and just call the display functions from other components.
We should always use state to rerender react component.
Not sure what you want to accomplish exactly but hopefully this will give you a general idea what Jim means with using state:
const Option = React.memo(function Option({
value,
onClick,
}) {
return <div onClick={() => onClick(value)}>{value}</div>;
});
const Options = React.memo(function Options({
options,
onClick,
}) {
return (
<div>
{options.map(value => (
<Option
key={value}
value={value}
onClick={onClick}
/>
))}
</div>
);
});
class List extends React.PureComponent {
state = {
options: [1, 2, 3],
selected: [],
};
showList() {
return this.list.map(element => <div>{element}</div>);
}
add = (
value //arrow funcion to bind this
) =>
this.setState({
options: this.state.options.filter(o => o !== value),
selected: [...this.state.selected, value],
});
remove = (
value //arrow funcion to bind this
) =>
this.setState({
selected: this.state.selected.filter(
o => o !== value
),
options: [...this.state.options, value],
});
render() {
return (
<div>
<div>
<h4>options</h4>
<Options
options={this.state.options}
onClick={this.add}
/>
</div>
<div>
<h4>choosen options</h4>
<Options
options={this.state.selected}
onClick={this.remove}
/>
</div>
</div>
);
}
}
const App = () => <List />;
//render app
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Passing react function to children down, event.target empty

I'm struggling to grasp a react concept that to me is likely used all the time.
I have an app with a state.
I have a section below app.
Below section I have clickable tile that receives a function to update app status. This works, however the event.target appears to be null.
I'm passing the function to update the status all the way down from app as a prop.
How can I fix this / what am I missing?
import React, { Component } from 'react';
import './App.css';
const Section = ({ handleClick }) => {
return (
<div className="section">
Section
<Tile handleClick={handleClick} title="1" />
<Tile handleClick={handleClick} title="2" />
<Tile handleClick={handleClick} title="3" />
</div>
)
}
const Tile = ({ handleClick, title }) => {
return (
<div className="tile" onClick={handleClick}>
tile {title}
</div>
)
};
class App extends Component {
constructor(props) {
super(props);
this.state = {
modalOpen: false
};
}
openModal = () => {
this.setState({
modalOpen: true,
openedBy: ""
})
}
closeModal = (event) => {
this.setState({
modalOpen: false,
openedBy: event.target.title
})
}
render() {
return (
<div className="App">
<div>ModalOpen = {this.state.modalOpen.toString()}</div>
<div>Opened by = {this.state.openedBy}</div>
<Section handleClick={this.openModal}></Section>
<a href="#" onClick={this.closeModal}>Close modal</a>
</div>
);
}
}
export default App;
Thanks so much for pointer in the right direction!
You are not passing down a title prop to your Tile component, but your are passing down a number prop.
You can create a new function in the Tile component that calls the handleClick with the number, which you then use to set the openedBy in your App.
Example
const Section = ({ handleClick }) => {
return (
<div className="section">
Section
<Tile handleClick={handleClick} number="1" />
<Tile handleClick={handleClick} number="2" />
<Tile handleClick={handleClick} number="3" />
</div>
);
};
const Tile = ({ handleClick, number }) => {
return (
<div className="tile" onClick={() => handleClick(number)}>
tile {number}
</div>
);
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
modalOpen: false,
openedBy: ""
};
}
openModal = title => {
this.setState({
modalOpen: true,
openedBy: title
});
};
closeModal = () => {
this.setState({
modalOpen: false,
openedBy: ""
});
};
render() {
return (
<div className="App">
<div>ModalOpen = {this.state.modalOpen.toString()}</div>
<div>Opened by = {this.state.openedBy}</div>
<Section handleClick={this.openModal} />
<a href="#" onClick={this.closeModal}>
Close modal
</a>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
It seems to be working perfectly fine :
const Section = ({ handleClick }) => {
return (
<div className="section">
Section
<Tile handleClick={handleClick} number="1" />
<Tile handleClick={handleClick} number="2" />
<Tile handleClick={handleClick} number="3" />
</div>
)
}
const Tile = ({ handleClick, title }) => {
return (
<div className="tile" onClick={handleClick}>
tile {title}
</div>
)
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
modalOpen: false
};
}
openModal = event => {
console.log(event.target)
this.setState({
modalOpen: true,
openedBy: ""
})
}
closeModal = event => {
console.log(event.target)
this.setState({
modalOpen: false,
openedBy: event.target.title
})
}
render() {
return (
<div className="App">
<div>ModalOpen = {this.state.modalOpen.toString()}</div>
<div>Opened by = {this.state.openedBy}</div>
<Section handleClick={this.openModal}></Section>
<a href="#" onClick={this.closeModal}>Close modal</a>
</div>
);
}
}
ReactDOM.render(<App/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.5.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.5.2/umd/react-dom.production.min.js"></script>
<div id='root'>
However I assume that you want to pass down the title of the clicked element back into the handler. If so, I recommend using a curried function, with 2 sets of parameters, and setting the title variable as the first one :
openModal = title => event => {
console.log('Opened by : ', title, event.target)
this.setState({
modalOpen: true,
openedBy: ""
})
}
Your Tile component can now indicate which title it has by calling the function the first time :
const Tile = ({ handleClick, title }) => {
return (
<div className="tile" onClick={handleClick(title)}>
tile {title}
</div>
)
};
Working example :
const Section = ({ handleClick }) => {
return (
<div className="section">
Section
<Tile handleClick={handleClick} title="1" />
<Tile handleClick={handleClick} title="2" />
<Tile handleClick={handleClick} title="3" />
</div>
)
}
const Tile = ({ handleClick, title }) => {
return (
<div className="tile" onClick={handleClick(title)}>
tile {title}
</div>
)
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
modalOpen: false
};
}
openModal = title => event => {
console.log('Opened by : ', title)
this.setState({
modalOpen: true,
openedBy: ""
})
}
closeModal = event => {
this.setState({
modalOpen: false,
openedBy: event.target.title
})
}
render() {
return (
<div className="App">
<div>ModalOpen = {this.state.modalOpen.toString()}</div>
<div>Opened by = {this.state.openedBy}</div>
<Section handleClick={this.openModal}></Section>
<a href="#" onClick={this.closeModal}>Close modal</a>
</div>
);
}
}
ReactDOM.render(<App/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.5.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.5.2/umd/react-dom.production.min.js"></script>
<div id='root'>

Resources