Я работаю с 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
Как я могу это исправить? Погуглил вопросы картирования, но не нашел желаемых результатов. Любая помощь будет принята с благодарностью.