简体   繁体   中英

React Redux - passing arguments to event handler not working

Ok, I got a head scratcher I need a little bit of help with. The setup is that I have React/Redux app with a Categories page that reads a list of categories from an API, then lists them out. That part works fine. What I'm trying to do is pass in an event handler to each of the category child components that, when clicked, dispatches an action that toggles the state of the component, ie, if the category is selected and clicked on, it will "unselect" it (which actually means deleting an entry from a database table called user_category), and if not selected, will "select" that category for that user (add an entry in the user_category table).

So I've got an onclick handler (handleCatClick) that is supposed to ultimately pass a categoryId and a userId to perform these operations. Unfortunately what I'm finding that even though these arguments are being passed to the function, they end up being undefined. So I'm not sure if I'm passing this function correctly or what exactly I've missed.

Everything works other than this - maybe you can help me spot the problem ;-)

Click here to view the database layout

Click here to see how the category page looks

The applicable pages in my app:

The architecture looks basically like this:

/views/[Categories]
  - index.js (wrapper for the Categories Component)
  - CategoriesComponent.jsx (should be self-explanatory)
   [duck]
        - index.js   (just imports a couple of files & ties stuff together)
        - operations.js  (where my handleCatClick() method is)
        - types.js  (Redux constants)
        - actions.js  (Redux actions)
        - reducers.js   (Redux reducers)
   [components]
        [Category]
                 - index.jsx  (the individual Category component)

/views/index.js(main Category page wrapper)

import { connect } from 'react-redux';
import CategoriesComponent from './CategoriesComponent';
import { categoriesOperations } from './duck'; // operations.js



const mapStateToProps = state => {
    // current state properties passed down to LoginComponent (LoginComponent.js)
    const { categoryArray } = state.categories;
    return { categoryArray }
  };



  const mapDispatchToProps = (dispatch) => {
    // all passed in from LoginOperations (operations.js)
    const loadUserCategories = () => dispatch(categoriesOperations.loadUserCategories());
    const handleCatClick = () => dispatch(categoriesOperations.handleCatClick());
    return {
        loadUserCategories,
        handleCatClick
    }
  };


  const CategoriesContainer = connect(mapStateToProps,mapDispatchToProps)(CategoriesComponent);

  export default CategoriesContainer;

/views/CategoriesComponent.jsx (display layer for the Categories view)

import React from 'react';
import {Row,Col,Container, Form, Button} from 'react-bootstrap';
import {Link} from 'react-router-dom';
import './styles.scss';
import Category from './components/Category';
import shortid from 'shortid';

class CategoriesComponent extends React.Component {
    constructor(props) {
        super(props);
        this.loadUserCats = this.props.loadUserCategories;
        this.handleCatClick = this.props.handleCatClick;
    }

    componentWillMount() {
        this.loadUserCats();
    }

    render() {
        return (
            <Container fluid className="categories nopadding">
                <Row>
                    <Col xs={12}>
                    <div className="page-container">
                        <div className="title-container">
                            <h4>Pick your favorite categories to contine</h4>
                        </div>
                        <div className="content-container">
                            <div className="category-container">
                                {
                                    this.props.categoryArray.map((item) => {
                                        return <Category className="category" handleClick={this.props.handleCatClick} key={shortid.generate()} categoryData={item} />
                                    })
                                }
                            </div>
                        </div>
                    </div>
                    </Col>
                </Row>
            </Container>
        )        
    }
}


export default CategoriesComponent

/views/Categories/components/index.jsx (Single Category Component)

import React from 'react';
import {Row,Col,Container, Form, Button} from 'react-bootstrap';
import './styles.scss';
import Img from 'react-image';

class Category extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            categoryName: this.props.categoryData.category_name,
            categoryImg: this.props.categoryData.category_img,
            categoryId: this.props.categoryData.category_id,
            userId: this.props.categoryData.user_id,
            selected: this.props.categoryData.user_id !== null,
            hoverState: ''
        }
        this.hover = this.hover.bind(this);
        this.hoverOff = this.hoverOff.bind(this);
        this.toggleCat = this.toggleCat.bind(this);
    }


    toggleCat() {

        // the onClick handler that is supposed to 
        // pass categoryId and userId.  When I do a 
        // console.log(categoryId, userId) these two values
        // show up no problem...

        const {categoryId, userId} = this.state;
        this.props.handleClick(categoryId, userId);
    }


    hover() {
        this.setState({
            hoverState: 'hover-on'
        });
    }

    hoverOff() {
        this.setState({
            hoverState: ''
        });
    }

    render() {
        const isSelected = (baseCat) => {
            if(this.state.selected) {
                return baseCat + " selected";
            }
            return baseCat;
        }
        return (
            <div className={"category" + ' ' + this.state.hoverState} onClick={this.toggleCat} onMouseOver={this.hover} onMouseOut={this.hoverOff}>
                <div className={this.state.selected ? "category-img selected" : "category-img"}>
                    <Img src={"/public/images/category/" + this.state.categoryImg} className="img-fluid" />
                </div>
                <div className="category-title">
                    <h5 className={this.state.selected ? "bg-primary" : "bg-secondary"}>{this.state.categoryName}</h5>
                </div>
            </div>
        );
    }
}
export default Category;

/views/Categories/duck/operations.js (where I tie it all together)

// operations.js
import fetch from 'cross-fetch';
import Actions from './actions';
import Config from '../../../../config';


const loadCategories = Actions.loadCats;
const selectCat = Actions.selectCat;
const unSelectCat = Actions.unSelectCat;

const localState = JSON.parse(localStorage.getItem('state'));
const userId = localState != null ? localState.userSession.userId : -1;



const loadUserCategories = () => {

        return dispatch => {
            return fetch(Config.API_ROOT + 'usercategories/' + userId)
            .then(response => response.json())
            .then(json => {
            dispatch(loadCategories(json));
            });
        }      
}


const handleCatClick = (categoryId, categoryUserId) => {

    // HERE IS WHERE I'M HAVING A PROBLEM:
    // for whatever reason, categoryId and categoryUserId
    // are undefined here even though I'm passing in the 
    // values in the Category component (see 'toggleCat' method)

    var params = {
        method: categoryUserId !== null ? 'delete' : 'post',
        headers: {'Content-Type':'application/json'},
        body: JSON.stringify(
            {
                "category_id": categoryId, 
                user_id: categoryUserId !== null ? categoryUserId : userId
            }
        )
    };

    const toDispatch = categoryUserId !== null ? unSelectCat : selectCat;
    return dispatch => {
        return fetch(Config.API_ROOT + 'usercategories/', params)
        .then(response => response.json())
        .then(json => {
            dispatch(toDispatch(json));
        });
    } 

}

export default {
    loadUserCategories,
    handleCatClick
}

The problem that I am having:

So I'm thinking I'm either not referencing handleCatClick correctly, or I'm somehow not passing the categoryId and userId correctly so that when it finally gets to handleCatClick(categoryId, categoryUserId) in operations.js , it ends up as undefined. It's probably something simple but I can't spot it. NOTE: I haven't included files like the types.js or reducers.js, because they seem to be outside the scope of the problem, but if you need them please let me know. Thanks in advance for your help!

Try this changes: Add params to these handlers

const handleCatClick = (categoryId, categoryUserId) => dispatch(categoriesOperations.handleCatClick(categoryId, categoryUserId));

and

return <Category className="category" handleClick={(categoryId, categoryUserId) => this.props.handleCatClick(categoryId, categoryUserId)} key={shortid.generate()} categoryData={item} />

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM