Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expected onClick listener to be a function, instead got type object - react redux

As explained in the title, I am getting the error Expected onClick listener to be a function, instead got type object

But I am unable to understand why this isnt working. as far as I know, the onClick listener IS a function.

Here's, the CharacterList Component where the error comes from

import React,{Component} from 'react';
import {connect} from 'react-redux';
import {addCharacterById} from '../actions';
import {bindActionCreators} from 'redux';


class CharacterList extends Component{

    render(){
        //console.log('name : ',this.props.characters[2].name);
        return(
            <div>
            <h3>Characters</h3>
        <ul>
        {this.props.characters.map((character)=>{
            return(<li key={character.id}>{character.name}
                <div
                onClick={this.props.addCharacterById(character.id)}
                >+</div>
                </li>);
        })}
        </ul>
            </div>

            )
    }
}

function mapStateToProps(state){
    return {
        characters:state
    }
}




export default connect(mapStateToProps,{addCharacterById})(CharacterList);

And here's the action creator

export const ADD_CHARACTER='ADD_CHARACTER';
export function addCharacterById(id){
    var action ={
        type:ADD_CHARACTER,
            id
        }
        return action;

}

So, what do you guys think? what is the problem here?

like image 579
faraz Avatar asked Jun 29 '17 19:06

faraz


4 Answers

The problem is that you're invoking the function immediately and then what's left is the return value, which might not be a function!

What you can do instead is wrap that function call inside an arrow function to solve your problem. It'll call the inner function once you onClick:

import React,{Component} from 'react';
import {connect} from 'react-redux';
import {addCharacterById} from '../actions';
import {bindActionCreators} from 'redux';


class CharacterList extends Component{

    render(){
        //console.log('name : ',this.props.characters[2].name);
        return(
            <div>
            <h3>Characters</h3>
        <ul>
        {this.props.characters.map((character)=>{
            return(<li key={character.id}>{character.name}
                <div
                onClick={() => this.props.addCharacterById(character.id)}
                >+</div>
                </li>);
        })}
        </ul>
            </div>

            )
    }
}

function mapStateToProps(state){
    return {
        characters:state
    }
}




export default connect(mapStateToProps,{addCharacterById})(CharacterList);

There's diferent ways of doing this, you could for example bind the parameter to the function, like:

{this.props.characters.map((character)=>{
    return(<li key={character.id}>{character.name}
        <div
        onClick={this.props.addCharacterById.bind(null, character.id)}
        >+</div>
        </li>);
})}

Just shared as an example, so that you understand what's going on and why the first approach is more readable. You may want to look into why .bind in render is a bad practice by reading the article https://ryanfunduk.com/articles/never-bind-in-render/

like image 68
punkbit Avatar answered Nov 02 '22 21:11

punkbit


onClick={this.props.addCharacterById(character.id)} this part of your code will execute immediately upon render() is call what you may want to do instead is:

onClick={(e)=> {this.props.addCharacterById(e, character.id)}}

Remember the first parameter pass to onClick is the click event.

like image 37
yahiramat Avatar answered Nov 02 '22 22:11

yahiramat


Don't directly call functions in onClick event. It will recursively call the method. So make the onClick input as a callback method.

Change this line

onClick={this.props.addCharacterById(character.id)}

to

onClick={() => this.props.addCharacterById(character.id)}
like image 4
Codemaker Avatar answered Nov 02 '22 21:11

Codemaker


There are some things that you need to change if you want to have good practices.

First, add mapDispatchToProps function.

import { bindActionCreators } from 'redux';
...
function mapDispatchToProps(dispatch) {
  return {
    addCharacterById: bindActionCreators(addCharacterById, dispatch)
  };
}

Second, the event handler could be:

onClick={() => this.props.addCharacterById(character.id)}

Third, export your component:

export default connect(mapStateToProps, mapDispatchToProps)(CharacterList);
like image 3
slorenzo Avatar answered Nov 02 '22 22:11

slorenzo