This should be pretty simple, but I can't figure out how to do it.
I have a component with multiple buttons, each with a "count" value, set with state. When a user clicks, the count goes up.
Right now, when I click one of the buttons, both counters change. How can I make it so only the div that was clicked updates, using the same state?
Edit: I don't want to have different counts, as I'd like for this component to render buttons dynamically. What if I don't know how many buttons I'll have at first?
class Block extends React.Component {
state = {
count: 0
};
handleClick = e => {
const count = this.state.count;
this.setState({ count: count + 1 });
};
render() {
return (
<div>
<button className="block" onClick={this.handleClick}>
<div className="counter">{this.state.count}</div>
</button>
<button className="block" onClick={this.handleClick}>
<div className="counter">{this.state.count}</div>
</button>
</div>
);
}
}
This is more of an issue of learning how to think in react.
If you need to be able to reuse a piece of functionality like a counter, you can make it its own component and have it manage its own state. Then you can reuse it wherever you need.
Here's an example:
class Counter extends React.Component {
state = {
count: 0
};
handleClick = () => {
// Use updater function when new state is derived from old
this.setState(prev => ({ count: prev.count + 1 }));
};
render() {
return (
<button className="block" onClick={this.handleClick}>
<div className="counter">{this.state.count}</div>
</button>
);
}
}
// Now you can use it dynamically like this:
class Block extends React.Component {
render() {
return (
<div>
<div>There are 4 counter component instances that each manage their own state.</div>
{[1,2,3,4].map(v => <Counter />)}
</div>
);
}
}
ReactDOM.render(<Block />, 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>
you should define two state and when press each button update the current state and you can render the current state in the dome like this
state = {
firstCount: 0,
secondCount: 0
}
and write your action (function) to handle update state like this
handleUpdateCount = stateName => {
this.setState({
[stateName]= this.state[stateName] + 1
})
}
then you should called this function like this =>
this.handleUpdateCount('firstCount')
If your buttons are dynamic you can set your state to be an array and update the relevant index
class Block extends React.Component {
state = [];
handleClick = index => {
this.setState(state => {
const newState = [...state]; //keep state immutable
!newState[index] && (newState[index] = 0)
newState[index]++
return newState
});
};
render() {
return (
<div>
{[1,2,3].map((value, index) => <button className="block" onClick={() => this.handleClick(index)}>
<div className="counter">{this.state[index]}</div>
</button>)}
</div>
);
}
}
You have to use another value to update function when new state is derived from old state (like increment)
import React, { Component } from 'react'
export class Ref3 extends Component {
constructor(props) {
super(props)
this.state = {
count:0
}
}
//use prevState to help you update the old value to a new one
clickHandler=()=>{
this.setState((prevState=>({
count:prevState.count+1
})))
}
render() {
return (
<div>
<button onClick={this.clickHandler}>Click To Count</button>
{this.state.count}
</div>
)
}
}
export default Ref3
Related
The question is probably rather unclear, but i did not how to formulate it, maybe that was the reason why i was not able to find solution to this puzzle i have. anyway, here is an example of what i want to accomplish:
<Calendar
tileContent={({ activeStartDate, date, view }) =>
this.renderGames(date, view)
}
/>
This is an example from npm package react-calendar, but i am sure you know what i mean. The param tileContent gets passed function that already has destructured object, and then i run my own function with data i get from that function.
I was thinking that this was done by executing function in child where i would pass an object (or single param, i just use object as an example).
I think what you're looking for are Render Props, not just executing function in parent with args (even though render props do this as well). It would appear your example is using Render Props specifically.
There are some good examples online of using render props in React, also referred to as "Wrapper Components", etc..
An example could be something like:
const { render } = ReactDOM;
class CounterWrapper extends React.Component {
state = {
count: 0
};
increment = () => {
const { count } = this.state;
return this.setState({ count: count + 1 });
};
decrement = () => {
const { count } = this.state;
return this.setState({ count: count - 1 });
};
render() {
const { count } = this.state;
return (
<React.Fragment>
{this.props.wrapperContent({
increment: this.increment,
decrement: this.decrement,
count
})}
</React.Fragment>
);
}
}
class App extends React.Component {
renderApp = (cnt, inc, dec) => {
return (
<div>
<h1>Render Props Counter Example</h1>
<div>
<p>{cnt}</p>
<button type="button" onClick={() => inc()}>
Increment
</button>
<button type="button" onClick={() => dec()}>
Decrement
</button>
</div>
</div>
)
};
render() {
return (
<CounterWrapper
wrapperContent={({ count, increment, decrement }) =>
this.renderApp(count, increment, decrement)
}
/>
);
}
}
render(<App />, document.body);
<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>
It sounds like you want to execute a function that's in the parent component, from a child component with arguments passed from the child.
Here is an example:
const ParentComponent = () => {
const handleClick = (args) => {
console.log(args)
}
return (
<div>
<ChildComponent onClick={handleClick} />
</div>
)
}
const ChildComponent = ({onClick}) => {
const val = 5;
return (
<div>
<button onClick={() => handleClick(val)} name="Click">Click Me</button>
</div>
)
}
This hsould render the child component which is just a button, with an event handler that is sent from the parent. When you click the button, you should get a console log of 5, which is coming from the parent. This is how you would propgate values from the child, up to the parent.
Expecting effect: click <li> --> take index --> send this index to component Watch.
When I click <li>, I grab the index and move it to theWatch component. However, when I click the second li it returns the index of the one I clicked for the first time. I think this is because it updates this index via componentDidMount. How can I reference this index after componentDidMount?
Todo
class Todo extends Component {
render () {
return (
<div className = "itemTodos" onClick={()=> this.props.selectTodo(this.props.index)}>
</div>
)
}
}
export default Todo;
App
class App extends Component {
constructor(){
super();
this.state {
selectedTodoIndex: index
}
}
selectTodo = (index) => {
this.setState({
selectedTodoIndex: index
})
}
render () {
return (
<div>
<ul>
{
this.state.todos
.map((todo, index) =>
<Todo
key={index}
index={index}
todo={todo}
selectTodo ={this.selectTodo}
/>
)
}
</ul>
<Watch
selectedTodoIndex = {selectedTodoIndex}
/>
</div>
)
}
}
export default App;
Watch
class Watch extends Component {
constructor(){
super();
this.state = {
selectIndex: null
}
}
componentDidMount() {
this.setState({
selectIndex: this.props.selectedTodo
});
}
render () {
return (
<div>
</div>
)
}
}
First of all you you use selectedTodoIndex in
<Watch
selectedTodoIndex = {selectedTodoIndex}
/>
but it not specified in your render code. Add
const {selectedTodoIndex} = this.state;
in render function.
Second, use componentDidUpdate in Watch for update inner state on props update:
class Watch extends Component {
constructor(){
super();
this.state = {
selectIndex: null
}
}
componentDidMount() {
this.setState({
selectIndex: this.props.selectedTodo
});
}
componentDidUpdate (prevProps) {
if (prevProps.selectedTodo !== this.props.selectedTodo)
this.setState({
selectIndex: this.props.selectedTodo
});
}
render () {
return (
<div>
</div>
)
}
}
If i am not wrong your Todo component is in watch??. So Watch component should be like this :
render () {
return (
<div>
<Todo index={this.state.selectedIndex} selectedTodo={this.props.selectedTodoIndex}/>
</div>
)
}
Here i made codesandbox of this code . Feel free to checkout and let me know if you any doubt. Code link : https://codesandbox.io/s/frosty-chaplygin-ws1zz
There are lot of improvements to be made. But I believe what you are looking for is getDerivedStateFromProps lifeCycle method in Watch Component. So the code will be:
getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.selectedTodoIndex !== prevState.selectedTodoIndex) {
return { selectIndex: nextProps.selectedTodoIndex }
}
}
This will check if the selected index has changed in App Component, if yes it will update the state in Watch Component.
I am in the process of learning React and Redux. Currently I am working on a project where I need to append a component on button click.
New Component should be added down the previous component
Previously added component contains the data added and it should not be refreshed while adding a new component.
I tried to search but all the solutions are recommending to use a List and incrementing the count on every click.
This is my requirement diagram:
Update:
I have added my code which I tried in the below JS Fiddle.
While appending the new component, the data modified in the existing component should be retained.
https://jsfiddle.net/np7u6L1w/
constructor(props) {
super(props)
this.state = { addComp: [] }
}
addComp() { // Onclick function for 'Add Component' Button
//this.setState({ addComp: !this.state.addComp })
this.setState({
addComp: [...this.state.addComp, <Stencil />]
});
}
render() {
return (
<div>
<div class="contentLeft"><h2>Workflows:</h2>
<Stencil />
{this.state.addComp.map((data, index) => {
{ data }
})}
</div>
<div class="contentRight" >
<button name="button" onClick={this.addComp.bind(this)} title="Append new component on to the end of the list">Add Component</button>
</div>
<div class="clear"></div>
</div>
)
}
Code is Updated:
You can do something like that
// New state
this.state = {
appendedCompsCount: 0
}
// Outside render()
handleClick = () => {
this.setState({
appendedCompsCount: this.state.appendedCompsCount + 1
})
}
getAppendedComponents = () => {
let appendedComponents = [];
for (let i = 0; i < this.state.appendedCompsCount; i++) {
appendedComponents.push(
<AppendedComponents key={i} />
)
}
return appendedComponents;
}
// In render()
<button onClick={this.handleClick}>Click here</button>
{
this.getAppendedComponents()
}
maybe when added new child, you want animation to work.
this is the best method react-transition-group
example: https://reactcommunity.org/react-transition-group/transition-group
My Parent class has two children
Counter component has state 'counter' which increments by the second;
class Counter extends Component {
constructor(props) {
super(props)
this.resetCount = this.resetCount.bind(this);
this.state = {
count : 0
}
}
resetCount() {
this.setState({
count : 0
});
}
componentDidMount() {
setInterval(() => {
this.setState({
count: this.state.count + 1
});
}, 1000);
}
render() {
const {count} = this.state;
const {color,size} = this.props;
return (
<Text style={{color, fontSize: size}}>{count}</Text>
);
}
}
In the Button Component, I have an onpress thing
<Button
onPress={resetCount}
title="Reset COunt"
color="#841584"
/>
In my main Parent Class I render
<Counter color={'green'} size={90} />
<Button/>
But I'm getting an error
'can't find variable resetCount' in App.js
You have to use 'this.resetCount' when using 'Button' inside Counter.render()
<Button
onPress={this.resetCount}
title="Reset COunt"
color="#841584"
/>
If Button is its own Component as mentioned you have to inherit the function onPress
Component Button
<Button onPress={this.props.onResetCount} ... />
Component Counter
render(){
return (
<Text style={{color, fontSize: size}}>{count}</Text>
<Button onResetCount={this.resetCount} title="Reset Count" color="... />
);
)
}
More detailed: https://reactjs.org/docs/faq-functions.html
This is due to Button not being able to access the class method inside its sibling Counter component. If your reorganise your code a little by moving the shared methods to the parent component you can a) achieve what you want, and b) make your code a little simpler. In other words make Counter the main component made up of two smaller dumb components / pure functions.
// No need for a component, just a function that returns
// a styled div
function Text({ count }) {
return <div>{count}</div>;
}
// Another function to return a button
function Button({ resetCount, text }) {
return <button onClick={resetCount}>{text}</button>;
}
// The main component that keeps the state which it passes
// to the dumb Text component
class Counter extends React.Component {
constructor() {
super()
this.state = { count: 0 };
this.resetCount = this.resetCount.bind(this);
}
componentDidMount() {
setInterval(() => {
this.setState({
count: this.state.count + 1
});
}, 1000);
}
resetCount() {
this.setState({ count: 0 });
}
render() {
return (
<div>
<Text count={this.state.count} />
<Button resetCount={this.resetCount} text="Reset count" />
</div>
)
}
}
ReactDOM.render(
<Counter />,
document.getElementById('container')
);
DEMO
You get the error because you can't do onPress={resetCount} this way. It is searching for the variable. But you don't have a variable, it's a function. So you should use this.resetCount if you want to access the function resetCount().
Here's an example how you can access the function of your parent component from the button in the child component:
// Parent component:
resetCount() {
// your code
}
render() {
return(
<Button resetCount={this.resetCount} /* your other stuff */ />
);
}
// Button component:
<button onPress={this.props.resetCount}>Click me</button>
Note: You can't update a sibling this way. You should move your functions from <Counter/> to your parent component.
So I am getting my hands dirty on React and I can't seem to figure out this simple problem (probably because of lack of sleep)
I want to add elements (or divs) inside the render on the fly when I click "Add Row".
How would I go on about it? Do I need to keep it in an array and within the render function, I will have to map it?
class SimpleExample extends React.Component {
constructor(props) {
super(props)
this.handleAddingDivs = this.handleAddingDivs.bind(this)
}
handleAddingDivs() {
const uniqueID = Date.now()
return (
<div>
This is added div! uniqueID: {uniqueID}
</div>
)
}
render() {
return (
<div>
<h1>These are added divs </h1>
<button className="btn-anchor-style add-row-link" type="button" onClick={this.handleAddingDivs}>{'Add Row'}</button>
</div>
)
}
}
Let's say you want to add multiple divs, so maintain a state variable for that, count or any other data (you can use any array also and store the unique value of all the divs), then use map or any other loop to create the divs for that.
Check this working snippet:
class SimpleExample extends React.Component {
constructor(props) {
super(props);
this.state = {count : 0}
this.handleAddingDivs = this.handleAddingDivs.bind(this)
}
handleAddingDivs() {
this.setState({count: this.state.count + 1})
}
renderDivs(){
let count = this.state.count, uiItems = [];
while(count--)
uiItems.push(
<div>
This is added div! uniqueID: {count}
</div>
)
return uiItems;
}
render() {
return (
<div>
<h1>These are added divs </h1>
<button className="btn-anchor-style add-row-link" type="button" onClick={this.handleAddingDivs}>{'Add Row'}</button>
{this.renderDivs()}
</div>
)
}
}
ReactDOM.render(<SimpleExample/>, document.getElementById('app'))
<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 ='app'/>
Try the code below. Whenever you click the button, a unique id is generated and stored in state.uids. In render(), added divs are rendered according to state.uids.
class SimpleExample extends React.Component {
constructor(props) {
super(props)
this.handleAddingDivs = this.handleAddingDivs.bind(this)
this.state = {uids:[]}
}
handleAddingDivs() {
let curr = this.state.uids;
const uniqueID = Date.now()
this.setState({uids:[...curr, uniqueID]});
}
render() {
return (
<div>
<h1>These are added divs </h1>
<button className="btn-anchor-style add-row-link" type="button" onClick={this.handleAddingDivs}>{'Add Row'}</button>
{ this.state.uids.map((uid, idx)=>
<div key={uid}>This is added div! uniqueID: {uid}</div>
)}
</div>
)
}
}
ReactDOM.render(<SimpleExample/>, document.getElementById('app'))
<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 ='app'/>
yes you will need to store data about new divs somewhere...
this is what flux/redux is sometimes used for: you store all data you need to render in Store and then you know what to render.
But if you whant this using only React, then use state !
Your state should be like this in your case:
{
addedDivs: [
{uniqueId: 123},
{uniqueId: 754},
]
}
then in render you will map it (don't forget to add key)
this.state.addedDivs.map((item) = > {return (<div key={item.uniqueId}></div>) })
and onClick you should just add some, using setState:
this.setState((prevState, props) => {
var addedDivs = prevState.addedDivs;
var uniqueId = Date.now();
addedDivs.push({uniqueId: uniqueId});
return {addedDivs: addedDivs}
});
You are thinking of terms of DOM manipulation/jQuery. When you use React, you need think in terms of data, and how it relates to your DOM.
In your case, you need to:
Update your component state with a new row, everytime there is a click to the 'Add Row" button, in the handleAddingDivs() method
Render rows based on the state, in the render() method
class SimpleExample extends React.Component {
constructor(props) {
super(props)
this.handleAddingDivs = this.handleAddingDivs.bind(this)
}
//Modify state of component when 'Add Row' button is clicked
handleAddingDivs() {
const uniqueID = Date.now();
this.setState({rows: this.state.rows.concat(uniqueId)});
}
//The `render()` function will be executed everytime state changes
render() {
return (
<div>
<h1>These are added divs </h1>
//The rows are rendered based on the state
{this.state.rows.map(function(uniqueId) {
return (
<div key={item.uniqueId}>This is added div! uniqueID: {uniqueID}</div>
)
}}
<button className="btn-anchor-style add-row-link" type="button" onClick={this.handleAddingDivs}>{'Add Row'}</button>
</div>
)
}
}