React Components: Pass data between siblings on state change - reactjs

I am sharing an event in App comp between two child components
App Comp
class App extends Component {
constructor(props) {
super(props);
this.state = { A : 'good' };
}
SharedEvent () {
var newvalue = this.setState({A:'update'}, () => {
console.log(this.state);
alert(this.state.A)
});
}
render() {
return (
<div className="App">
<Content child1Event = {this.SharedEvent} text = {this.state.A}
child2Event = {this.SharedEvent} />
</div>
);
}
}
Parent comp
render(props) {
return (
<div className = "App">
<Subcontent whatever = {this.props.child1Event} name = {this.props.text} />
<Subcontent2 whatever = {this.props.child2Event} />
</div>
);
}
}
Child Comp
constructor(props) {
super(props);
this.state = { };
}
render() {
return (
<button id = 'btn' onClick = {this.props.whatever.bind(this , 'shared')} > {this.props.name} </button>
);
}
}
subcontent2 is same as subontent
I can successfully trigger sharedEvent from both components but it should change the name of button on setstate which it does not where am i wrong ???

the problem can be from one of these two issues:
First, you should replace your SharedEvent(){} function with SharedEvent = ()=>{...you function code} and it's because the scope has changed and if you are referring to this in your component for calling one of its functions, you should either use arrow functions or define them like you have done and bind them in your constructor to this which you have not done.
Second, the onClick event on button, restarts the page by its default behavior and everything refreshes as does your component state, and this might be the cause that you do not see the text change because the page refreshes and the state gets back to 'good', so try replacing your onClick function with this:
<button onClick={e => {e.preventDefault(); this.props.whatever();}}> {this.props.name} </button>

Related

React -- conditional render not clear input value

My code is
class Com extends React.Component {
constructor(props) {
super(props);
this.state = {is_clicked: false};
}
render() {
let sub_com1 = () => {
return (
<div>Input1:<input/></div>
);
};
let sub_com2 = () => {
return (
<div>Input2:<input/></div>
);
};
return (
<div>
<div>
{this.state.is_clicked ? sub_com1() : sub_com2()}
</div>
<button onClick={()=>{
let is_clicked=this.state.is_clicked;
this.setState({is_clicked: !is_clicked});
}}>
Click me
</button>
</div>
);
}
}
and the live display: codepen.
In this code, I use conditional rendering in Com's render method.
What I expect
Each time I click the button, the input area should be cleared since it is rendered to another component
What I met
Each time I click the button, the "input1" or "input2" label has changed, but the input area is not cleared.
To fix this issue you have to add key attributes to your input elements, change the code be like this and it will work:
class Com extends React.Component {
constructor(props) {
super(props);
this.state = {is_clicked: false};
}
render() {
let sub_com1 = () => {
return (
<div>Input1:<input key={1} id='A' /></div>
);
};
let sub_com2 = () => {
return (
<div>Input2:<input key={2} id='b' /></div>
);
};
return (
<div>
<div>
{this.state.is_clicked ? sub_com1() : sub_com2()}
</div>
<button onClick={()=>{
let is_clicked=this.state.is_clicked;
this.setState({is_clicked: !is_clicked});
}}>
Click me
</button>
</div>
);
}
}
ReactDOM.render(
<Com/>,
mountNode,
);
The following article discuss it in more depth and why its important to have key attribute for elements:
full article: keys-in-children-components-are-important
Key is not really about performance, it’s more about identity (which
in turn leads to better performance). Randomly assigned and changing
values do not form an identity Paul O’Shannessy

React - how to map buttons in a list to the object they are in?

I have the following class Component which reads data from localstorage.
The Localstorage has an array of objects. Those objects are rendered in a list as you can see below. In each list item there is a button I added in the code. If a user clicks the <ExtendButton /> I want to extend the {el.infoDays} of 7 days.
Can anyone help me with that, or at least with binding the button to the object it is in, so that if a user clicks the button I will get the whole object (where the button is in) displayed in the console.log?
I tried the following, I tried with e.target, this, etc. The onExtendBtnClick method is not well written.
let uniqid = require('uniqid');
class History extends Component {
state = {
loans: []
};
componentDidMount() {
const rawInfos = localStorage.getItem('infos');
const infos = JSON.parse(rawInfos);
this.setState({
infos: infos
});
}
render() {
const {infos} = this.state;
return (
<Container>
<Header />
<div>
<ul>
{infos.map((el) => (
<li key={uniqid()}>
<small>Requested date: {el.infoRequestTime}</small><br />
<div>
<span ><em>Requested amount</em><em>{el.infoAmount} €</em></span>
<span><em>In days</em><em>{el.infoDays}</em></span>
</div>
<spa>Give back: {el.infoCost} €</span>
<ExtendButton />
</li>
))}
</ul>
</div>
</Container>
);
}
}
export default History;
And I have also the button component:
class ExtendButton extends Component {
onExtendBtnClick = () => {
console.log(this)
};
render() {
return (
<button
className="extend-button"
onClick={this.onExtendBtnClick}
>
Extend for 1 week
</button>
);
}
}
export default ExtendButton;
Have your button component take in an onClick prop and set that on its own internal button:
class ExtendButton extends Component {
onExtendBtnClick = () => {
this.props.onClick();
};
render() {
return (
<button
className="extend-button"
onClick={this.onExtendBtnClick}
>
Extend for 1 week
</button>
);
}
}
Then just pass an onClick function to your component:
<ExtendButton onClick={() => {console.log(el)}} />

Why does the onClick only work the first time?

After the first onClick the modal displays. It is then removed after when clicking outside of it. However, despite the button not being a child of the modal, its onClick fails to update this.state.changed and remove the div. Why?
Codepen: https://codepen.io/Matt-dc/pen/KJYxqv
class Modal extends React.Component {
setWrapperRef = (node) => {
this.wrapperRef = node;
}
componentWillMount = () => {
document.addEventListener('mouseup', this.handleClickOutside);
}
componentDidMount = () => {
document.addEventListener('mouseup', this.handleClickOutside);
}
handleClickOutside = (e) => {
if (this.wrapperRef && !this.wrapperRef.contains(e.target)) {
this.props.close()
}
}
render() {
if(!this.props.changed) {
return null
}
return(
<div id="modal">
<div
id="ref"
style={myDiv}
ref={this.setWrapperRef}
>
{this.props.children}
</div>
</div>
);
}
}
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
changed: false,
}
this.handleChange = this.handleChange.bind(this);
}
handleChange = () => {
this.setState({
changed: !this.state.changed,
});
}
render(){
return(
<div id="app">
<input type="button" value="show" onClick={this.handleChange} />
<Modal changed={this.state.changed} close={this.handleChange} />
</div>
This is because of document.addEventListener('mouseup', this.handleClickOutside) in you Modal Component. Try by commenting -
document.addEventListener('mouseup', this.handleClickOutside) and it will work.
Also its better if you can render {this.state.changed.toString()} to get more details while debugging :)
It only looks like it does not close on button click, but actually it does close the modal and then change it immediately, again, because the handleChange function is fired two times.
The problem lies in your handleClickOutside logic, which that you trigger the handleChange function (passed via close props):
<Modal changed={this.state.changed} close={this.handleChange} />
The button you are clicking is outside the modal - that's why it is fired, again.

Passing API data from parent container to child component in React

I have a React container with a number of child components. One of which is supposed to be a modal that will show the user their name which is fetched from a user data api in the parent container. I should be able to pass the user data into the child with a prop, but must be missing something, as the display name does not show in the input as the value.
Parent Container
class ParentContainer extends React.Component {
constructor (props) {
super(props)
this.state = {
displayName: this.state.user.displayName
}
this.config = this.props.config
}
async componentDidMount () {
try {
const userData = await superagent.get(`/api/user`)
await this.setState({ user: userData.body })
console.log(userData.body.displayName) <===logs out user display name
} catch (err) {
console.log(`Cannot GET user.`, err)
}
}
render () {
return (
<div className='reviews-container'>
<ReviewForm
config={this.config} />
<ReviewList
reviews={reviews}
ratingIcon={this.ratingIcon}
/>
<DisplayNameModal
config={this.config}
displayName={this.displayName} />
</div>
)
}
}
export default ParentContainer
Child Component
class DisplayNameModal extends React.Component {
constructor (props){
super(props)
this.state = {
displayName: this.props.displayName
}
}
render (props) {
const {contentStrings} = this.props.config
return (
<div className='display-name-container' style={{ backgroundImage: `url(${this.props.bgImgUrl})` }}>
<h2 className='heading'>{contentStrings.displayNameModal.heading}</h2>
<p>{contentStrings.displayNameModal.subHeading}</p>
<input type="text" placeholder={this.props.displayName}/>
<button
onClick={this.submitName}
className='btn btn--primary btn--md'>
<span>{contentStrings.displayNameModal.button}</span>
</button>
<p>{contentStrings.displayNameModal.cancel}</p>
</div>
)
}
}
export default DisplayNameModal
I found that adding displayName: userData.body.displayName to setState and then wrapping the component in the parent with
{this.state.displayName &&
<div>
<DisplayNameModal
config={this.config}
displayName={this.state.displayName} />
</div>
}
works as the solution.
The prop should be passed by:
<DisplayNameModal
config={this.config}
displayName={this.state.displayName} />
where you are using:
<DisplayNameModal
config={this.config}
displayName={this.displayName} />
You have set the displayName on state in the parent, anything you refer to from state should be referred to as this.state.foo, where as any method on that component can be referred to as this.foo.
First of all, you fetch the data in wrong way, you can check it here:
componentDidMount () {
superagent.get(`/api/user`).then(res => this.setState({ user: res.body }))
}
the second one, initialize default state for displayName, for example, an empty string, it will be replaced when promise retrieves data data from the server:
constructor (props){
super(props)
this.state = {
displayName: ''
}
}
and pass this state as props to your child component:
render () {
return (
<div className='reviews-container'>
<ReviewForm
config={this.config} />
<ReviewList
reviews={reviews}
ratingIcon={this.ratingIcon}
/>
<DisplayNameModal
config={this.config}
displayName={this.props.displayName} />
</div>
)
}
in your child component, you can simply call this props:
<input type="text" placeholder={this.props.displayName}/>

Calling Event on Child Component in React [duplicate]

I'm making a very basic React app from teamtreehouse.com, and I'm constantly encountering
"TypeError: Cannot read property 'onPlayerScoreChange' of undefined"
even though I'm binding my functions correctly (I think)
'onPlayerScoreChange' Is a method in the Grandparent component which executes when a user hits a '+' or '-' button to change a player's score.
It would be really helpful if someone could explain what is wrong, because I think I am setting this.onPlayerScoreChange = this.onPlayerScoreChange.bind(this) in the great grandparent's constructor.
Parent component:
class App extends React.Component {
constructor(props) {
super(props);
this.onPlayerScoreChange = this.onPlayerScoreChange.bind(this)
this.state = {
initialPlayers: props.initialPlayers,
};
}
onPlayerScoreChange(delta, index) {
this.setState((prevState, props) => {
return {initialPlayers: this.prevState.initialPlayers[index].score += delta}
})
}
render() {
return(
<div className = "scoreboard">
<Header title = {this.props.title}/>
<div className = "players">
{this.state.initialPlayers.map(function(player, index) {
return(
<Player
name = {player.name}
score = {player.score}
key = {player.id}
index = {index}
onScoreChange = {this.onPlayerScoreChange}
/>
)
})}
</div>
</div>
)
}}
(Component has default props for title)
Child component:
class Player extends React.Component {
render() {
return(
<div className = "player">
<div className = "player-name">
{this.props.name}
</div>
<div className = "player-score">
<Counter score = {this.props.score} onChange = {this.props.onScoreChange} index = {this.props.index}/>
</div>
</div>
)
}}
Grandchild component:
class Counter extends React.Component {
constructor(props) {
super(props)
this.handleDecrement = this.handleDecrement.bind(this)
this.handleIncrement = this.handleIncrement.bind(this)
}
handleDecrement() {
this.props.onChange(-1, this.props.index)
}
handleIncrement() {
this.props.onChange(1, this.props.index)
}
render() {
return(
<div className = "counter">
<button className = "counter-action decrement" onClick = {this.handleDecrement}> - </button>
<div className = "counter-score"> {this.props.score} </div>
<button className = "counter-action increment" onClick = {this.handleIncrement}> + </button>
</div>
)}}
Thank you!
You have not done binding for the map function where you are using onScoreChange = {this.onPlayerScoreChange},
you can either use bind or arrow functions for binding
P.S. Binding is needed because the context of the map function is different from the React Component context and hence this inside this function won't be Referring to the React Components this and thus you can't access that properties of the React Component class.
With Arrow function:
{this.state.initialPlayers.map((player, index)=> {
return(
<Player
name = {player.name}
score = {player.score}
key = {player.id}
index = {index}
onScoreChange = {this.onPlayerScoreChange}
/>
)
})}
With bind
{this.state.initialPlayers.map(function(player, index) {
return(
<Player
name = {player.name}
score = {player.score}
key = {player.id}
index = {index}
onScoreChange = {this.onPlayerScoreChange}
/>
)
}.bind(this))}
Can also be done by passing second argument as this to map function as onClick event uses local this of map function which is undefined here and this currently refers to the global object.
{this.state.initialPlayers.map(function(player, index) {
return(
<Player
name = {player.name}
score = {player.score}
key = {player.id}
index = {index}
onScoreChange = {this.onPlayerScoreChange}
/>
)
}),this}
sometimes it is quite easy and it all depends on how you declare your loop
example you will get this error if you try to do something like this var.map(function(example, index) {}
but if you call the new function within the map with this
this.sate.articles.map(list =>
{<a onClick={() => this.myNewfunction()}>{list.title}</a>)}
the second loop will get you out of the undefined error
and dont forget to bind your new function
//This should be declared befor map function
const thObj = this;
this.sate.articles.map(list =>
{<a onClick={() => thObj.myNewfunction()}>{list.title})}

Resources