I'm new to the reagent and Redux. I am trying to make the menu, but the console getting error:
App.js?eb5a:12 Uncaught TypeError: Cannot read property 'handleClickMenu' of undefined
How to fix the error?
As the payload in a new state record?
App.js:
import React, { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import User from '../components/User'
import Page from '../components/Page'
import BottomMenu from '../components/BottomMenu'
import * as pageActions from '../actions/PageActions'
import * as userActions from '../actions/UserActions'
import * as bmenuActions from '../actions/BottomMenuActions'
class App extends Component {
render() {
const { user, page, bottomMenu } = this.props
const { getPhotos } = this.props.pageActions
const { handleClickMenu } = this.props.bmenuActions
const { handleLogin } = this.props.userActions
return <div className='row'>
<Page photos={page.photos} year={page.year} getPhotos={getPhotos} fetching={page.fetching} error={page.error}/>
<User name={user.name} handleLogin={handleLogin} error={user.error} />
<BottomMenu selectedItem={bottomMenu.selectedItem} bmenuClick={() => handleClickMenu} />
</div>
}
}
function mapStateToProps(state) {
return {
user: state.user,
page: state.page,
bottomMenu: state.bottomMenu
}
}
function mapDispatchToProps(dispatch) {
return {
pageActions: bindActionCreators(pageActions, dispatch),
userActions: bindActionCreators(userActions, dispatch),
bmenuActions: bindActionCreators(bmenuActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
component bottomMenu.js:
import React, { PropTypes, Component } from 'react'
import BottomMenuItem from '../components/BottomMenuItem'
export default class BottomMenu extends Component {
render() {
const { selectedItem, bmenuClick } = this.props;
const menuItems = [{
url: 'home',
name: 'Главная страница'
}, {
url: 'goods',
name: 'Объем поставок'
}, {
url: 'geo',
name: 'География поставок'
}, {
url: 'clients',
name: 'Клиенты и партнеры'
}, {
url: 'production',
name: 'Виды продукции'
}, {
url: 'cost',
name: 'Рассчитайте стоимость'
}, {
url: '',
name: 'Свяжитесь с нами'
}];
return <ul className='footer-menu'>
{ menuItems.map((item,index) => <BottomMenuItem key={index} item={item} selected={index === selectedItem} bmenuClick={bmenuClick} /> )}
</ul>
}
}
BottomMenu.propTypes = {
item: PropTypes.object,
selected: PropTypes.bool
}
component BottomMenuItem.js:
import React, { PropTypes, Component } from 'react'
export default class BottomMenuItem extends Component {
bmenu_Click(e){
e.preventDefault()
this.props.bmenuClick(e)
}
render() {
const { item, selected} = this.props
const className = 'footer-menu__li footer-menu__li--' + item.url + (selected ? ' footer-menu__li--current' : '')
return <li className={className}>
{!selected ?
<a href={item.url} className='footer-menu__href' onClick={::this.bmenu_Click}>{item.name}</a>
:
<span className='footer-menu__href'>{item.name}</span>
}
</li>
}
}
BottomMenuItem.propTypes = {
item: PropTypes.object.isRequired,
selected: PropTypes.bool.isRequired,
bmenuClick: PropTypes.func.isRequired
}
action: BottomMenuAction.js
export function handleClickMenu(el) {
console.log(el)
return function(dispatch) {
dispatch({
type: 'bmenuClick',
payload:''
})
}
}
A few mistakes in your code
First is a typo where you bindAction as bmenuActions and then in your App.js you are using it with the wrong case as const { handleClickMenu } = this.props.bMenuActions. You need to change that to const { handleClickMenu } = this.props.bmenuActions
Also now since here handleClickMenu is a function you need to bind it while passing down to BottomMenu component like
<BottomMenu selectedItem={bmenu.selectedItem} bmenuClick={() => handleClickMenu} />
Now again in App.js your state is available as bottomMenu and you are resolving it like bmenu. Change it to
const { user, page, bottomMenu } = this.props
Now from BottomMenu component you are sending the props as bmenuClick to the BottomMenuItem component like
return <ul className='footer-menu'>
{ menuItems.map((item,index) => <BottomMenuItem key={index} item={item} selected={item === selectedItem} bmenuClick={() => bmenuClick }/> )}
</ul>
but you are using it in your BottomMenuItem component as this.props.handleClickMenu here
bmenu_Click(e){
this.props.handleClickMenu(e)
}
You need to change it as
bmenu_Click(e){
this.props.bmenuClick(e)
}
Thank you, I've done:
<BottomMenuItem key={index} item={item} pos={index} selected={index === selectedItem} bmenuClick={bmenuClick} />
and
<a href={item.url} className='footer-menu__href' onClick={::this.bmenu_Click} data-pos={pos}>{item.name}</a>
Related
I have a react app with a large menu, and as such am trying to move it to a seperate file from the main app.js
at the mement when you click on a link in the menu it call a node api and which returns some data, however when I try to seperate I can not get it to populate the results section which is still in the main script
Working version app.js
import React,{ useState } from 'react';
import './App.css';
import axios from 'axios';
import { Navigation } from "react-minimal-side-navigation";
import "react-minimal-side-navigation/lib/ReactMinimalSideNavigation.css";
export default class MyList extends React.Component {
constructor(props) {
super(props);
this.state = {
result: [],
};
this.callmyapi = this.callmyapi.bind(this);
}
render() {
return (
<div>
<div class="menu">
<Navigation
onSelect={({itemId}) => {
axios.get(`/api/menu/`, {
params: {
Menu: itemId,
}
})
.then(res => {
const results = res.data;
this.setState({ results });
})
.catch((err) => {
console.log(err);
})
}}
items={[
{
title: 'Pizza',
itemId: '/menu/Pizza/',
},
{
title: 'Cheese',
itemId: '/menu/cheese',
}
]}
/>
</div>
<div class="body">
this.state.results && this.state.results.map(results => <li>* {results.Name}</li>);
</div>
</div>
);
}
}
New app.js
import React,{ useState } from 'react';
import './App.css';
//import axios from 'axios';
//import { Navigation } from "react-minimal-side-navigation";
//import "react-minimal-side-navigation/lib/ReactMinimalSideNavigation.css";
import MyMenu from './mymenu';
export default class MyList extends React.Component {
constructor(props) {
super(props);
this.state = {
result: [],
};
this.callmyapi = this.callmyapi.bind(this);
}
render() {
return (
<div>
<div class="menu">
<MyMenu />
</div>
<div class="body">
this.state.results && this.state.results.map(results => <li>* {results.Name}</li>);
</div>
</div>
);
}
}
New menu file
mymenu.js
import React, { Component } from 'react';
import axios from 'axios';
import './App.css';
//import MyList from './App.js';
//import { ProSidebar, Menu, MenuItem, SubMenu } from 'react-pro-sidebar';
//import 'react-pro-sidebar/dist/css/styles.css';
import { Navigation } from "react-minimal-side-navigation";
//import Icon from "awesome-react-icons";
import "react-minimal-side-navigation/lib/ReactMinimalSideNavigation.css";
//export default async function MyMenu(){
export default class MyMenu extends React.Component {
constructor(props) {
super(props);
};
render() {
return (
<div>
<Navigation
// you can use your own router's api to get pathname
activeItemId="/management/members"
onSelect={({itemId}) => {
// return axios
axios.get(`/api/menu/`, {
params: {
// Menu: itemId,
Menu: "meat",
SubMenu : "burgers"
}
})
.then(res => {
const results = res.data;
this.setState({ results });
})
.catch((err) => {
console.log(err);
})
}}
items={[
{
title: 'Pizza',
itemId: '/menu/Pizza/',
},
{
title: 'Cheese',
itemId: '/menu/cheese',
}
]}
/>
</div>
);
}
}
Any help would be greatly appreciated
That one is quite easy once you understand state. State is component specific it that case. this.state refers to you App-Component and your Menu-Component individually. So in order for them to share one state you have to pass it down the component tree like this.
export default class MyList extends React.Component {
constructor(props) {
super(props);
this.state = {
result: [],
};
}
render() {
return (
<div>
<div class="menu">
<MyMenu handleStateChange={(results: any[]) => this.setState(results)} />
</div>
<div class="body">
this.state.results && this.state.results.map(results => <li>* {results.Name}</li>);
</div>
</div>
);
}
}
See this line: <MyMenu handleStateChange={(results: any[]) => this.setState(results)} />
There you pass a function to mutate the state of App-Component down to a the child
There you can call:
onSelect={({itemId}) => {
// return axios
axios.get(`/api/menu/`, {
params: {
// Menu: itemId,
Menu: "meat",
SubMenu : "burgers"
}
})
.then(res => {
const results = res.data;
this.props.handleStateChange(results)
})
.catch((err) => {
console.log(err);
})
You mutate the parent state and the correct data is being rendered. Make sure to practice state and how it works and how usefull patterns look like to share state between components.
Thanks - I Have found solution (also deleted link question)
above render added function
handleCallback = (results) =>{
this.setState({data: results})
}
then where I display the menu
<MyMenu parentCallback = {this.handleCallback}/>
where i display the results
{this.state.results && this.state.results.map(results => <li>{results.Name}</li>}
No aditional changes to the menu scripts
I'm creating the redux state in this page :
import React from 'react';
import { connect } from 'react-redux';
import styled from 'styled-components';
import wrapper from '../redux/store';
import Container from '../components/Container/Container';
import Card from '../components/Card/Card';
import Circle from '../components/Circle/Circle';
import PieChart from '../components/PieChart/PieChart';
import Accordion from '../components/Accordion/Accordion';
import RadioButton from '../components/Ui/RadioButton/RadioButton';
import { manageList, reportList } from '../components/helper';
import { getManageListAndCategoryId } from '../redux/actions/actions';
const Panel = ({ manageProductsList }) => (
<>
{console.log(manageProductsList)}
<MainContainer>
<Title>Управление</Title>
<ContainersWrapper>
{manageProductsList.map((item, index) => <Card key={index} title={item.title} type="service" serviceName={item.value} />)}
</ContainersWrapper>
<SecondSection>
<CustomContainer>
<Title>Отчетность</Title>
<p>Показатели за:</p>
Здесь будут ТАБЫ
<ContainersWrapper>
{reportList.map((item, index) => <Card key={index} item={item} type="report" />)}
</ContainersWrapper>
<DiagreammWrapper>
<PieChart />
<Circle percent={20} />
<Circle percent={87} />
<Circle percent={30} />
<Circle percent={47} />
</DiagreammWrapper>
</CustomContainer>
</SecondSection>
<CustomContainer>
<TitleTwo>Доступные отчеты</TitleTwo>
<Accordion />
<RadioButton />
</CustomContainer>
</MainContainer>
</>
);
export const getStaticProps = wrapper.getStaticProps(async ({ store }) => {
store.dispatch(getManageListAndCategoryId(manageList));
});
const mapStateToProps = (state) => ({
manageProductsList: state.mainReducer.manageProductsList,
});
export default connect(mapStateToProps, null)(Panel);
And I still can see the data manageProductsList (screenshot) in Redux in this page. But when I navigate to another dynamic route page forms/[id.tsx]
import React from 'react';
import { connect } from 'react-redux';
import wrapper from '../redux/store';
import { util, manageList, reportList } from '../../components/helper';
import { getManageListAndCategoryId } from '../../redux/actions/actions';
export async function getStaticPaths(categoryIds) {
console.log('categoryIds', categoryIds);
//temporarely make static path data while categoryIds is undefined
const paths = [
{ params: { id: 'object' } },
{ params: { id: 'service' } },
{ params: { id: 'club_cards' } },
{ params: { id: 'schedule' } },
{ params: { id: 'agents' } },
{ params: { id: 'abonements' } },
{ params: { id: 'price_category' } },
{ params: { id: 'person_data' } },
{ params: { id: 'roles' } },
];
return {
paths,
fallback: false,
};
}
export async function getStaticProps({ params, manageProductsList }) {
// const postData = util.findFormData(params.id, manageProductsList);
const postData = { title: 'asdsadasdsad' };
return {
props: {
postData,
},
};
}
const Form = ({ manageProductsList }) => (
<div>
{console.log(manageProductsList)}
{/* {postData.title} */}
dasdsadsad
</div>
);
const mapStateToProps = (state) => ({
categoryIds: state.mainReducer.categoryIds,
manageProductsList: state.mainReducer.manageProductsList,
});
export default connect(mapStateToProps, null)(Form);
the manageProductsList and categoryIds are empty arrays (screenshot 2)
I am using native Link from next/link component to navigate the page
Here is Card component which navigate to dynamic page:
import React, { FunctionComponent, HTMLAttributes } from 'react';
import styled from 'styled-components';
import Link from 'next/link';
import EditIcon from '#material-ui/icons/Edit';
import AddIcon from '#material-ui/icons/Add';
interface CardProps extends HTMLAttributes<HTMLOrSVGElement>{
title: string
type: string
item?: {
title: string
amount: number
}
serviceName: string
}
const Card: FunctionComponent<CardProps> = ({
type, title, serviceName, item,
}) => (
<>
{
type === 'service'
&& (
<FirstSection>
<h1>{title}</h1>
<ImageWrapper>
<Link href={`/forms/${serviceName}`}>
<a><AddIcon fontSize="large" onClick={(e) => { console.log(serviceName); }} /></a>
</Link>
<EditIcon />
</ImageWrapper>
</FirstSection>
)
}
{
type === 'report'
&& (
<SecondSection>
<h1>{item.title}</h1>
<p>{item.amount}</p>
</SecondSection>
)
}
</>
);
export default Card;
I would be very gratefull if someone can help
Your <Link> will cause server-side rendering, you can observe whether the browser tab is loading or not when navigate to another page. If it is, the page will reload and the redux state would be refresh.
The official docs shows the right way for using dynamic route.
<Link href="/forms/[id]" as={`/forms/${serviceName}`}>
I have a problem when trying to add my redux array to a component’s state:
componentDidMount() {
this.props.cardAction()
this.setState({ showCards: this.props.cardAction() })
}
hhhhhh undefined console log undefined
Here’s my dashboard code:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import Header from '../../common/Header/'
import Masonry from '../../common/Masonry/'
import { cardAction } from '../../store/actions/Cards'
import Arrow_Down from '../../assets/img/arrow-down.svg'
class Dashboard extends Component {
componentDidMount() {
this.props.cardAction()
this.setState({ showCards: this.props.cardAction() })
}
constructor(props) {
super(props)
this.state = {
collapsed: true,
class: 'collapsed',
showCards: {},
}
this.toggleCollapse = this.toggleCollapse.bind(this);
}
toggleCollapse(i, info) {
console.log('i', info, 'iiiii', i)
this.setState({
collapsed: !this.state.collapsed,
class: this.state.collapsed ? '' : 'collapsed',
showCards: info
}, () => {
// my state is updated here !
console.log('cardsss', this.state.showCards)
})
if (this.state.showCards === 'active') {
let carddd = this.state.showCards
this.setState({
showCards: {
...this.state.showCards,
open: 'inactive'
}
});
}
else {
this.setState({
showCards: {
...this.state.showCards,
open: 'active'
}
});
}
}
render() {
console.log('hhhhhh', this.state.showCards)
const cardList = this.props.Cards.map((info, i) => {
return (
<div className={(info.open === 'active') ? 'collapsed' : ''} key={i}>
<div className={(info.open === 'active') ? 'header flex space-between active' : 'header flex space-between'}>
<h2>{info.title}</h2>
<span onClick={() => { this.toggleCollapse(i, info) }}><img src={Arrow_Down} alt='Arrow' /></span>
</div>
<div className='content'>
<p>{info.description}</p>
</div>
</div>
)
})
return (
<div>
<Header />
<Masonry columns={3} gap={20}>
{cardList}
</Masonry>
</div>
)
}
}
Dashboard.defaultProps = {
columns: 2,
gap: 20,
Cards: []
}
Dashboard.propTypes = {
Cards: PropTypes.array.isRequired,
}
const mapStateToProps = state => {
return { Cards: state.cards.result }
}
const mapDispatchToProps = dispatch => ({
cardAction: () => dispatch(cardAction())
})
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard)
this.props.cardAction() is a redux action, it's not meant for you to directly assign to state, reason is redux action will return to reducer, not component. You should remove the setState in componentDidMount
componentDidMount() {
this.props.cardAction();
}
When you invoked this.props.cardAction(), it will call the function that defined in redux action file, and the result will be available at this.props.Cards as you mentioned above
const mapStateToProps = state => {
return { Cards: state.cards.result }
}
I have to render few tabs. ONclick of it, it should get highlighted. I have tabList object coming from reducer (hard-coded value). onclicking of a tab a action is generated which set the "state" with clicked tab object(I call it "activeTab").
while rendering the tabList (all the tabs) I am checking if "rendered_tabid == active_tabid" then add a class "active" (it is conditional basis)
active-tab-reducer.js
export const tabReducer = () => {
return [
{
id: 1,
name:"Dental Page",
url: '#dentalPage'
},
{
id: 2,
name:"Vision Page",
url: '#visionPage'
},
{
id: 3,
name:"Other page Tab",
url: '#OtherPage'
}
]
}
const activeTabReducer = (state = {}, action) => {
switch(action.type) {
case "TAB_SELECTED": return action.payload;
break;
}
return state;
}
export default activeTabReducer;
(combined-reducer) index.js
import {combineReducers} from 'redux';
import activeTabReducer, {tabReducer} from './active-tab-reducer';
const allReducers = combineReducers({
tabList: tabReducer,
activeTab: activeTabReducer
});
export default allReducers;
(action) index.js
export const selectTab = (tab) => {
console.log('action invoked', tab);
return {
type: "TAB_SELECTED",
payload: tab
}
}
tablist.js
import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {selectTab} from '../actions/index';
import TabsListItem from './tabsListItem';
class TabList extends React.Component {
constructor(props){
super(props);
}
createTabItems(){
return this.props.tabList.map((item, i) => {
return (
<TabsListItem key={i} tabList={item} />
)
});
}
render() {
return (
<div id="layout-header" className="layout-header">
<div id="header" className="header">
<ul className="tabs tabs--horizontal">
{this.createTabItems()}
</ul>
</div>
</div>
);
}
};
function mapStateToProps(state) {
return {
tabList: state.tabList,
activeTab: state.activeTab
}
}
function matchDispatchToProps(dispatch){
return bindActionCreators({selectTab: selectTab}, dispatch);
}
export default connect(mapStateToProps, matchDispatchToProps)(TabList);
tabListItem.js
import React from 'react';
class TabListItem extends React.Component {
constructor(props){
super(props);
console.log(this.props.tabList)
}
render() {
return (
<li onClick={() => this.props.selectTab(this.props.tabList)}
className={"tab "+((this.props.activeTab.id==item.id)?'active':'')+""+( (this.props.activeTab.id==undefined) && (item.id == 1)?'active':'' )
role="presentation" className={"tab " }>
<a href="#">
<div className="tab__label">
<div className="tab__label__value">{this.props.tabList.name}</div>
</div>
</a>
</li>
);
}
};
export default TabListItem;
when I click any tab (from tabListItem), a action TAB_SELECTED action should dispatch, which set the state with "activeTab" object.
How to generate action from child?
You should pass a function to the child component via props.
As the action has a parameter to select the correct tab, you can use a function returning a function:
createTabItems() {
return this.props.tabList.map((item, i) => {
return (
<TabsListItem key={i} tabList={item} onSelect={() => this.onSelect(i).bind(this)} />
);
});
}
In this way your child component calls your method onSelect passing the correct parameter.
In your onSelect method on the parent (container) component you will then dispatch your action:
onSelect(i) {
this.props.selectTab(i);
}
I am slowly learning React and also learning to implement it with Redux. But I seem to have hit a road block. So this is what I have so far.
/index.jsx
import './main.css'
import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/App.jsx'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import ShoppingList from './reducers/reducer'
let store = createStore(ShoppingList)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
)
/actions/items.js
import uuid from 'node-uuid'
export const CREATE_ITEM = 'CREATE_ITEM'
export function createItem(item) {
return {
type: CREATE_ITEM,
item: {
id: uuid.v4(),
item,
checked: false
}
}
}
/reducers/reducer.js
import * as types from '../actions/items'
import uuid from 'node-uuid'
const initialState = []
const items = (state = initialState, action) => {
switch (action.type) {
case types.CREATE_ITEM:
return {
id: uuid.v4(),
...item
}
default:
return state;
}
}
export default items
/reducers/index.js
UPDATE:
import { combineReducers } from 'redux'
import items from './reducer'
const ShoppingList = combineReducers({
items
})
export default ShoppingList
/components/Item.jsx
import React from 'react';
import uuid from 'node-uuid'
export default class Item extends React.Component {
constructor(props) {
super(props);
this.state = {
isEditing: false
}
}
render() {
if(this.state.isEditing) {
return this.renderEdit();
}
return this.renderItem();
}
renderEdit = () => {
return (
<input type="text"
ref={(event) =>
(event ? event.selectionStart = this.props.text.length : null)
}
autoFocus={true}
defaultValue={this.props.text}
onBlur={this.finishEdit}
onKeyPress={this.checkEnter}
/>
)
};
renderDelete = () => {
return <button onClick={this.props.onDelete}>x</button>;
};
renderItem = () => {
const onDelete = this.props.onDelete;
return (
<div onClick={this.edit}>
<span>{this.props.text}</span>
{onDelete ? this.renderDelete() : null }
</div>
);
};
edit = () => {
this.setState({
isEditing: true
});
};
checkEnter = (e) => {
if(e.key === 'Enter') {
this.finishEdit(e);
}
};
finishEdit = (e) => {
const value = e.target.value;
if(this.props.onEdit) {
this.props.onEdit(value);
this.setState({
isEditing: false
});
}
};
}
/components/Items.jsx
import React from 'react';
import Item from './Item.jsx';
export default ({items, onEdit, onDelete}) => {
return (
<ul>{items.map(item =>
<li key={item.id}>
<Item
text={item.text}
onEdit={onEdit.bind(null, item.id)}
onDelete={onDelete.bind(null, item.id)}
/>
</li>
)}</ul>
);
}
// UPDATE: http://redux.js.org/docs/basics/UsageWithReact.html
// Is this necessary?
const mapStateToProps = (state) => {
return {
state
}
}
Items = connect(
mapStateToPros
)(Items) // `SyntaxError app/components/Items.jsx: "Items" is read-only`
//////////////////////////////////////
// Also tried it this way.
//////////////////////////////////////
Items = connect()(Items)
export default Items // same error as above.
Tried this as well
export default connect(
state => ({
items: store.items
})
)(Items) // `Uncaught TypeError: Cannot read property 'items' of undefined`
UPDATE:
After many attempts #hedgerh in Gitter pointed out that it should be state.items instead. so the solution was
export default connect(
state => ({
items: state.items
})
)(Items)
credits to #azium as well.
/components/App.jsx
export default class App extends React.Component {
render() {
return (
<div>
<button onClick={this.addItem}>+</button>
<Items />
</div>
);
}
}
What am I missing here in order to implement it correctly? Right now it breaks saying that Uncaught TypeError: Cannot read property 'map' of undefined in Items.jsx. I guess it makes sense since it doesn't seem to be hooked up correctly. This is the first part of the app, where the second will allow an user to create a many lists, and these lists having many items. I will probably have to extract the methods from Item.jsx since the List.jsx will do pretty much the same thing. Thanks
You're missing connect. That's how stuff gets from your store to your components. Read the containers section from the docs http://redux.js.org/docs/basics/UsageWithReact.html
import React from 'react'
import Item from './Item.jsx'
import { connect } from 'react-redux'
let Items = ({items, onEdit, onDelete}) => {
return (
<ul>{items.map(item =>
<li key={item.id}>
<Item
text={item.text}
onEdit={onEdit.bind(null, item.id)}
onDelete={onDelete.bind(null, item.id)}
/>
</li>
})
</ul>
)
}
export default connect(
state => ({
items: state.items
})
)(Items)
Also you seem to be expecting onEdit and onDelete functions passed from a parent but you're not doing that so those functions will be undefined.