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)}}
Related
I need to get a component that I cliked and see its target property. I try to get it but the evt param is undefined
getcomponent(evt){
console.log(evt.target)
//...
}
//...
render() {
return (<button id="btn" onClick={() =>this.getcomponent()}></button>);
}
You didn't pass the event to function call. Pass the event like this:
onClick={(evt) => this.getcomponent(evt)}.
Add event as a parameter to the onClick:
render() {
return (<button id="btn" onClick={(event) =>this.getcomponent(event)}></button>);
}
Make code short and simple:
onClick = event => {
console.log(event.target)
}
render() {
return <button id="btn" onClick={this.onClick}></button>
}
You need to pass event in order to get it back. Here is the code.
class TestJS extends React.Component {
constructor(props) {
super(props);
this.getcomponent = this.getcomponent.bind(this);
}
getcomponent(event){
console.log(event.target);
}
render() {
return(
<div id="root">
<button id="btn" onClick={(event) =>this.getcomponent(event)}></button>;
</div>
)};
}
export default TestJS;
I wrote two sample components to show what I'm trying to do.
If the call was from the same class, I could do like below.
onClick={this.myFunc.bind(this, param1, param2)}
How do I do the same thing from a stateless component without the need to mess with binding 'this'.
onClick={props.onClick['need to add params']}
import React from 'react';
class BigComponent extends React.Component{
constructor(props){
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(param1, param2){
// do something with parameters
}
render(){
return(
<div>
<SmallComponent handleClick={this.handleClick}/>
</div>
);
}
}
function SmallComponent(props){
return(
<div>
<button onClick={ () => {props.handleClick('value_1', 'value_2')}}></button>
<button onClick={ () => {props.handleClick('value_3', 'value_4')}}></button>
{/* how to do above without arrow functions, because I read that it's not optimized*/}
</div>
);
}
Add a callback inside of this.myFunc.
this.myFunc = event => (param1, param2) => { ... do stuff }
In your top level component you can do:
handleClick={this.myFunc}
In your child, just do:
onClick={handleClick(param1, param2)}
Hope this is helps.
Alternatively you can do the following:
function SmallComponent(props){
const handleClick = (param1, param2) => (event) => props.handleClick(param1, param2);
return(
<div>
<button onClick={handleClick(param1, param2)}></button>
...
);
}
I'm trying to do unit testing to a component using enzyme shallow rendering. Trying to test state activeTab of the component and it throws TypeError: Cannot read property state. my component Accordion. Accordion component jsx code
class Accordion extends Component {
constructor(props) {
super(props)
this.state = {
activeTab: 0
}
}
static defaultProps = {
tabs: [{title: 'Status'}, {title: 'Movement'}]
}
render() {
const { tabs } = this.props
, { activeTab } = this.state
return (
<div className={`accordion`}>
{tabs.map((t, i) => {
const activeClass = activeTab === i ? `accordion--tab__active` : ''
return(
<section key={i} className={`accordion--tab ${activeClass}`}>
<header className={`accordion--header`}>
<h4 className={`accordion--title`}>
<button onClick={() => {this._selectAccordion(i)}}>{t.title}</button>
</h4>
</header>
<div className="accordion--content">
{t.title}
Content
</div>
</section>
)
})}
</div>
)
}
_selectAccordion = activeTab => {this.setState({activeTab})}
}
export default Accordion
and Accordion.react.test.js
import { shallow } from 'enzyme'
import Accordion from './components/Accordion'
test('Accordion component', () => {
const component = shallow(<Accordion name={`Main`}/>)
expect(component.state('activeTab')).equals(0)
})
This could be a this scoping issue. With event handlers in React, you have to bind the event handler in the constructor to "this". Here is some info from React's docs about it:
You have to be careful about the meaning of this in JSX callbacks. In
JavaScript, class methods are not bound by default. If you forget to
bind this.handleClick and pass it to onClick, this will be undefined
when the function is actually called.
This is not React-specific behavior; it is a part of how functions
work in JavaScript. Generally, if you refer to a method without ()
after it, such as onClick={this.handleClick}, you should bind that
method.
class Accordion extends Component {
constructor(props) {
super(props)
this.state = {
activeTab: 0
}
// This binding is necessary to make `this` work in the callback
this._selectAccordion = this._selectAccordion.bind(this);
}
static defaultProps = {
tabs: [{title: 'Status'}, {title: 'Movement'}]
}
_selectAccordion(activeTab){
this.setState({activeTab : activeTab})
}
render() {
const { tabs } = this.props,
{ activeTab } = this.state
return (
<div className={`accordion`}>
{tabs.map((t, i) => {
const activeClass = activeTab === i ? `accordion--tab__active` : ''
return(
<section key={i} className={`accordion--tab ${activeClass}`}>
<header className={`accordion--header`}>
<h4 className={`accordion--title`}>
<button onClick={() => {this._selectAccordion(i)}}>{t.title}</button>
</h4>
</header>
<div className="accordion--content">
{t.title}
Content
</div>
</section>
)
})}
</div>
)
}
}
Your tests should verify how the component works but not "how to change a state". You need to throw new props into your component and get a result, and the result is expected.
I've tested my components with snapshots
This is an example of my current project
describe('<Component />', () => {
it('Page rendered', () => {
const rendered = renderComponent({
...testProps,
loadDataList,
loading: true,
});
expect(rendered).toMatchSnapshot();
});
});
What I have
I have a class that's not being exported but being used internally by a file. (The classes are all in the same file)
class SearchResults extends Component {
constructor()
{
super();
this.fill_name = this.fill_name.bind(this);
}
fill_name(event, name)
{
console.log("search results", event.target);
this.props.fill_name(name, event.target);
}
render()
{
return (
<div className="search-results-item" onClick={ () => this.fill_name(event, this.props.name)}>
<div className="highlight">
{this.props.name}
</div>
</div>
)
}
}
I'm trying to get the <div> element to be sent back to the parent, which is defined below (skipping irrelevant stuff):
class PublishComponent extends Component {
fill_name(name, me)
{
console.log(me);
$("#company_input").val(name);
this.setState({ list: { ...this.state.list, company: [] } });
}
}
me is the event.
What I'm getting
The console posts the following:
search results <react></react>
undefined
so the event.target is <react></react> for some reason, while the me is getting undefined.
Expected behaviour
It should return the element i.e. <div className="search-results-item"...></div>
You are not passing the event object
Change This
<div
className="search-results-item"
onClick={() => this.fill_name(event, this.props.name)}
/>
To This
<div
className="search-results-item"
// pass the event here
onClick={event => this.fill_name(event, this.props.name)}
/>
This should work for you:
class SearchResults extends Component {
constructor() {
super();
this.fill_name = this.fill_name.bind(this);
}
fill_name() {
this.props.fill_name(this.props.name, this.ref)
}
render() {
return (
<div className="search-results-item" ref={ref => { this.ref = ref }} onClick={this.fill_name}>
<div className="highlight">
{this.props.name}
</div>
</div>
)
}
}
I have a class that contains the class Component.
When I provide an onClick prop for the to use and try to pass a function, I get the following error:
AddForm.js:74 Uncaught TypeError: _this3.props.onClick is not a function
Code:
export default class Company extends React.Component {
constructor(props) {
super(props);
this.state = {
AddOrView: <AddForm header="Add User" formFields={this.fields} />,
users: this.props.initialUsers
}
}
handleAdd() {
console.log('Hello World');
}
render() {
return (
<AddWithTitle onClick={e => this.setState({
AddOrView: <AddForm header="Add User"
formFields={this.fields}
formResponses=""
onClick={this.handleAdd} />
})
} src="blah.png">
Add User</AddWithTitle>
);
}
}
export default class AddForm extends React.Component {
render() {
return(
<button className="btn btn-primary"
formResponses={this.state.fieldValues}
onClick={() => this.props.onClick()} >
Save
</button>
);
}
};
<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>
Thank you for your help :)
You have to bind the function to your top level component for it to fire whatever you want it to fire in the onClick method.
<AddWithTitle onClick={e => this.setState({
AddOrView: <AddForm header="Add User"
formFields={this.fields}
formResponses=""
onClick={this.handleAdd.bind(this)} />
})
} src="blah.png">
Add User</AddWithTitle>