将操作传递给子表示组件/从子容器组件(ownProps)访问Route Params

问题描述 投票:1回答:1

所以我一直在建立一个购物网站,我正在清理我的代码。

我有一个产品页面容器组件,它呈现:

  1. 类别概述表示组件。
  2. 品牌过滤器列表和价格范围过滤器列表(一个单独的过滤器是表示组件)。
  3. 产品列表(每个产品是一个容器组件)。

这些都是使用依赖于渲染容器组件的路径的道具呈现的。

请注意,使用过滤器调用的动作创建器会在每个过滤器选择中修改所呈现的产品,该过滤器会影响redux存储中的产品状态。

这听起来应该是干净的,但过滤器列出了div中我可以通过切换显示/隐藏视图的位置。这是最后添加的,并在容器组件中实现,如下所示:

import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';

import changeBrandFilter from '../actions/changeBrandFilter';
import changePriceFilter from '../actions/changePriceFilter';

import CategoryOverview from './CategoryOverview';
import Filter from './Filter';
import ProductsListItem from './ProductsListItem';
import ProductsPageContainerCSS from './ProductsPageContainer.css';
import Pagination from './Pagination';

class ProductsPage extends React.Component{
    createCategoryOverview() {
        let i = 1;
        return this.props.overview.map(overview => {
            i++
            return (
                <CategoryOverview 
                    key={"catOverview"+i} //each child in an array or iterator should have a unique "key" prop
                    title={overview.title}
                    text={overview.text}
                    image={overview.imageSource}
                    alt={overview.imageAlt}
                />
            )
        })
    }
    createBrandFilterList() {
        let i = 1;
        return this.props.brandFilters.map(filter => {
            i++
            return (
                <Filter
                    key={filter.brand+i+"brand"}
                    name={this.props.match.params.type + "brandFilter"} //so that each seperate group of radio buttons (filters) refer only to each other. (the name is shared within each group)
                    id={filter.brand}
                    changeFilterResetPageNumber={() => {this.props.changeBrandFilter(filter); this.handlePageChange(1)}} //without page reset would often get no products displayed on filter application due to the activePage state remaining at the page that was active at the time of filter application
                    inuse={filter.inuse}
                />
            )
        })
    }
    createPriceRangeFilterList() {
        let i = 1;
        return this.props.priceRangeFilters.map(filter => {
            i++
            return (
                <Filter
                    key={filter.priceRange+i+"priceRange"}
                    name={this.props.match.params.type + "priceFilter"} 
                    id={filter.priceRange}
                    changeFilterResetPageNumber={() => {this.props.changePriceFilter(filter); this.handlePageChange(1)}}
                    inuse={filter.inuse}
                />
            )
        })
    }
    filterDivExtenionToggle () {
            var filterDivExtension = document.querySelector('.filterDivExtension');
            var chevronUp = document.querySelector('#chevronUp');
            var chevronDown = document.querySelector('#chevronDown');
            if (filterDivExtension.style.display === 'block') {
                filterDivExtension.style.display = 'none';
                chevronUp.style.display = 'none';
                chevronDown.style.display = 'block';
            } else {
                filterDivExtension.style.display = 'block';
                chevronUp.style.display = 'block';
                chevronDown.style.display = 'none';
            }
    }
    createProductsList() {
        if(this.props.products.length > 0) {
            return this.props.products.map(product =>{
                if (this.props.products.indexOf(product) >= (this.state.activePage*12) - 12 && this.props.products.indexOf(product) < (this.state.activePage*12)) { //render the 12 (number of products per page) products that correspond to the current (active) page
                    return (
                        <ProductsListItem
                            key={product.id}
                            brand={product.brand}
                            model={product.model}
                            price={product.price}
                            image={product.image}
                            link={"/"+this.props.match.params.type+"/"+product.id}
                        />
                    )
                }

            })} else {
                return <div>No products match the filter criteria selected above.</div>
            } 
    }
    state = {
        activePage: 1
    }
    handlePageChange(pageNumber) {
        this.setState({activePage: pageNumber});
    }
    createPagination() {
        if (this.props.products.length > 12) {
            if (this.props.products.length > this.state.activePage * 12 && this.state.activePage > 1) { //if there are products following AND preceding the current page
                return (
                    <Pagination 
                        onclick1={() => this.handlePageChange(this.state.activePage - 1)}
                        onclick2={() => this.handlePageChange(this.state.activePage + 1)}
                        disabled1={false}
                        disabled2={false}
                    />
                )
            } else if (this.props.products.length > this.state.activePage * 12) { //if there are only products following the current page
                return (
                    <Pagination 
                        onclick1={() => this.handlePageChange(this.state.activePage - 1)}
                        onclick2={() => this.handlePageChange(this.state.activePage + 1)}
                        disabled1={true}
                        disabled2={false}
                    />
                ) 
            } else if (this.state.activePage > 1) { //if there are only products preceding the current page
                return (
                    <Pagination 
                        onclick1={() => this.handlePageChange(this.state.activePage - 1)}
                        onclick2={() => this.handlePageChange(this.state.activePage + 1)}
                        disabled1={false}
                        disabled2={true}
                    />
                ) 
            }

        }
    }
    render () {
        return (
            <div>
                <div className="container">
                    {this.createCategoryOverview()}
                    <div className="row">
                        <div className="col-12">
                            <div className= "filterDiv">
                                <div className="iconCrossbar">
                                    <i id="chevronDown" className="fa fa-chevron-down" onClick={this.filterDivExtenionToggle}></i>
                                    <i id="chevronUp" className="fa fa-chevron-up" onClick={this.filterDivExtenionToggle}></i>
                                </div>
                                <div className="filterDivExtension">
                                    <div className="row">
                                        <div className="filtersList col-md-5 col-11 mx-auto">
                                            Filter by Brand:
                                            <div>
                                                {this.createBrandFilterList()}
                                            </div>
                                        </div>
                                        <div className="filtersList col-md-5 col-11 mx-auto">
                                            Filter by Price Range:
                                            <div>
                                                {this.createPriceRangeFilterList()}
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div className="row productsList">
                        {this.createProductsList()}
                    </div>
                    {this.createPagination()}
                </div>
            </div>
        )
    }
};

function mapStateToProps(state , ownProps) {
    let brandFilters = state.brandFilters;
    let filtered_brandFilters = brandFilters;
    filtered_brandFilters = filtered_brandFilters.filter(
        filter => filter.type === ownProps.match.params.type //gets type from the the route params and finds products which have type that matches
    )
    let priceRangeFilters = state.priceRangeFilters;
    let filtered_priceRangeFilters = priceRangeFilters;
    filtered_priceRangeFilters = filtered_priceRangeFilters.filter(
        filter => filter.type === ownProps.match.params.type
    )
    let overviews = state.overviews;
    let overview = overviews.filter(
        overview => overview.type === ownProps.match.params.type
    )
    let products = state.products;
    let filtered_products = products;
    filtered_products = filtered_products.filter(
        product => product.type === ownProps.match.params.type
    )
    let activeBrandFilters = filtered_brandFilters.filter(
        item => item.inuse === true
    );
    activeBrandFilters.forEach(filter => {
        if (filter.brand != "ALL") {
            filtered_products = filtered_products.filter(
                product => product.brand === filter.brand
            )
        }
    });
    let activePriceRangeFilters = filtered_priceRangeFilters.filter(
        item => item.inuse === true
    );
    activePriceRangeFilters.forEach(filter => {
        if (filter.priceRange != "ALL") {
            filtered_products = filtered_products.filter(
                product => product.priceRange === filter.priceRange
            );
        }
    });
    let key = ownProps.match.params.type;
    return {
        overview: overview,
        brandFilters: filtered_brandFilters,
        priceRangeFilters: filtered_priceRangeFilters,
        products: filtered_products,
        key: key //a change of key property means the component remounts. this was needed so that when on a second page of products (state is activePage: 2) and switching to a 'page' with products type that does not have a second page (uses same components but displays different type of products), no products would be displayed because the component did not remount and thh state remained the same (activePage did not reset to 1)
    };
};

function mapDispatchToProps(dispatch){
    return bindActionCreators({changeBrandFilter: changeBrandFilter, changePriceFilter: changePriceFilter}, dispatch);
};

export const ProductsPageContainer = connect(mapStateToProps, mapDispatchToProps)(ProductsPage);

正如您所看到的渲染看起来很混乱所以我继续将整个过滤器列表部分分成如下所示的子容器组件:

import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';

import Filter from './Filter';

import changeBrandFilter from '../actions/changeBrandFilter';
import changePriceFilter from '../actions/changePriceFilter';

class FiltersLists extends React.Component {
    createBrandFilterList() {
        let i = 1;
        return this.props.brandFilters.map(filter => {
            i++
            return (
                <Filter
                    key={filter.brand+i+"brand"}
                    name={this.props.type + "brandFilter"} //so that each seperate group of radio buttons (filters) refer only to each other. (the name is shared within each group)
                    id={filter.brand}
                    changeFilterResetPageNumber={() => {this.props.changeBrandFilter(filter); this.props.handlePageChange(1)}} //without page reset would often get no products displayed on filter application due to the activePage state remaining at the page that was active at the time of filter application
                    inuse={filter.inuse}
                /> 
            )
        })
    }
    createPriceRangeFilterList() {
        let i = 1;
        return this.props.priceRangeFilters.map(filter => {
            i++
            return (
                <Filter
                    key={filter.priceRange+i+"priceRange"}
                    name={this.props.type + "priceFilter"} 
                    id={filter.priceRange}
                    changeFilterResetPageNumber={() => {this.props.changePriceFilter(filter); this.props.handlePageChange(1)}}
                    inuse={filter.inuse}
                />
            )
        })
    }
    filterDivExtenionToggle () {
            var filterDivExtension = document.querySelector('.filterDivExtension');
            var chevronUp = document.querySelector('#chevronUp');
            var chevronDown = document.querySelector('#chevronDown');
            if (filterDivExtension.style.display === 'block') {
                filterDivExtension.style.display = 'none';
                chevronUp.style.display = 'none';
                chevronDown.style.display = 'block';
            } else {
                filterDivExtension.style.display = 'block';
                chevronUp.style.display = 'block';
                chevronDown.style.display = 'none';
            }
    }
    render () {
        return (
            <div className="col-12">
                <div className= "filterDiv">
                    <div className="iconCrossbar">
                        <i id="chevronDown" className="fa fa-chevron-down" onClick={this.filterDivExtenionToggle}></i>
                        <i id="chevronUp" className="fa fa-chevron-up" onClick={this.filterDivExtenionToggle}></i>
                    </div>
                    <div className="filterDivExtension">
                        <div className="row">
                            <div className="filtersList col-md-5 col-11 mx-auto">
                                Filter by Brand:
                                <div>
                                    {this.createBrandFilterList()}
                                </div>
                            </div>
                            <div className="filtersList col-md-5 col-11 mx-auto">
                                Filter by Price Range:
                                <div>
                                    {this.createPriceRangeFilterList()}
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        )
    }
}

function mapStateToProps(state , ownProps) {
    let brandFilters = state.brandFilters;
    let filtered_brandFilters = brandFilters;
    filtered_brandFilters = filtered_brandFilters.filter(
        filter => filter.type === ownProps.match.params.type //gets type from the the route params and finds products which have type that matches
    )
    let priceRangeFilters = state.priceRangeFilters;
    let filtered_priceRangeFilters = priceRangeFilters;
    filtered_priceRangeFilters = filtered_priceRangeFilters.filter(
        filter => filter.type === ownProps.match.params.type
    )
    return {
        brandFilters: filtered_brandFilters,
        priceRangeFilters: filtered_priceRangeFilters,
//a change of key property means the component remounts. this was needed so that when on a second page of products (state is activePage: 2) and switching to a 'page' with products type that does not have a second page (uses same components but displays different type of products), no products would be displayed because the component did not remount and thh state remained the same (activePage did not reset to 1)
    };
};

function mapDispatchToProps(dispatch){
    return bindActionCreators({changeBrandFilter: changeBrandFilter, changePriceFilter: changePriceFilter}, dispatch);
};

export const FiltersListsContainer = connect(mapStateToProps, mapDispatchToProps)(FiltersLists);

现在父容器组件如下所示:

import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';

import changeBrandFilter from '../actions/changeBrandFilter';
import changePriceFilter from '../actions/changePriceFilter';

import CategoryOverview from './CategoryOverview';
import Filter from './Filter';
import {FiltersListsContainer} from './FiltersLists';
import ProductsListItem from './ProductsListItem';
import ProductsPageContainerCSS from './ProductsPageContainer.css';
import Pagination from './Pagination';

class ProductsPage extends React.Component{
    createCategoryOverview() {
        let i = 1;
        return this.props.overview.map(overview => {
            i++
            return (
                <CategoryOverview 
                    key={"catOverview"+i} //each child in an array or iterator should have a unique "key" prop
                    title={overview.title}
                    text={overview.text}
                    image={overview.imageSource}
                    alt={overview.imageAlt}
                />
            )
        })
    }
    createFiltersLists() {
        return (
            <FiltersListsContainer />
        )
    }
    createProductsList() {
        if(this.props.products.length > 0) {
            return this.props.products.map(product =>{
                if (this.props.products.indexOf(product) >= (this.state.activePage*12) - 12 && this.props.products.indexOf(product) < (this.state.activePage*12)) { //render the 12 (number of products per page) products that correspond to the current (active) page
                    return (
                        <ProductsListItem
                            key={product.id}
                            brand={product.brand}
                            model={product.model}
                            price={product.price}
                            image={product.image}
                            link={"/"+this.props.match.params.type+"/"+product.id}
                        />
                    )
                }

            })} else {
                return <div>No products match the filter criteria selected above.</div>
            } 
    }
    state = {
        activePage: 1
    }
    handlePageChange(pageNumber) {
        this.setState({activePage: pageNumber});
    }
    createPagination() {
        if (this.props.products.length > 12) {
            if (this.props.products.length > this.state.activePage * 12 && this.state.activePage > 1) { //if there are products following AND preceding the current page
                return (
                    <Pagination 
                        onclick1={() => this.handlePageChange(this.state.activePage - 1)}
                        onclick2={() => this.handlePageChange(this.state.activePage + 1)}
                        disabled1={false}
                        disabled2={false}
                    />
                )
            } else if (this.props.products.length > this.state.activePage * 12) { //if there are only products following the current page
                return (
                    <Pagination 
                        onclick1={() => this.handlePageChange(this.state.activePage - 1)}
                        onclick2={() => this.handlePageChange(this.state.activePage + 1)}
                        disabled1={true}
                        disabled2={false}
                    />
                ) 
            } else if (this.state.activePage > 1) { //if there are only products preceding the current page
                return (
                    <Pagination 
                        onclick1={() => this.handlePageChange(this.state.activePage - 1)}
                        onclick2={() => this.handlePageChange(this.state.activePage + 1)}
                        disabled1={false}
                        disabled2={true}
                    />
                ) 
            }

        }
    }
    render () {
        return (
            <div>
                <div className="container">
                    {this.createCategoryOverview()}
                    <div className="row">
                        {this.createFiltersLists()}
                    </div>
                    <div className="row productsList">
                        {this.createProductsList()}
                    </div>
                    {this.createPagination()}
                </div>
            </div>
        )
    }
};

function mapStateToProps(state , ownProps) {
    let brandFilters = state.brandFilters;
    let filtered_brandFilters = brandFilters;
    filtered_brandFilters = filtered_brandFilters.filter(
        filter => filter.type === ownProps.match.params.type //gets type from the the route params and finds products which have type that matches
    )
    let priceRangeFilters = state.priceRangeFilters;
    let filtered_priceRangeFilters = priceRangeFilters;
    filtered_priceRangeFilters = filtered_priceRangeFilters.filter(
        filter => filter.type === ownProps.match.params.type
    )
    let overviews = state.overviews;
    let overview = overviews.filter(
        overview => overview.type === ownProps.match.params.type
    )
    let products = state.products;
    let filtered_products = products;
    filtered_products = filtered_products.filter(
        product => product.type === ownProps.match.params.type
    )
    let activeBrandFilters = filtered_brandFilters.filter(
        item => item.inuse === true
    );
    activeBrandFilters.forEach(filter => {
        if (filter.brand != "ALL") {
            filtered_products = filtered_products.filter(
                product => product.brand === filter.brand
            )
        }
    });
    let activePriceRangeFilters = filtered_priceRangeFilters.filter(
        item => item.inuse === true
    );
    activePriceRangeFilters.forEach(filter => {
        if (filter.priceRange != "ALL") {
            filtered_products = filtered_products.filter(
                product => product.priceRange === filter.priceRange
            );
        }
    });
    let key = ownProps.match.params.type;
    return {
        overview: overview,
        brandFilters: filtered_brandFilters,
        priceRangeFilters: filtered_priceRangeFilters,
        products: filtered_products,
        key: key //a change of key property means the component remounts. this was needed so that when on a second page of products (state is activePage: 2) and switching to a 'page' with products type that does not have a second page (uses same components but displays different type of products), no products would be displayed because the component did not remount and thh state remained the same (activePage did not reset to 1)
    };
};

function mapDispatchToProps(dispatch){
    return bindActionCreators({changeBrandFilter: changeBrandFilter, changePriceFilter: changePriceFilter}, dispatch);
};

export const ProductsPageContainer = connect(mapStateToProps, mapDispatchToProps)(ProductsPage);

现在,当我尝试渲染整个事情时,它会抛出一个错误,说明TypeError:无法读取未定义的属性'params'。这与子容器组件有关。我似乎无法从子容器中读取路径参数,即使它是通过在路由上呈现的父组件呈现的。

有没有办法做到这一点?

另外要注意的是我试图将子容器组件作为表示组件传递给演示相关的道具和动作相关的道具。这个按照惯例渲染和出现,但是当我点击一个过滤器时,动作创建者没有使用正确的参数进行调用,并且它会抛出错误,无法读取未定义的属性“品牌”。很明显,这个行动并没有明确说明它被称为什么。

那么有没有一种方法可以将行动从容器传递给表现性的孩子,然后传递给下一个表现性的孩子(个体过滤器)?

PS。我真的很抱歉这个极长的啰嗦问题,但任何帮助都将不胜感激!谢谢

reactjs react-redux react-router-v4
1个回答
0
投票

对于那些可能对此人感到恼火的人,我很抱歉,但我现在有了解决方案。

所以我回到了第一种方式,我尝试这样做只是为了将FiltersLists分离成一个本身不是容器的子组件,只是简单地传递道具和动作创建者,然后依次传递动作创建者从该组件到单个过滤器。

正如我现在完全意识到的那样(我认为),我的斗争是让动作创造者调用正确的元素。

最初我在容器组件(ProductsPageContainer)中有这样的东西:

        brandFilterClick={() => this.props.changeBrandFilter()} 
        priceFilterClick={() => this.props.changePriceFilter()}

在其直接子组件(FiltersLists)中跟随此:

createBrandFilterList() {
    let i = 1;
    return this.props.brandFilters.map(filter => {
        i++
        return (
            <Filter
                key={filter.brand+i+"brand"}
                name={this.props.type + "brandFilter"} //so that each seperate group of radio buttons (filters) refer only to each other. (the name is shared within each group)
                id={filter.brand}
                onFilterClick={() => {this.props.brandFilterClick(filter)}} //action creator passed from parent container component
                inuse={filter.inuse}
            />
        )
    })
}
createPriceRangeFilterList() {
    let i = 1;
    return this.props.priceRangeFilters.map(filter => {
        i++
        return (
            <Filter
                key={filter.priceRange+i+"priceRange"}
                name={this.props.type + "priceFilter"} 
                id={filter.priceRange}
                onFilterClick={() => {this.props.priceFilterClick(filter)}}
                inuse={filter.inuse}
            />
        )
    })
}

然后这在树中最低的子组件(Filter)中:

            <input type="radio"
                defaultChecked={this.props.inuse}
                name={this.props.name}
                onClick={() => this.props.onFilterClick()} //action creator passed down from container component
                />
            {'\u00A0'}{'\u00A0'}
            <label ref="text">{this.props.id}</label>

问题出现在这个父容器组件中,我所要做的就是将其更改为:

brandFilterClick={this.props.changeBrandFilter} //action creator passed down. this will not be called on the component that this function creates, but on the element in the component that is rendered by the component that this function creates - (ProductsPageContainer > FiltersLists > Filter - **input**). this is why no curley brackets. '()' were used here but in the component below - follow this action down the tree for more clarity.
priceFilterClick={this.props.changePriceFilter}

正如我在我的代码评论中所述。我认为这是因为动作创建者不是作为直接渲染的组件上的函数调用的,而是在由该组件呈现的组件上。

虽然我最终设法自己到达那里,但我希望它可能仍然对其他有类似问题的人有用。

© www.soinside.com 2019 - 2024. All rights reserved.