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
Related
I'm trying to make a toggle content button with React. But I can only get it to open, not to close when I click on it again. Can someone please take a look and let me know what I need to change within the code to accomplish it?
Here's what I have so far:
class Test extends React.Component {
constructor(props) {
super(props)
this.state = {
activeLocation: 0,
}
}
changeActiveLocation = (activeLocation) => {
this.setState({
activeLocation: activeLocation,
});
}
render() {
const activeLocation = company.locations[this.state.activeLocation];
return (
{company.locations.map((location, index) => (
<div className="test-item">
<div className="test-item-container" onClick={() => {this.changeActiveLocation(index)}}>
<div className="test-item-header">
<h3>Text goes here!</h3>
<a><FontAwesomeIcon icon={(this.state.activeLocation === index) ? 'times' : 'chevron-right'} /></a>
</div>
</div>
</div>
))}
)
}
}
Thank you!
You're setting the active location to be the same location that you've clicked already so the this.state.activeLocation === index is always true. I would refactor the locations to their own component with an isOpen state value that gets updated when the location is clicked. So like the following:
// test class
class Test extends React.Component {
constructor(props) {
super(props)
this.state = {
activeLocation: 0,
}
}
changeActiveLocation = (activeLocation) => {
this.setState({
activeLocation: activeLocation,
});
}
render() {
const activeLocation = company.locations[this.state.activeLocation];
return (
{company.locations.map((location, index) => (
<LocationItem location={location} onClick={() => this.changeActiveLocation(index)} />
))}
)
}
}
// LocationItem
class LocationItem extends React.Component {
state = { isOpen: false };
handleClick = () => {
this.setState(prevState => { isOpen: !prevState.isOpen});
// call parent click to set new active location if that's still needed
if(this.props.onClick) this.props.onClick;
}
render() {
return <div className="test-item">
<div className="test-item-container" onClick={this.handleClick}>
<div className="test-item-header">
<h3>Text goes here!</h3>
<a><FontAwesomeIcon icon={(this.state.isOpen ? 'times' : 'chevron-right'} /></a>
</div>
</div>
</div>
}
}
export default class player extends React.Component {
constructor(...args) {
super(...args);
this.state = {
shoot: 0
};
}
shootis the variable i'm trying to change in the function shooter, and display later in <h1>.
shooter() {
this.setState({ shoot: Math.floor(Math.random() * Math.floor(3)) });
console.log("hello");
}
render() {
return (
<div>
<h1>{this.state.shoot}</h1>
<button onClick={() => this.shooter}>shoot it</button>
</div>
);
}
}
the <h1> is not changing as the state changes, won't the state change as shooter() fires? and doesn't it update the <h1>.
any help much appreciated. :-)
Change the line
<button onClick={() => this.shooter}>shoot it</button>
To
<button onClick={() => this.shooter()}>shoot it</button>
Bind your class method shooter in your constructor, in order to use it like this onClick={this.shooter}.
You can find a further explanation here.
export default class player extends React.Component {
constructor(...args) {
super(...args);
this.state = {
shoot: 0
};
this.shooter = this.shooter.bind(this);
}
render() {
return (
<div>
<h1>{this.state.shoot}</h1>
<button onClick={this.shooter}>shoot it</button>
</div>
);
}
}
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>
I'm learning React and I'm trying to make a button which will change on click. It must be ether "Succeed" or "Not succeed" depending on the server's answer. This is what I've done so far. My question is - what the handleClick function must do? Should I use transition by toggling classes?
Thanks!
class Btn extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handlenClick.bind(this);
}
handleClick() {
???
}
render() {
const succeed = (
<div>
<ButtonToolbar>
<Button bsStyle="primary" bsSize="large">Succeed</Button>
</ButtonToolbar>
</div>
)
const notsucceed = (
<div>
<ButtonToolbar>
<Button bsStyle="primary" bsSize="large">Not succeed</Button>
</ButtonToolbar>
</div>
)
return (
<div onClick={this.handleClick.bind(this)}>
{this.state ? succeed : notsucceed}
</div>
)
}
};
You need to implement the state.
For example: https://codesandbox.io/s/313vmr23k6
class Btn extends React.Component {
constructor(props) {
super(props);
this.state = {
succeed: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(previousState => {
return {
succeed: !previousState.succeed
};
});
}
render() {
const succeed = (
<div>
<button bsStyle="primary" bsSize="large">Succeed</button>
</div>
)
const notsucceed = (
<div>
<button bsStyle="primary" bsSize="large">Not Succeed</button>
</div>
)
return (
<div onClick={this.handleClick.bind(this)}>
{this.state.succeed ? succeed : notsucceed}
</div>
)
}
};
You would call your api in there and set the state depending if the call fails or succeeeds. For this your api call must return a Promise. Something like:
handleClick() {
Api.call()
.then((response) => { this.setState({ succeed: true }); })
.catch((error) => { this.setState({ succeed: false }); })
}
Hey I am trying to create a simple to-do list and I have added the components necessary. However, the state is not being updated in the Title {this.state.data.length} and the TodoList {this.state.data}. A Codepen and the relevant code is below.
https://codepen.io/skasliwal12/pen/BREYXK
const TodoForm = ({addTodo}) => {
let input;
return (
<div>
<input ref={node => {input = node;}} />
<button onClick={(e) => {
e.preventDefault();
addTodo(input.value);
input.value='';
}}> +
</button>
</div>
);
};
const TodoList = ({todos}) => {
let todoNodes = todos.map(todo => {
return <li>{todo}</li>
});
return <div> {todoNodes} </div>;
}
const Title = ({todoCount}) => {
return (
<div>
<div>
<h1>To-do App {todoCount} items</h1>
</div>
</div>
);
}
class TestApp extends React.Component {
constructor(props) {
super(props);
this.state = { data : [] }
}
addTodo(val) {
let todo = {text: val}
this.state.data.push(todo);
this.setState = ({data: this.state.data});
console.log('state updated?')
}
render(){
return (
<div>
<Title todoCount={this.state.data.length}/>
<TodoForm addTodo={this.addTodo.bind(this)}/>
<TodoList todos={this.state.data}/>
</div>
);
}
}
ReactDOM.render(<TestApp />, document.getElementById('root'));
Quite simply it is important that you DO NOT MUTATE the state like you are doing here
this.state.data.push(todo);
It is hard to debug and adds side effects that are hard to keep track of. Following your approach you should copy the state to a var, update that var and then pass it as the new field in your state. Which could work but it's also something I do not recommend. A general good approach is to to compute the new state based on the old one
// this.state.data.push(todo); You can remove this line
this.setState(prevState => ({ data: prevState.data.concat(todo) }))
This will fix your issue and avoid mutating the state, which is something you should never do, only update the state using the setState method.
I also updated your TodoList which was not displaying properly, you have to access the text field of the todo in order to show something.
const TodoList = ({todos}) => {
let todoNodes = todos.map(todo => {
return <li>{todo.text}</li>
});
return <div> {todoNodes} </div>;
}
https://codepen.io/anon/pen/MmRVmX?editors=1010