Неожиданный рендеринг, вызванный map () при смене реквизита - PullRequest
0 голосов
/ 31 августа 2018

Я работаю с React и Redux. Я хочу, чтобы Redux сохранял информацию о клиенте (размеры области просмотра, имена и версии браузера, операционные системы и т. Д.). Я уже настроил как редукторы, так и действия, и сопоставил все состояния с реквизитами компонента <App />. Внутри компонента <App /> есть компонент <Header />, который передает реквизиты компоненту <NavigationBar />. <NavigationBar /> затем отображает реквизиты для рендеринга <NavigationItems />, который затем создает следующую панель навигации:

Home  | About us | Apply | News | Login

Однако из-за сопоставления реквизита внутри компонента <App /> каждый раз, когда пользователь изменяет размер области просмотра, сообщает об изменениях внутри хранилища Redux, вызывает изменение реквизита внутри <App />, и, конечно же, React рендерит всю панель навигации. производит следующее неожиданное поведение:

Home | Home | About us | Apply | News | Login

Или

About us | Home | About us | Apply | News | Login

Короче говоря <NavigationBar /> возвращает больше предметов, чем должно. Я включил структуру кода для лучшей иллюстрации:

Store (contains client info) 
---> <App /> (map states, dispatchers from store to props) 
---> <Header /> (functional component acts as container, pass navigation settings as props down to <NavigationBar />)
---> <NavigationBar /> (receives props from <Header /> and renders <NavigationItems /> component with props from <Header /> using map() function)
---> <NavigationItems /> (renders navigation items based on the props)

Я также включил код на всякий случай:

rootTypes.js (определяет типы действий)

export const tUpdateViewport = 'updateViewport'

rootActions.js (определяет диспетчер)

import { tUpdateViewport } from './rootTypes';

export const aUpdateViewport = () => ({type: tUpdateViewport })

rootReducer.js (определяет редуктор приложения и объединяет его с другими)

import { tUpdateViewport } from './rootTypes';
import { combineReducers } from 'redux';

const appStates = {
    viewportWidth: document.documentElement.clientWidth,
    viewportHeight: document.documentElement.clientHeight,
}; 

const appReducer = (states = appStates, action) => {
    switch(action.type) {
        case tUpdateViewport: 
            return {
                ...states,
                viewportWidth: document.documentElement.clientWidth,
                viewportHeight: document.documentElement.clientHeight,
            };
        default:
            return states;
    }
};

export default combineReducers({
    app: appReducer,
})

App.js (Определяет <App /> компонент и отображает <Header />)

import React from 'react';
import Header from './components/container/header/Header';
import * as actions from './rootActions';
import { connect } from 'react-redux';

const mapStatesToProps = states => ({
    browserInfo: states.app
});

class App extends React.Component {
    componentDidMount = () => {
        window.addEventListener('resize', this.props.aUpdateViewport, false);
    };
    componentWillUnmount = () => {
        window.removeEventListener('resize', this.props.aUpdateViewport, false);
    };
  render = () => {
    return (
            <Header />
    );
  };
}

export default connect(mapStatesToProps, actions)(App)

Header.js (Определяет <Header /> компонент и отображает <NavigationBar /> компонент)

import React from 'react';
import NavigationBar from '../../presentational/header/NavigationBar';
import { Container, Grid, Row, Col } from '../../../css/Grid.css';
import { Header as Navigator } from '../../../css/Layout.css';
import logo from '../../../media/images/logo.png';

const Header = props => {
    return (
        <Navigator>
            <Container>
                <header>
                    <Grid>
                        <Row>
                            <Col>
                                <a href="#"><img src={logo} alt="CKY-UK Logo" /></a>
                            </Col>
                            <Col stretchWidth>
                                <Grid>
                                    <Row stretchHeight alignEnd="md" alignMiddle="md">
                                        <NavigationBar settings={{
                                            items:
                                            [
                                                {
                                                    title: 'Home',
                                                    link: '/'
                                                },
                                                {
                                                    title: 'About us',
                                                    dropdownHierarchy: 'parents',
                                                    icons: [{
                                                        title: 'chevron-down',
                                                        iconPos: 'afterText'
                                                    }],
                                                    items: [{
                                                        title: 'Leadership Team',
                                                        link: '#leadership-team'
                                                    }, 
                                                    {
                                                        title: 'Star players',
                                                        link: '#star-players'
                                                    }]
                                                },
                                                {
                                                    title: 'Apply',
                                                    link: '#apply'
                                                },
                                                {
                                                    title: 'News',
                                                    link: '#news'
                                                },
                                                { 
                                                    title: 'Login',
                                                    link: '#login',
                                                    icons: [{
                                                        title: 'sign-in-alt',
                                                        iconPos: 'beforeText'
                                                    }],
                                                }
                                            ]
                                            }} />
                                    </Row>
                                </Grid>
                            </Col>
                        </Row>
                    </Grid>
                </header>
            </Container>
        </Navigator>
    );
};

export default Header

NavigationBar.js (Определяет <NavigationBar /> компонент и отображает реквизиты для рендеринга <NavigationItems /> компонента)

import React from 'react';
import NavigationItems from './NavigationItems';
import { uniqID } from '../../../common/Functions';
import { Row } from '../../../css/Grid.css';

const NavigationBar = props => {
    const settings = props.settings;
    return (
        <nav>
            <ul>
                <Row>
                    {settings.items.map((contents, index) => 
                        <NavigationItems settings={{...contents}} isDropdown={contents.items ? true : false} key={uniqID()} />
                    )}
                </Row>
            </ul>
        </nav>
    );
};

export default NavigationBar

NavigationItems.js (определяет <NavigationItems /> и отображает панель навигации)

import React from 'react';
import { uniqID } from '../../../common/Functions';
import { NavLi, NavItem, Dropdown } from '../../../css/Navigation.css';
import { Icon } from '../../../css/Common.css';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

class NavigationItems extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            dropdownOpened: false,
            navLiWidth: null,
            navLiHeight: null
        }
    }
    handleClick = e => {
        e.preventDefault();
        this.setState(prevState => ({
            dropdownOpened: !prevState.dropdownOpened
        }));
    };
    handleClickOutside = e => {
        const target = e.target;
        if (!this.NavLi.contains(target)) {
            this.setState({
                dropdownOpened: false
            })
        }
    }
    componentDidMount = () => {
        const navLiWidth = this.NavLi.clientWidth,
                    navLiHeight = this.NavLi.clientHeight;
        this.setState({
            navLiWidth: navLiWidth,
            navLiHeight: navLiHeight
        });
        document.addEventListener('click', this.handleClickOutside, false);
    }
    componentWillUnmount = () => {
        document.removeEventListener('click', this.handleClickOutside, false);
    }
    render = () => {
        const settings = this.props.settings,
                    isDropdown = this.props.isDropdown
        return (
            <NavLi isDropdown={isDropdown} innerRef={x => this.NavLi = x}>
                <NavItem href={settings.link || '#'} onClick={isDropdown ? this.handleClick : null}>
                {settings.icons && settings.icons.filter(contents => contents.iconPos === 'beforeText').map(contents => 
                    <Icon beforeText key={uniqID()}><FontAwesomeIcon icon={contents.title} fixedWidth /></Icon> 
                )} 
                {settings.title}
                {settings.icons && settings.icons.filter(contents => contents.iconPos === 'afterText').map(contents => 
                    <Icon afterText key={uniqID()}><FontAwesomeIcon icon={contents.title} fixedWidth /></Icon>
                )}
                </NavItem>
                {isDropdown && 
                    <Dropdown hierarchy={settings.dropdownHierarchy} opened={this.state.dropdownOpened} topPos={this.state.navLiHeight} leftPos={this.state.navLiWidth} desPos={10} startPos={50}>
                        {settings.items.map(contents => 
                            <NavigationItems settings={{...contents}} isDropdown={contents.items ? true : false} key={uniqID()} />
                                                                                                                                                                                                                                                                                                                                    )} 
                                                                                                                                                                                                                                                                                                         </Dropdown>
              }
        </NavLi> 
        );
    };
};

export default NavigationItems

Как я могу это исправить? Погуглил вопросы картирования, но не нашел желаемых результатов. Любая помощь будет принята с благодарностью.

1 Ответ

0 голосов
/ 31 августа 2018

Полагаю, uniqID - это просто генератор случайных чисел?

key prop определенно существует, чтобы быть non -лучайным. Измените его на согласованное и уникальное значение для каждого цикла, например title, и это, вероятно, решит вашу проблему.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...