Below is my partial code but my question is very simple, how can I get says data-id="1" to my function when user clicked on the li?
render(){
return(
<ul id="todo">
{this.state.items.map((item,i) =>
<li className='list-group-item' key={i} data-id={item.id}>{item.name}
<button onClick={//how to pass item.id to my function?}>X</button>
</li>
)}
</ul>
)
}
Since you are already using ES6 - might be a little cleaner to use an arrow function here:
render(){
return(
<ul id="todo">
{this.state.items.map((item,i) =>
<li className='list-group-item' key={i} data-id={item.id}>{item.name}
<button onClick={() => this.yourfunc(item.id)}>X</button>
</li>
)}
</ul>
)
}
You can use bind() to do this.
render(){
return(
<ul id="todo">
{this.state.items.map((item,i) =>
<li className='list-group-item' key={i} data-id={item.id}>{item.name}
<button onClick={yourfunc.bind(this, item.id)}>X</button>
</li>
)}
</ul>
)
}
Your function will receive item.id as the first parameter
In my opinion, you shouldn't declare functions, nor bind methods within render method. Neither of these:
onClick={(e) => this.handleClick(e, item.id)}
onClick={this.handleClick.bind(this, item.id)}
I know it's plenty of tutoriales showing that syntax. But there's also a considerable number of blog posts warning about why that's not a good idea. Basically, you are creating a new function on each render.
Go check the manual:
https://reactjs.org/docs/handling-events.html
And I'm aware that in the last two examples it does create functions on render. But react manual also shows this example and says:
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// This syntax ensures `this` is bound within handleClick
return (
<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
);
}
}
The problem with this syntax is that a different callback is created
each time the LoggingButton renders. In most cases, this is fine.
However, if this callback is passed as a prop to lower components,
those components might do an extra re-rendering. We generally
recommend binding in the constructor or using the class fields syntax,
to avoid this sort of performance problem.
BETTER SOLUTION
So, if you only need to pass one value, then check out the other examples in the manual. Basically you can bind the method in the constructor (or use an experimental syntax to avoid that step).
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
And how would you get the id/value that you are trying to get? See this example:
class App extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick({currentTarget}) {
console.log(currentTarget.value) // e.currentTarget.value would be equivalent
}
render() {
return (
<button value="here!" onClick={this.handleClick}>
Click me
</button>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
body {
padding: 5px;
background-color: white;
}
<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>
So, if you are using buttons or any form element (accepting a value), you may definitively consider this syntax.
You can do this as follows :
class Test extends React.Component {
constructor(props){
super(props);
this.state = {
items: [
{item: "item", id: 1},
{item1: "item1", id: 2}
]
}
}
handleClick(id, e){
alert(id);
}
render(){
return(
<ul id="todo">
{this.state.items.map((item,i) =>
<li className='list-group-item' key={i} data-id={item.id}>{item.name}
<button onClick={this.handleClick.bind(this, item.id)}>X</button>
</li>
)}
</ul>
)
}
}
React.render(<Test />, document.getElementById('container'));
Here is jsfiddle.
Mayid is correct that it is not good to declare or bind functions in render method if the function is passed into other component as a props.
Unfortunately currentTarget didn't work for me.
I have used getAttribute function of event.target. This doesn't cause unnecessary rerenders.
class App extends React.Component {
handleClick = (event) => {
console.log(event.target.getAttribute('index'))
}
render() {
return (
<button index="1" onClick={this.handleClick}>
Click me
</button>
);
}
}
Below is a running sample;
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [{
id: 0,
name: "Buy milk"
}, {
id: 1,
name: "Write unit tests"
}, {
id: 2,
name: "Cook a meal"
}]
}
this.handleClick = this.handleClick.bind(this);
}
handleClick(value) {
console.log(`${value} clicked`);
}
renderTodos() {
return this.state.items.map((item, idx) => {
return ( < li className = 'list-group-item'
key = {
idx
} > {
item.name
} < button onClick = {
() => this.handleClick(item.id)
} > X < /button>
</li >
)
})
}
render() {
return ( < ul id = "todo" > {
this.renderTodos()
} < /ul>
)
}
}
ReactDOM.render(
<App/ > ,
document.getElementById('react_example')
);
<!DOCTYPE html>
<html>
<head>
<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>
<meta charset="utf-8">
</head>
<body>
<div id="react_example"></div>
</body>
</html>
Related
I'm learning React and I'm trying to make a simple application: you click on a button and it increments a counter. I've prepared two components, ClickCounter and ClickButton, but I'm not sure how to connect them together. I've read different tutorials but they expect my components to be Parent/Child - is there something I'm completely missing from a architectural perspective?
class ClickCounter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
render() {
return <h1>{this.state.count}</h1>;
}
}
function ClickButton(props) {
function handleClick(e) {
e.preventDefault();
console.log("clicked");
// increment the ClickCounter..how?
}
return (
<button id="btn" onClick={handleClick}>Click me</button>
);
}
function Container() {
return (
<div>
<ClickCounter />
<ClickButton />
</div>
);
}
const root = document.getElementById("root");
ReactDOM.render(<Container />, root);
A common technique for when two sibling components need to share some state is to lift the state up to the first common ancestor (Container in this case) and pass down the state and state-altering functions as props to the children.
Example
function ClickCounter(props) {
return <h1>{props.count}</h1>;
}
function ClickButton(props) {
return (
<button id="btn" onClick={props.handleClick}>Click me</button>
);
}
class Container extends React.Component {
state = { count: 0 };
onClick = () => {
this.setState(prevState => {
return { count: prevState.count + 1 };
});
};
render() {
return (
<div>
<ClickCounter count={this.state.count} />
<ClickButton handleClick={this.onClick} />
</div>
);
}
}
ReactDOM.render(<Container />, 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>
I'm new to ReactJS and I would like to communicate between my components.
When I click an image in my "ChildA" I want to update the correct item image in my "ChildB" (type attribute in ChildA can only be "itemone", "itemtwo", "itemthree"
Here is what it looks like
Parent.js
export default class Parent extends Component {
render() {
return (
<div className="mainapp" id="app">
<ChildA/>
<ChildB/>
</div>
);
}
}
if (document.getElementById('page')) {
ReactDOM.render(<Builder />, document.getElementById('page'));
}
ChildA.js
render() {
return _.map(this.state.eq, ecu => {
return (
<img src="../images/misc/ec.png" type={ecu.type_eq} onClick={() => this.changeImage(ecu.img)}/>
);
});
}
ChildB.js
export default class CharacterForm extends Component {
constructor(props) {
super(props);
this.state = {
items: [
{ name: "itemone" image: "defaultone.png"},
{ name: "itemtwo" image: "defaulttwo.png"},
{ name: "itemthree" image: "defaultthree.png"},
]
};
}
render() {
return (
<div className="items-column">
{this.state.items.map(item => (<FrameCharacter key={item.name} item={item} />))}
</div>
);
}
}
I can retrieve the image on my onClick handler in my ChildA but I don't know how to give it to my ChildB. Any hints are welcomed, thanks you!
What you need is for Parent to pass an event handler down to ChildA which ChildA will call when one of the images is clicked. The event handler will call setState in Parent to update its state with the given value, and then Parent will pass the value down to ChildB in its render method.
You can see this working in the below example. Since I don't have any actual images to work with—and to keep it simple—I've used <button>s instead, but the principle is the same.
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
clickedItem: 'none',
};
}
render() {
return (
<div>
<ChildA onClick={this.handleChildClick}/>
<ChildB clickedItem={this.state.clickedItem}/>
</div>
);
}
handleChildClick = clickedItem => {
this.setState({ clickedItem });
}
}
const items = ['item1', 'item2', 'item3'];
const ChildA = ({ onClick }) => (
<div>
{items.map(name => (
<button key={name} type="button" onClick={() => onClick(name)}>
{name}
</button>
))}
</div>
);
const ChildB = ({clickedItem}) => (
<p>Clicked item: {clickedItem}</p>
);
ReactDOM.render(<Parent/>, document.querySelector('div'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.development.js"></script>
<div></div>
I'd like to add a new input everytime the plus icon is clicked but instead it always adds it to the end. I want it to be added next to the item that was clicked.
Here is the React code that I've used.
const Input = props => (
<div className="answer-choice">
<input type="text" className="form-control" name={props.index} />
<div className="answer-choice-action">
<i onClick={props.addInput}>add</i>
<i>Remove</i>
</div>
</div>
);
class TodoApp extends React.Component {
constructor(props) {
super(props);
this.state = {
choices: [Input]
};
}
addInput = index => {
this.setState(prevState => ({
choices: update(prevState.choices, { $splice: [[index, 0, Input]] })
}));
};
render() {
return (
<div>
{this.state.choices.map((Element, index) => {
return (
<Element
key={index}
addInput={() => {
this.addInput(index);
}}
index={index}
/>
);
})}
</div>
);
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"));
<div id="app"></div>
<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>
I must admit this get me stuck for a while but there was a problem with how react deals with key props. When you use an index as a key it doesn't work. But if you make sure inputs will always be assigned the same key even when the list changes it will work as expected:
const Input = props => (
<div className="answer-choice">
<input type="text" className="form-control" name={props.index} />
<div className="answer-choice-action">
<i onClick={props.addInput}>add </i>
<i>Remove</i>
</div>
</div>
);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
choices: [],
nrOfElements: 0
};
}
addInput = index => {
this.setState(prevState => {
const choicesCopy = [...prevState.choices];
choicesCopy.splice(index, 0, `input_${prevState.nrOfElements}`);
return {
choices: choicesCopy,
nrOfElements: prevState.nrOfElements + 1
};
});
};
componentDidMount() {
this.addInput(0);
}
render() {
return (
<div>
{this.state.choices.map((name, index) => {
return (
<Input
key={name}
addInput={() => {
this.addInput(index);
}}
index={index}
/>
);
})}
</div>
);
}
}
Some reference from the docs:
Keys should be given to the elements inside the array to give the
elements a stable identity...
...We don’t recommend using indexes for keys if the order of items may
change. This can negatively impact performance and may cause issues
with component state.
I'm making a simple todo app, where i have put in the logic to edit and delete the todos as well. I'm trying to update the parent state from child component but when i'm trying to click on delete it is throwing me an error e.preventDefault() is not a function and it is removing all of the todos here are the components:
PARENT
export default class App extends React.Component {
constructor(props){
super(props);
this.state = {
listArr: [],
}
}
deleteTodos(i) {
var lists = this.state.listArr;
lists.splice(i, 1);
this.setState({listArr: lists})
}
render() {
.......
<ToDoList {...this.state} passDeleteTodos={this.deleteTodos} />
......
}
CHILD
export class ToDoList extends React.Component {
constructor(props) {
super(props);
this.state = {
editing: false,
};
handleDelete(e, i) {
e.preventDefault();
this.props.passDeleteTodos()
}
renderDisplay() {
return(
<div>
{
this.props.listArr.map((list,i) => {
return(
<div key={i} index={i} ref="text">
<li>{list}
<div style={{float: 'right'}}>
<button className="btn btn-danger btn-xs glyphicon glyphicon-trash"
onClick={() => this.handleDelete(i)}
/>
</div>
</div>
</div>
You need to pass the event object to handleDelete function when you make use of Arrow function as done in your implementation.
You can think of an arrow function like a function that calls another function to which you need to pass the arguments. Event object is a parameter to the arrow function and you indeed need to pass this on to the handleDelete function
onClick={(e) => this.handleDelete(e, i)}
However after this change you still need to bind the deleteTodos function in the parent, since the context of this inside this function won't be that of the React class component, you can do it like
deleteTodos = (i) => {
var lists = this.state.listArr;
lists.splice(i, 1);
this.setState({listArr: lists})
}
or
constructor(props){
super(props);
this.state = {
listArr: [],
}
this.deleteTodos = this.deleteTodos.bind(this);
}
I change e.preventDefault() => e.preventDefault and bind the function.
Example
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
listArr: [],
}
this.deleteTodos = this.deleteTodos.bind(this)
}
handleDelete(e, i) {
e.preventDefault;
this.props.passDeleteTodos()
...
}
render() {
return(
<div>
{
this.props.listArr.map((list,i) => {
return(
<div key={i} index={i} ref="text">
<li>{list}
<div style={{float: 'right'}}>
<button className="btn btn-danger btn-xs glyphicon glyphicon-trash"
onClick={(e,i) => this.handleDelete(e,i)}
/>
</div>
</div>
)}
}
</div>
You are not sending e to the correspondent method.
You could also bind the event
onClick={this.handleDelete.bind(this, i)}
Same applies for deleteTodos in the App component.
Either way you can use the same approach or bind it in the constructor:
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
listArr: [],
}
this.deleteTodos = this.deleteTodos.bind(this)
}
...
}
doesn't behave the same way as an so you can't expect the same preventDefault call.
But your problem is you in bind the order of params change. So you're binded param becomes first in the function. See my snippet below.
const App = () => {
const _click = (externalVar, e) => {
console.log("PARAMS", externalVar, e);
};
const externalVar = 1
return (
<button onClick={_click.bind(undefined, externalVar)}>click me</button>
);
};
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>
Like it says here
fun.bind(thisArg[, arg1[, arg2[, ...]]])
arg1, arg2, ... Arguments to prepend to arguments provided to the
bound function when invoking the target function.
arrow function in react doesn't need to bind to this.
But during call to the functions, for example to call this function handleDelete
handleDelete(e, i) {
e.preventDefault();
this.props.passDeleteTodos()
}
we will use synatx as:
handleDelete.bind(i)
handleDelete(e, i) {
e.preventDefault();
this.props.passDeleteTodos()
...
}
onClick={(e,i) => this.handleDelete(e,i)}
if the above code is not working properly try this.
handleDelete(i) {
this.props.passDeleteTodos()
...
}
onClick={(e,i) => {e.preventDefault(); this.handleDelete(i)}}
Is there any way to add dynamical li element into my ul list ?
I'd like add my li by clicking the button. Here is example code
class Component1 extends React.Component {
constructor() {
super();
}
add() {
let ul = document.getElementById('mylist');
let li = document.createElement('li');
li.appendChild(document.createTextNode({some_variables});
ul.appendChild(li);
}
render() {
return (
<a href="#" onClick={() => this.add()}>Add</a>
<ul id="mylist">
/* dynamic list ITEM */
</ul>
);
}
}
ReactDOM.render(<Component1 />, document.getElementById('root'));
Of course current function add() doesn't work on React
When using react we are not "touching" the DOM as we usually do with other libraries (like jQuery).
One of the best and core features of react is the virtual DOM, the Reconciliation & diffing algorithm
React builds and maintains an internal representation of the rendered
UI. It includes the React elements you return from your components.
This representation lets React avoid creating DOM nodes and accessing
existing ones beyond necessity, as that can be slower than operations
on JavaScript objects. Sometimes it is referred to as a “virtual DOM”
In react you create components (functions) that renders / returns a jsx (markup).
A simple example to your scenario could be:
const ListItem = ({ value, onClick }) => (
<li onClick={onClick}>{value}</li>
);
const List = ({ items, onItemClick }) => (
<ul>
{
items.map((item, i) => <ListItem key={i} value={item} onClick={onItemClick} />)
}
</ul>
);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
inputValue: '',
fruites: ['Apple', 'Banana', 'Orange']
};
}
onClick = () => {
const { inputValue, fruites } = this.state;
if (inputValue) {
const nextState = [...fruites, inputValue];
this.setState({ fruites: nextState, inputValue: '' });
}
}
onChange = (e) => this.setState({ inputValue: e.target.value });
handleItemClick = (e) => {console.log(e.target.innerHTML)}
render() {
const { fruites, inputValue } = this.state;
return (
<div>
<input type="text" value={inputValue} onChange={this.onChange} />
<button onClick={this.onClick}>Add</button>
<List items={fruites} onItemClick={this.handleItemClick} />
</div>
);
}
}
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>