Component Doesn't Update once the Parent component's state is updated - javascript

import React, {Component} from 'react';
import "./DisplayCard.css";
class DisplayCard extends Component {
runArray = (array) => {
for (var i = 0; i<array.length; i++) {
return <div>{array[i].task}</div>
}
}
renderElements = (savedTasks) =>{
if (savedTasks.length === 0) {
return <div className="noTasks"> <p>You have no saved tasks.</p> </div>
} else {
return this.runArray(savedTasks)
}
}
render() {
return (
<div className="DisplayCardContainer">
{this.renderElements(this.props.saved)}
</div>
)
}
}
export default DisplayCard;
Hey guys,
I am new to react, so this is my child component that takes state from its parent component. My goal is to re-render component every time the array this.props.saved is changed.
This component renders: <p>You have no saved tasks.</p> when the this.props.saved.length === 0 and it renders <div>{array[0].task}</div> when i enter the first task, but it keeps it at <div>{array[0].task}</div> after that. I do see that the state keeps changing and this.props.saved keeps getting bigger, but my component doesn't change anymore.

Here's your problem:
runArray = (array) => {
for (var i = 0; i<array.length; i++) {
//the first time we get here, it immediately ends the function!
return <div>{array[i].task}</div>
}
}
This loop only ever goes through once (at i=0) and then returns, exiting the runArray function and cancelling the rest of the loop. You probably wanted to return an array of elements, one for each of the tasks. I recommend using Array.map() for this, which takes an array and transforms each element, creating a new array:
runArray = (array) => {
return array.map(arrayElement => <div>arrayElement.task</div>);
}
This should do the trick. Note that React may complain about the fact that your elements lack the key property - see the documentation for more info: https://reactjs.org/docs/lists-and-keys.html

The problem is in your runArray function. Inside your loop, you are returning the first element and that's it. My guess is, you see only the first entry?
When you are trying to render all your tasks, I would suggest to map your tasks, e.g.
runArray = (array) => array.map(entry => <div>{entry.task}</div>)

It is because you write wrong the runArray function. You make a return in the for loop so it breaks after the first iteration. It will not iterate over the full array.
You need to transform your for loop to a map : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
runArray = (array) => {
return array.map(v => <div>{v.task}</div>)
}
Does it fix your issue ?

You have to update state of the component to trigger render function. Your render function is not triggered because you did not update the state when the props changed. There are many ways to update state when props updated. One method may be the following:
componentWillReceiveProps(nextProps){
if (nextProps.saved !== this.props.saved) {
this.setState({ saved: nextProps.saved })
}
}
Also change yoour render function to use state of the component as below:
renderElements = () =>{
if (this.state.savedTasks.length === 0) {
return <div className="noTasks"> <p>You have no saved tasks.</p> </div>
} else {
return this.runArray(this.state.savedTasks)
}
}

Use .map so that it renders your task correctly. You can remove runArray and rely entirely on props so you don't need to pass arguments across functions as it can get messy quickly. Here's a quick running example of how to create a parent component where you can add a task and pass them into a component so that it renders your data when props are changed, therefore making it reactive.
class App extends React.Component {
state = {
taskLabel: "",
tasks: [
{
id: 1,
label: "Do something"
},
{
id: 2,
label: "Learn sometihng"
}
]
};
handleInput = evt => {
this.setState({
[evt.target.name]: evt.target.value
});
};
handleSubmit = evt => {
evt.preventDefault();
this.setState(prevState => ({
taskLabel: "",
tasks: [
...prevState.tasks,
{
id: prevState.tasks.length + 1,
label: this.state.taskLabel
}
]
}));
};
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input
name="taskLabel"
type="text"
placeholder="Task label"
value={this.state.taskLabel}
onChange={this.handleInput}
/>
<button>Create task</button>
</form>
<DisplayCard tasks={this.state.tasks} />
</div>
);
}
}
class DisplayCard extends React.Component {
renderTasks = () => {
if (this.props.tasks.length !== 0) {
return this.props.tasks.map(task => (
<div key={task.id}>{task.label}</div>
));
} else {
return <div>No tasks</div>;
}
};
render() {
return <div>{this.renderTasks()}</div>;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<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>

Related

ReactJS Wrong Component gets removed from DOM

I have three files: ShopsContainer.js ShopsComponent.js and ShopsItemComponent.js
ShopsContainer maintains an array of shop items in local state that gets passed down into ShopsComponent as props. ShopsComponent then maps through the items array that is being received as props and renders a ShopsItemComponent for each item in the array.
Within my ShopsContainer file, I have a method that removes a shop item from state using the following code:
removeShop = (shopAccount) => {
this.setState(prevState => ({
items: prevState.items.filter(shop => {
return shop.shopAccount !== shopAccount
})
}));
}
When this happens, the correct item is removed from the items array in state, however, whatever the last ShopItem is that is in the DOM at the time of the removeShop call will get removed no matter if it is the correct item that should be removed or not. In other words, when removeShop gets called and the items array in state gets updated correctly, the wrong ShopItemComponent gets removed from the DOM.
What I would like to happen (or what I think should happen) is when removeShop gets called, that shop gets removed from the items array in state and ShopsContainer re-renders causing ShopsComponent to re-render with the updated props being received. And lastly ShopsComponent would map through the newly updated items array in props displaying a `ShopItemComponent for the correct items. Perhaps the problem has to do with the props being updated?
My code is as follows:
ShopsContainer.js
class ShopsContainer extends Component {
constructor() {
this.state = {
items: null
}
this.getAll();
this.removeShop = this.removeShop.bind(this);
}
getAll = () => {
// API request that fetches items and updates state
}
removeShop = (shopAccount) => {
this.setState(prevState => ({
items: prevState.items.filter(shop => {
return shop.shopAccount !== shopAccount
})
}));
}
render() {
return (
<div>
{this.state.items ? <ShopComponent items={this.state.items} removeShop={this.removeShop} /> : <div><h1>Loading...</h1></div>}
</div>
);
}
}
ShopsComponent.js
class ShopsComponent extends Component {
constructor() {
this.handleRemove = this.handleRemove.bind(this);
}
handleRemove = (shopAccount) => {
this.props.removeShop(shopAccount);
}
render() {
return (
<React.Fragment>
<Header />
{this.props.items.map((shopItem, i) => {
return (<ShopItemComponent key={i} item={shopItem} removeShop={this.handleRemove} />);
})}
</React.Fragment>
);
}
}
Your code is working great, but you only has one mistake , your ShopComponent is assign index as a key for each ShopItemComponent and react is tracking those indexes to update the correct component, so you need to set key as a unique value between items, then I realize that shopAccount should be your id for each item.
The solution code is below.
class ShopsComponent extends Component {
handleRemove = (shopAccount) => {
this.props.removeShop(shopAccount);
}
render() {
return (
<React.Fragment>
<Header />
{this.props.items.map((shopItem) => <ShopItemComponent key={shopItem.shopAccount} item={shopItem} removeShop={this.handleRemove} />)}
</React.Fragment>
);
}
}
I hope you can find useful.
Note, when you are using a arrow function into your class, don't bind that method into the constructor, so remove it, because
handleRemove = (shopAccount) => {
this.props.removeShop(shopAccount);
}
is already binded.

Call another code while array.map() is running

I have the following react code:
{myArray.map(arr => {
return ( <MyComponent title={arr.ttile} /> )
})}
I would like to call a Loading component while the map() is not completely finished. Is it possible to do that? If yes, how would I do that?
If you are getting your data from an API, you might want to render the data as usual, but you can get the data in the componentDidMount hook instead, and e.g. keep an additional piece of state isLoading which you can use in the render method to decide if you should show a loading component.
Example
function getBooks() {
return new Promise(resolve => {
setTimeout(() => resolve([{ title: "foo" }, { title: "bar" }]), 1000);
});
}
function MyComponent(props) {
return <div> {props.title} </div>;
}
class App extends React.Component {
state = { books: [], isLoading: true };
componentDidMount() {
getBooks().then(books => {
this.setState({ books, isLoading: false });
});
}
render() {
const { isLoading, books } = this.state;
if (isLoading) {
return <div> Loading... </div>;
}
return (
<div>
{this.state.books.map(book => <MyComponent title={book.title} />)}
</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>
If you want to actually be able to see the components being loaded behind/under the loading indicator, then it would be more challenging and would probably need more work than this proposed solution. But if you just want a loading indicator to show while the .map() prototype function is working, I believe this would do the trick:
constructor(props) {
super(props);
this.state = { loadingIndicator : null };
}
getArrayOfMyComponents() {
return myArray.map((arr, index) => {
if (index === 0) {
const loadingIndicator = <Loading/>;
this.setState({ loadingIndicator : loadingIndicator });
} else if (index === myArray.length - 1) {
this.setState({ loadingIndicator : null });
}
return ( <MyComponent title={arr.title} /> );
});
}
render() {
const arrayOfMyComponents = this.getArrayOfMyComponents();
return (
<div>
{this.state.loadingIndicator}
{arrayOfMyComponents}
</div>
);
}
Array.prototype.map() is really just a fancier version of Array.prototype.forEach(). So we can leverage that fact to launch the display of the loading indicator on the first iteration and remove it on the last.
you can have a boolean in a state, and just before you start array map put boolean true and run another code o component render, and then when array maps end you put that state to false, for redux im using state fetch start, fetching, fetched, and then you can take the control of situation

setState in action handler using ReactJS not updating

New to React.
I have a handler, as follows, that updates state of an array. The data is a set of animal pairs.
class Animal extends Component {
state = {
pairs: [
{ fromAnimal: 'Dog', toAnimal: 'Cat' },
{ fromAnimal: 'Lion', toAnimal: 'Tiger' },
{ fromAnimal: 'Rabbit', toAnimal: 'Bear' }
]
};
closePairHandler = (fromAnimal, toAnimal) => {
let newPairs = this.state.pairs.filter((pair) => {
return !(pair.fromAnimal === fromAnimal && pair.toAnimal === toAnimal);
});
console.log('pairs', newPairs); // This shows that the correct pair was removed from the array.
this.setState({ pairs: newPairs });
};
render() {
return (
<div>
{
this.state.pairs.map((pair, index) => {
return <SomeComponent key={index} pair={pair} closePair={(fromAnimal, toAnimal) => this.closePairHandler(fromAnimal, toAnimal)} />;
}
}
</div>
);
};
};
export default Animal;
This is a super simplified version of the code I have. BUT, when the closePairHandler is called to remove an animal pair (for example, Lion/Tiger). The console.log in the closePairHandler shows that the array has been updated successfully.
However, when the components render. It is removing the LAST component in the array and not the one that was selected. It's reducing the array size by 1, but not removing the correct item in the mapping (in render), althought the closePairHandler console.log is showing the array correctly updated before setting the state.
Can anyone explain to me what is going on here?
Thanks again!
You are not providing the key for your mapped data while rendering SomeComponent and hence react is not able to correctly identify what element got changed. You can use index as the key if you don't have a unique id in your pair object else you should use that for performance reasons
return (
<div>
{
this.state.pairs.map((pair, index) => {
return <SomeComponent key={index} pair={pair} closePair={(fromAnimal, toAnimal) => this.closePairHandler(fromAnimal, toAnimal)} />;
}
}
</div>
);

How to remove an instance of a React component class instantiated by its parent's state?

(Pardon the verbose question. I'm brand new to React and ES6, and I'm probably overly-convoluting this.)
I am writing an app that contains a button component. This button calls a method onAddChild that creates another component of class ColorModule by adding a value to an array stored in the App's state.
In each newly created ColorModule, I want to include another button that will remove the module. Since this component is created by an array.map method, my thought is that if I can find the index of the array item that corresponds with the component and use that index in array.splice then perhaps that component will be removed (untested theory). That said, I'm not really sure how to find the index where I would use this in my onRemoveModule method.
Two part question: 1) How would I go about finding the index of the array item in my state, and 2) if I'm completely off base or there's a better way to do this altogether, what does that solution look like?
imports...
class App extends Component {
static propTypes = {
children: PropTypes.node,
};
constructor(props) {
super(props);
this.state = {
// Here's the array in question...
moduleList: [1],
};
this.onAddChild = this.onAddChild.bind(this);
this.onRemoveModule = this.onRemoveModule.bind(this);
this.className = bemClassName.bind(null, this.constructor.name);
}
onAddChild(module) {
const moduleList = this.state.moduleList;
this.setState({ moduleList: moduleList.concat(1) });
}
onRemoveModule( e ) {
e.preventDefault();
...¯\_(ツ)_/¯
}
render() {
const { className } = this;
return (
<div className={className('container')}>
<Header onAddChild={this.onAddChild} /> /* Add module button lives here */
<div className="cf">
{this.state.moduleList.map(
( delta, index ) => {
return (
<ColorModule
className="cf"
onRemove={this.onRemoveModule}
key={index}
moduleId={'colorModule' + index}
/>
); /* Remove module button would live in the module itself */
}
)}
</div>
</div>
);
}
}
export default App;
Well this part is pretty easy, all you need to do is pass the index as prop to the ColorModule component and when calling the onRemove method in it you could pass it back to the onRemoveModule. However react optimizes based on keys and its a really good idea to have a unique id given to each module instance.
class App extends Component {
static propTypes = {
children: PropTypes.node,
};
constructor(props) {
super(props);
this.state = {
// Here's the array in question...
moduleList: [1],
};
this.onAddChild = this.onAddChild.bind(this);
this.onRemoveModule = this.onRemoveModule.bind(this);
this.className = bemClassName.bind(null, this.constructor.name);
}
onAddChild(module) {
const moduleList = this.state.moduleList;
this.setState({ moduleList: moduleList.concat(uuid()) }); //uuid must return a unique id everytime to be used as component key
}
onRemoveModule( index ) {
// now with this index you can update the moduleList
}
render() {
const { className } = this;
return (
<div className="cf">
{this.state.moduleList.map(
( delta, index ) => {
return (
<ColorModule
className="cf"
index={index}
onRemove={this.onRemoveModule}
key={delta}
moduleId={'colorModule' + delta}
/>
);
}
)}
</div>
);
}
}
Now in ColorModule component
class ColorModule extends React.Component {
onRemoveClick=() => {
this.props.onRemove(this.props.index);
}
}
Check this answer for more details on how to pass data from Child component to Parent
I ended up solving this problem using some of the guidance here from #ShubhamKhatri (didn't know about unique ID generation!), but I took a slightly different approach and handled the solution using state manipulation in App without needing a new method in my ColorModule component. I also never knew about currying in ES6, so that discovery made passing in the index values needed to manipulate my state array possible
If I'm off-base here or being inefficient, I'm definitely still open to feedback on a better way!
class App extends Component {
constructor(props) {
super(props);
this.state = {
moduleList: [{ id: UniqId(), removeModule: false }],
};
this.onAddChild = this.onAddChild.bind(this);
this.className = bemClassName.bind(null, this.constructor.name);
}
onAddChild(module) {
const moduleList = this.state.moduleList;
this.setState({
moduleList: moduleList.concat({
id: UniqId(),
removeModule: false,
}),
});
}
onRemoveModule = ( i, arr ) => (e) => {
const moduleList = this.state.moduleList;
e.preventDefault();
moduleList[i].removeModule = true;
this.setState({ moduleList: moduleList });
}
render() {
const { className } = this;
return (
<div className={className('container')}>
<Header onAddChild={this.onAddChild} />
<div className="cf">
{this.state.moduleList.map(
( delta, index ) => {
if ( !this.state.moduleList[index].removeModule ) {
return (
<ColorModule
className="cf"
onRemove={this.onRemoveModule( index, this.state.moduleList )}
index={index}
key={delta.id}
moduleId={'colorModule' + delta}
/>
);
}
}
)}
</div>
</div>
);
}
}

React js onClick can't pass value to method

I want to read the onClick event value properties. But when I click on it, I see something like this on the console:
SyntheticMouseEvent {dispatchConfig: Object, dispatchMarker: ".1.1.0.2.0.0:1", nativeEvent: MouseEvent, type: "click", target
My code is working correctly. When I run I can see {column} but can't get it in the onClick event.
My Code:
var HeaderRows = React.createClass({
handleSort: function(value) {
console.log(value);
},
render: function () {
var that = this;
return(
<tr>
{this.props.defaultColumns.map(function (column) {
return (
<th value={column} onClick={that.handleSort} >{column}</th>
);
})}
{this.props.externalColumns.map(function (column) {
// Multi dimension array - 0 is column name
var externalColumnName = column[0];
return ( <th>{externalColumnName}</th>);
})}
</tr>
);
}
});
How can I pass a value to the onClick event in React js?
Easy Way
Use an arrow function:
return (
<th value={column} onClick={() => this.handleSort(column)}>{column}</th>
);
This will create a new function that calls handleSort with the right params.
Better Way
Extract it into a sub-component.
The problem with using an arrow function in the render call is it will create a new function every time, which ends up causing unneeded re-renders.
If you create a sub-component, you can pass handler and use props as the arguments, which will then re-render only when the props change (because the handler reference now never changes):
Sub-component
class TableHeader extends Component {
handleClick = () => {
this.props.onHeaderClick(this.props.value);
}
render() {
return (
<th onClick={this.handleClick}>
{this.props.column}
</th>
);
}
}
Main component
{this.props.defaultColumns.map((column) => (
<TableHeader
value={column}
onHeaderClick={this.handleSort}
/>
))}
Old Easy Way (ES5)
Use .bind to pass the parameter you want, this way you are binding the function with the Component context :
return (
<th value={column} onClick={this.handleSort.bind(this, column)}>{column}</th>
);
There are nice answers here, and i agree with #Austin Greco (the second option with separate components)
There is another way i like, currying.
What you can do is create a function that accept a parameter (your parameter) and returns another function that accepts another parameter (the click event in this case). then you are free to do with it what ever you want.
ES5:
handleChange(param) { // param is the argument you passed to the function
return function (e) { // e is the event object that returned
};
}
ES6:
handleChange = param => e => {
// param is the argument you passed to the function
// e is the event object that returned
};
And you will use it this way:
<input
type="text"
onChange={this.handleChange(someParam)}
/>
Here is a full example of such usage:
const someArr = ["A", "B", "C", "D"];
class App extends React.Component {
state = {
valueA: "",
valueB: "some initial value",
valueC: "",
valueD: "blah blah"
};
handleChange = param => e => {
const nextValue = e.target.value;
this.setState({ ["value" + param]: nextValue });
};
render() {
return (
<div>
{someArr.map(obj => {
return (
<div>
<label>
{`input ${obj} `}
</label>
<input
type="text"
value={this.state["value" + obj]}
onChange={this.handleChange(obj)}
/>
<br />
<br />
</div>
);
})}
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<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>
Note that this approach doesn't solve the creation of a new instance on each render.
I like this approach over the other inline handlers as this one is more concise and readable in my opinion.
Edit:
As suggested in the comments below, you can cache / memoize the result of the function.
Here is a naive implementation:
let memo = {};
const someArr = ["A", "B", "C", "D"];
class App extends React.Component {
state = {
valueA: "",
valueB: "some initial value",
valueC: "",
valueD: "blah blah"
};
handleChange = param => {
const handler = e => {
const nextValue = e.target.value;
this.setState({ ["value" + param]: nextValue });
}
if (!memo[param]) {
memo[param] = e => handler(e)
}
return memo[param]
};
render() {
return (
<div>
{someArr.map(obj => {
return (
<div key={obj}>
<label>
{`input ${obj} `}
</label>
<input
type="text"
value={this.state["value" + obj]}
onChange={this.handleChange(obj)}
/>
<br />
<br />
</div>
);
})}
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<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" />
Nowadays, with ES6, I feel we could use an updated answer.
return (
<th value={column} onClick={()=>this.handleSort(column)} >{column}</th>
);
Basically, (for any that don't know) since onClick is expecting a function passed to it, bind works because it creates a copy of a function. Instead we can pass an arrow function expression that simply invokes the function we want, and preserves this. You should never need to bind the render method in React, but if for some reason you're losing this in one of your component methods:
constructor(props) {
super(props);
this.myMethod = this.myMethod.bind(this);
}
[[h/t to #E.Sundin for linking this in a comment]
The top answer (anonymous functions or binding) will work, but it's not the most performant, as it creates a copy of the event handler for every instance generated by the map() function.
This is an explanation of the optimal way to do it from the ESLint-plugin-react:
Lists of Items
A common use case of bind in render is when rendering a list, to have
a separate callback per list item:
const List = props => (
<ul>
{props.items.map(item =>
<li key={item.id} onClick={() => console.log(item.id)}>
...
</li>
)}
</ul>
);
Rather than doing it this way, pull the repeated section into its own
component:
const List = props => (
<ul>
{props.items.map(item =>
<ListItem
key={item.id}
item={item}
onItemClick={props.onItemClick} // assume this is passed down to List
/>
)}
</ul>
);
const ListItem = props => {
const _onClick = () => {
console.log(props.item.id);
}
return (
<li onClick={_onClick}>
...
</li>
);
});
This will speed up rendering, as it avoids the need to create new
functions (through bind calls) on every render.
This is my approach, not sure how bad it is, please comment
In the clickable element
return (
<th value={column} onClick={that.handleSort} data-column={column}> {column}</th>
);
and then
handleSort(e){
this.sortOn(e.currentTarget.getAttribute('data-column'));
}
React Hooks Solution 2022
const arr = [
{ id: 1, txt: 'One' },
{ id: 2, txt: 'Two' },
{ id: 3, txt: 'Three' },
]
const App = () => {
const handleClick = useCallback(
(id) => () => {
console.log("ID: ", id)
},
[],
)
return (
<div>
{arr.map((item) => (
<button onClick={handleClick(item.id)}>{item.txt}</button>
))}
</div>
)
}
You can pass a function to useCallback's return, you can then call your function normally in the render by passing params to it. Works like a charm! Just make sure you set your useCallback's dependency array appropriately.
Best Solution with React >= 16
The cleanest way I've found to call functions with multiple parameters in onClick, onChange etc. without using inline functions is to use the custom data attribute available in React 16 and above versions.
const App = () => {
const onClick = (e) => {
const value1 = e.currentTarget.getAttribute("data-value1")
const value2 = e.currentTarget.getAttribute("data-value2")
const value2 = e.currentTarget.getAttribute("data-value2")
console.log("Values1", value1)
console.log("Values2", value2)
console.log("Values3", value3)
}
return (
<button onClick={onClick} data-value1="a" data-value2="b" data-value3="c" />
)
}
Above example is for a functional component but the implementation is pretty similar even in class components.
This approach doesn't yield unnecessary re-renders because you aren't using inline functions, and you avoid the hassle of binding with this.
It allows you to pass as many values as you would like to use in your function.
If you are passing values as props to your children to be used in the Child Component's onClick, you can use this approach there as well, without creating a wrapper function.
Works with array of objects as well, in cases where you want to pass the id from the object to the onClick, as shown below.
const App = () => {
const [arrState, setArrState] = useState(arr)
const deleteContent = (e) => {
const id = e.currentTarget.getAttribute("data-id")
const tempArr = [...arrState]
const filteredArr = tempArr.filter((item) => item.id !== id)
setArrState(filteredArr)
}
return (
<div>
{arrState.map((item) => (
<React.Fragment key={item.id}>
<p>{item.content}</p>
<button onClick={deleteContent} data-id={item.id}>
Delete
</button>
</React.Fragment>
))}
</div>
)
}
this example might be little different from yours. but i can assure you that this is the best solution you can have for this problem.
i have searched for days for a solution which has no performance issue. and finally came up with this one.
class HtmlComponent extends React.Component {
constructor() {
super();
this.state={
name:'MrRehman',
};
this.handleClick= this.handleClick.bind(this);
}
handleClick(event) {
const { param } = e.target.dataset;
console.log(param);
//do what you want to do with the parameter
}
render() {
return (
<div>
<h3 data-param="value what you wanted to pass" onClick={this.handleClick}>
{this.state.name}
</h3>
</div>
);
}
}
UPDATE
incase you want to deal with objects that are supposed to be the parameters. you can use JSON.stringify(object) to convert to it to string and add to the data set.
return (
<div>
<h3 data-param={JSON.stringify({name:'me'})} onClick={this.handleClick}>
{this.state.name}
</h3>
</div>
);
Simply create a function like this
function methodName(params) {
//the thing you wanna do
}
and call it in the place you need
<Icon onClick = {() => { methodName(theParamsYouwantToPass);} }/>
class extends React.Component {
onClickDiv = (column) => {
// do stuff
}
render() {
return <div onClick={() => this.onClickDiv('123')} />
}
}
I realize this is pretty late to the party, but I think a much simpler solution could satisfy many use cases:
handleEdit(event) {
let value = event.target.value;
}
...
<button
value={post.id}
onClick={this.handleEdit} >Edit</button>
I presume you could also use a data- attribute.
Simple, semantic.
Making alternate attempt to answer OP's question including e.preventDefault() calls:
Rendered link (ES6)
<a href="#link" onClick={(e) => this.handleSort(e, 'myParam')}>
Component Function
handleSort = (e, param) => {
e.preventDefault();
console.log('Sorting by: ' + param)
}
One more option not involving .bind or ES6 is to use a child component with a handler to call the parent handler with the necessary props. Here's an example (and a link to working example is below):
var HeaderRows = React.createClass({
handleSort: function(value) {
console.log(value);
},
render: function () {
var that = this;
return(
<tr>
{this.props.defaultColumns.map(function (column) {
return (
<TableHeader value={column} onClick={that.handleSort} >
{column}
</TableHeader>
);
})}
{this.props.externalColumns.map(function (column) {
// Multi dimension array - 0 is column name
var externalColumnName = column[0];
return ( <th>{externalColumnName}</th>
);
})}
</tr>);
)
}
});
// A child component to pass the props back to the parent handler
var TableHeader = React.createClass({
propTypes: {
value: React.PropTypes.string,
onClick: React.PropTypes.func
},
render: function () {
return (
<th value={this.props.value} onClick={this._handleClick}
{this.props.children}
</th>
)
},
_handleClick: function () {
if (this.props.onClick) {
this.props.onClick(this.props.value);
}
}
});
The basic idea is for the parent component to pass the onClick function to a child component. The child component calls the onClick function and can access any props passed to it (and the event), allowing you to use any event value or other props within the parent's onClick function.
Here's a CodePen demo showing this method in action.
You can simply do it if you are using ES6.
export default class Container extends Component {
state = {
data: [
// ...
]
}
handleItemChange = (e, data) => {
// here the data is available
// ....
}
render () {
return (
<div>
{
this.state.data.map((item, index) => (
<div key={index}>
<Input onChange={(event) => this.handItemChange(event,
item)} value={item.value}/>
</div>
))
}
</div>
);
}
}
There are couple of ways to pass parameter in event handlers, some are following.
You can use an arrow function to wrap around an event handler and pass parameters:
<button onClick={() => this.handleClick(id)} />
above example is equivalent to calling .bind or you can explicitly call bind.
<button onClick={this.handleClick.bind(this, id)} />
Apart from these two approaches, you can also pass arguments to a function that is defined as a curry function.
handleClick = (id) => () => {
console.log("Hello, your ticket number is", id)
};
<button onClick={this.handleClick(id)} />
Implementing show total count from an object by passing count as a parameter from main to sub components as described below.
Here is MainComponent.js
import React, { Component } from "react";
import SubComp from "./subcomponent";
class App extends Component {
getTotalCount = (count) => {
this.setState({
total: this.state.total + count
})
};
state = {
total: 0
};
render() {
const someData = [
{ name: "one", count: 200 },
{ name: "two", count: 100 },
{ name: "three", count: 50 }
];
return (
<div className="App">
{someData.map((nameAndCount, i) => {
return (
<SubComp
getTotal={this.getTotalCount}
name={nameAndCount.name}
count={nameAndCount.count}
key={i}
/>
);
})}
<h1>Total Count: {this.state.total}</h1>
</div>
);
}
}
export default App;
And Here is SubComp.js
import React, { Component } from 'react';
export default class SubComp extends Component {
calculateTotal = () =>{
this.props.getTotal(this.props.count);
}
render() {
return (
<div>
<p onClick={this.calculateTotal}> Name: {this.props.name} || Count: {this.props.count}</p>
</div>
)
}
};
Try to implement above and you will get exact scenario that how pass parameters works in reactjs on any DOM method.
I wrote a wrapper component that can be reused for this purpose that builds on the accepted answers here. If all you need to do is pass a string however, then just add a data-attribute and read it from e.target.dataset (like some others have suggested). By default my wrapper will bind to any prop that is a function and starts with 'on' and automatically pass the data prop back to the caller after all the other event arguments. Although I haven't tested it for performance, it will give you the opportunity to avoid creating the class yourself, and it can be used like this:
const DataButton = withData('button')
const DataInput = withData('input');
or for Components and functions
const DataInput = withData(SomeComponent);
or if you prefer
const DataButton = withData(<button/>)
declare that Outside your container (near your imports)
Here is usage in a container:
import withData from './withData';
const DataInput = withData('input');
export default class Container extends Component {
state = {
data: [
// ...
]
}
handleItemChange = (e, data) => {
// here the data is available
// ....
}
render () {
return (
<div>
{
this.state.data.map((item, index) => (
<div key={index}>
<DataInput data={item} onChange={this.handleItemChange} value={item.value}/>
</div>
))
}
</div>
);
}
}
Here is the wrapper code 'withData.js:
import React, { Component } from 'react';
const defaultOptions = {
events: undefined,
}
export default (Target, options) => {
Target = React.isValidElement(Target) ? Target.type : Target;
options = { ...defaultOptions, ...options }
class WithData extends Component {
constructor(props, context){
super(props, context);
this.handlers = getHandlers(options.events, this);
}
render() {
const { data, children, ...props } = this.props;
return <Target {...props} {...this.handlers} >{children}</Target>;
}
static displayName = `withData(${Target.displayName || Target.name || 'Component'})`
}
return WithData;
}
function getHandlers(events, thisContext) {
if(!events)
events = Object.keys(thisContext.props).filter(prop => prop.startsWith('on') && typeof thisContext.props[prop] === 'function')
else if (typeof events === 'string')
events = [events];
return events.reduce((result, eventType) => {
result[eventType] = (...args) => thisContext.props[eventType](...args, thisContext.props.data);
return result;
}, {});
}
I have below 3 suggestion to this on JSX onClick Events -
Actually, we don't need to use .bind() or Arrow function in our code. You can simple use in your code.
You can also move onClick event from th(or ul) to tr(or li) to improve the performance. Basically you will have n number of "Event Listeners" for your n li element.
So finally code will look like this:
<ul onClick={this.onItemClick}>
{this.props.items.map(item =>
<li key={item.id} data-itemid={item.id}>
...
</li>
)}
</ul>
// And you can access item.id in onItemClick method as shown below:
onItemClick = (event) => {
console.log(e.target.getAttribute("item.id"));
}
I agree with the approach mention above for creating separate React Component for ListItem and List. This make code looks good however if you have 1000 of li then 1000 Event Listeners will be created. Please make sure you should not have much event listener.
import React from "react";
import ListItem from "./ListItem";
export default class List extends React.Component {
/**
* This List react component is generic component which take props as list of items and also provide onlick
* callback name handleItemClick
* #param {String} item - item object passed to caller
*/
handleItemClick = (item) => {
if (this.props.onItemClick) {
this.props.onItemClick(item);
}
}
/**
* render method will take list of items as a props and include ListItem component
* #returns {string} - return the list of items
*/
render() {
return (
<div>
{this.props.items.map(item =>
<ListItem key={item.id} item={item} onItemClick={this.handleItemClick}/>
)}
</div>
);
}
}
import React from "react";
export default class ListItem extends React.Component {
/**
* This List react component is generic component which take props as item and also provide onlick
* callback name handleItemClick
* #param {String} item - item object passed to caller
*/
handleItemClick = () => {
if (this.props.item && this.props.onItemClick) {
this.props.onItemClick(this.props.item);
}
}
/**
* render method will take item as a props and print in li
* #returns {string} - return the list of items
*/
render() {
return (
<li key={this.props.item.id} onClick={this.handleItemClick}>{this.props.item.text}</li>
);
}
}
I have added code for onclick event value pass to the method in two ways . 1 . using bind method 2. using arrow(=>) method . see the methods handlesort1 and handlesort
var HeaderRows = React.createClass({
getInitialState : function() {
return ({
defaultColumns : ["col1","col2","col2","col3","col4","col5" ],
externalColumns : ["ecol1","ecol2","ecol2","ecol3","ecol4","ecol5" ],
})
},
handleSort: function(column,that) {
console.log(column);
alert(""+JSON.stringify(column));
},
handleSort1: function(column) {
console.log(column);
alert(""+JSON.stringify(column));
},
render: function () {
var that = this;
return(
<div>
<div>Using bind method</div>
{this.state.defaultColumns.map(function (column) {
return (
<div value={column} style={{height : '40' }}onClick={that.handleSort.bind(that,column)} >{column}</div>
);
})}
<div>Using Arrow method</div>
{this.state.defaultColumns.map(function (column) {
return (
<div value={column} style={{height : 40}} onClick={() => that.handleSort1(column)} >{column}</div>
);
})}
{this.state.externalColumns.map(function (column) {
// Multi dimension array - 0 is column name
var externalColumnName = column;
return (<div><span>{externalColumnName}</span></div>
);
})}
</div>);
}
});
Below is the example which passes value on onClick event.
I used es6 syntax. remember in class component arrow function does not bind automatically, so explicitly binding in constructor.
class HeaderRows extends React.Component {
constructor(props) {
super(props);
this.handleSort = this.handleSort.bind(this);
}
handleSort(value) {
console.log(value);
}
render() {
return(
<tr>
{this.props.defaultColumns.map( (column, index) =>
<th value={ column }
key={ index }
onClick={ () => this.handleSort(event.target.value) }>
{ column }
</th>
)}
{this.props.externalColumns.map((column, index) =>
<th value ={ column[0] }
key={ index }>
{column[0]}
</th>
)}
</tr>
);
}
}
I guess you will have to bind the method to the React’s class instance. It’s safer to use a constructor to bind all methods in React. In your case when you pass the parameter to the method, the first parameter is used to bind the ‘this’ context of the method, thus you cannot access the value inside the method.
1. You just have to use an arrow function in the Onclick event like this:
<th value={column} onClick={() => that.handleSort(theValue)} >{column}</th>
2.Then bind this in the constructor method:
this.handleSort = this.handleSort.bind(this);
3.And finally get the value in the function:
handleSort(theValue){
console.log(theValue);
}
Using arrow function :
You must install stage-2:
npm install babel-preset-stage-2 :
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value=0
}
}
changeValue = (data) => (e) => {
alert(data); //10
this.setState({ [value]: data })
}
render() {
const data = 10;
return (
<div>
<input type="button" onClick={this.changeValue(data)} />
</div>
);
}
}
export default App;
Theres' a very easy way.
onClick={this.toggleStart('xyz')} .
toggleStart= (data) => (e) =>{
console.log('value is'+data);
}
class TableHeader extends Component {
handleClick = (parameter,event) => {
console.log(parameter)
console.log(event)
}
render() {
return (
<button type="button"
onClick={this.handleClick.bind(this,"dataOne")}>Send</button>
);
}
}
Coming out of nowhere to this question, but i think .bind will do the trick. Find the sample code below.
const handleClick = (data) => {
console.log(data)
}
<button onClick={handleClick.bind(null, { title: 'mytitle', id: '12345' })}>Login</button>
There are 3 ways to handle this :-
Bind the method in constructor as :-
export class HeaderRows extends Component {
constructor() {
super();
this.handleSort = this.handleSort.bind(this);
}
}
Use the arrow function while creating it as :-
handleSort = () => {
// some text here
}
Third way is this :-
<th value={column} onClick={() => that.handleSort} >{column}</th>
You can use your code like this:
<th value={column} onClick={(e) => that.handleSort(e, column)} >{column}</th>
Here e is for event object, if you want to use event methods like preventDefault() in your handle function or want to get target value or name like e.target.name.
There were a lot of performance considerations, all in the vacuum.
The issue with this handlers is that you need to curry them in order to incorporate the argument that you can't name in the props.
This means that the component needs a handler for each and every clickable element. Let's agree that for a few buttons this is not an issue, right?
The problem arises when you are handling tabular data with dozens of columns and thousands of rows. There you notice the impact of creating that many handlers.
The fact is, I only need one.
I set the handler at the table level (or UL or OL...), and when the click happens I can tell which was the clicked cell using data available since ever in the event object:
nativeEvent.target.tagName
nativeEvent.target.parentElement.tagName
nativeEvent.target.parentElement.rowIndex
nativeEvent.target.cellIndex
nativeEvent.target.textContent
I use the tagname fields to check that the click happened in a valid element, for example ignore clicks in THs ot footers.
The rowIndex and cellIndex give the exact location of the clicked cell.
Textcontent is the text of the clicked cell.
This way I don't need to pass the cell's data to the handler, it can self-service it.
If I needed more data, data that is not to be displayed, I can use the dataset attribute, or hidden elements.
With some simple DOM navigation it's all at hand.
This has been used in HTML since ever, since PCs were much easier to bog.
When working with a function as opposed to a class, it's actually fairly easy.
const [breakfastMain, setBreakFastMain] = useState("Breakfast");
const changeBreakfastMain = (e) => {
setBreakFastMain(e.target.value);
//sometimes "value" won't do it, like for text, etc. In that case you need to
//write 'e.target/innerHTML'
}
<ul onClick={changeBreakfastMain}>
<li>
"some text here"
</li>
<li>
"some text here"
</li>
</ul>
I'd do it like this:
const HeaderRows = props => {
const handleSort = value => () => {
}
return <tr>
{props.defaultColumns.map((column, i) =>
<th key={i} onClick={handleSort(column)}>{column}</th>)}
{props.externalColumns.map((column, i) => {
// Multi dimension array - 0 is column name
const externalColumnName = column[0]
return (<th key={i}>{externalColumnName}</th>)
})}
</tr>
}

Categories