Using 2 dropdowns in React. Cannot read property 'map' of undefined - javascript

I'm having an issue with 2 dropdowns. I'm getting the following error: TypeError: Cannot read property 'map' of undefined.
Both dropdowns should work at the same time depending if the user wants to select a value from one of them or wants to select values from both dropdowns.
This is how I have implemented my 2 Dropdowns and how they should work. If I click the button I will get the error:
dropdown.js
import * as actionTypes from '../actions';
const initialState = {
selection: 'Ambos',
dropdownValues: ['Chatbot', 'Agente'],
selectionME: 'Todos',
dropdownValuesME: ['Messenger', 'Web', 'WhatsApp']
};
//
const reducer = ( state = initialState, action ) => {
if (typeof state === 'undefined') {
return initialState
}
switch ( action.type ) {
case actionTypes.UPDATE_SELECTION:
{
return {
...state,
selection: action.dataSelected,
dropdownValues: action.dropdownValues,
selectionME: action.dataSelectedME,
dropdownValuesME: action.dropdownValuesME
}
}
default: {
//statements;
break;
}
}
return state;
};
export default reducer;
DropdownSelect.js
import React, { Component } from 'react';
import { ButtonDropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
import {connect} from 'react-redux';
import * as actionTypes from '../../store/actions'
class DropdownSelect extends Component {
constructor(props)
{
super(props);
console.log(props)
this.state = {
dropdownOpen: false,
solved: this.props.selectData,
dropdownValues: this.props.dropdownValues,
dropdownOpenME: false,
solvedME: this.props.selectDataME,
dropdownValuesME: this.props.dropdownValuesME,
}
}
componentDidMount() {
if(this.props.onRef)
this.props.onRef(this);
}
toggleDropdown = () => {
this.setState({
dropdownOpen: !this.state.dropdownOpen,
});
}
toggleDropdown2 = () => {
this.setState({
dropdownOpenME: !this.state.dropdownOpenME,
});
}
changeDropdownValue = async (event) => {
const currentSolved = this.state.solved
let newdropdownValues = this.state.dropdownValues.concat(currentSolved)
newdropdownValues = newdropdownValues.filter(item => item !== event.target.innerText)
await this.setState({
dropdownOpen: !this.state.dropdownOpen,
solved: event.target.innerText,
dropdownValues: newdropdownValues,
}, () => {
this.props.onUpdate(this.state.solved, this.state.dropdownValues);
});
}
changeDropdownValueME = async (event) => {
const currentSolvedME = this.state.solvedME
let newdropdownValuesME = this.state.dropdownValuesME.concat(currentSolvedME)
newdropdownValuesME = newdropdownValuesME.filter(item2 => item2 !== event.target.innerText)
await this.setState({
dropdownOpenME: !this.state.dropdownOpenME,
solvedME: event.target.innerText,
dropdownValuesME: newdropdownValuesME
}, () => {
this.props.onUpdate(this.state.solvedME, this.state.dropdownValuesME);
});
}
render() {
return (
<>
<ButtonDropdown isOpen={this.state.dropdownOpen} toggle={this.toggleDropdown}>
<DropdownToggle caret>
{this.state.solved}
</DropdownToggle>
<DropdownMenu>
{this.state.dropdownValues.map(e => {return <DropdownItem key={e} onClick={this.changeDropdownValue}>{e}</DropdownItem>})}
</DropdownMenu>
</ButtonDropdown>
<p className="text-uppercase text-left font-weight-bold -label">Tipo de atenciĆ³n</p>
<ButtonDropdown isOpen={this.state.dropdownOpenME} toggle={this.toggleDropdown2}>
<DropdownToggle caret>
{this.state.solvedME}
</DropdownToggle>
<DropdownMenu>
{this.state.dropdownValuesME.map(e => {return <DropdownItem key={e} onClick={this.changeDropdownValueME}>{e}</DropdownItem>})}
</DropdownMenu>
</ButtonDropdown>
</>
);
}
}
const mapStateToProps = state => ({
//selectData: state.dropDownReducer.selection || [],
//dropdownValues: state.dropDownReducer.dropdownValues || ['Ambos', 'Chatbot', 'Agente'],
selectData: state.dropDownReducer.selection,
dropdownValues: state.dropDownReducer.dropdownValues,
selectDataME: state.dropDownReducer.selectionME,
dropdownValuesME: state.dropDownReducer.dropdownValuesME
});
const mapDispatchToProps = dispatch => {
return {
onUpdate: (selected, dropdownValues) => dispatch({type: actionTypes.UPDATE_SELECTION, dataSelected: selected, dropdownValues:dropdownValues})
}
};
export default connect(mapStateToProps, mapDispatchToProps)(DropdownSelect);

hey Jorge try to add conditions before mapping the data
<DropdownMenu>
{this.state.dropdownValues && this.state.dropdownValues.map(e => {return <DropdownItem key={e} onClick={this.changeDropdownValue}>{e}</DropdownItem>})}
</DropdownMenu>
the same here
{this.state.dropdownValuesME && this.state.dropdownValuesME.map(e => {return <DropdownItem key={e} onClick={this.changeDropdownValueME}>{e}</DropdownItem>})}
and I believe you should not assign the state directly from the props, you should create the state as an empty array
then in the component did mount you should set the state with the new values
this.state = {
dropdownOpen: false,
solved: this.props.selectData,
dropdownValues: [],
dropdownOpenME: false,
solvedME: this.props.selectDataME,
dropdownValuesME: [],
}
}
componentDidMount() {
if(this.props.onRef)
this.props.onRef(this);
this.setState(oldState => {...oldState, dropdownValues: this.props.dropdownValues ,dropdownValuesME: this.props.dropdownValuesME})
}

Related

How can I write callback func for setState on event click

after onclick event occurs in backpackList.js, fetch data in context.js and then through setState I want to update noneUserCart . After that i want to get data from context.js to backpackList.js to show web page. but the data is inital data []. How can I solve this problem?!
I think this is a Asynchronous problem, but I'm new react, so I don't know how to write code for this. or do I use async, await.
Help me please!
import React, { Component } from 'react';
const ProductContext = React.createContext();
const ProductConsumer = ProductContext.Consumer;
class ProductProvider extends Component {
constructor() {
super();
this.state = {
totalProducts: 0,
isLogin: false,
cartList: [],
isNavOpen: false,
isCartOpen: false,
noneUserCart: [],
};
}
noneUserAddCart = bagId => {
fetch('/data/getdata.json', {
method: 'GET',
})
.then(res => res.json())
.catch(err => console.log(err))
.then(data => {
this.setState(
{
noneUserCart: [...this.state.noneUserCart, data],
},
() => console.log(this.state.noneUserCart)
);
});
};
render() {
return (
<ProductContext.Provider
value={{
...this.state,
handleCart: this.handleCart,
getToken: this.getToken,
addNoneUserCart: this.addNoneUserCart,
hanldeCheckout: this.hanldeCheckout,
openNav: this.openNav,
showCart: this.showCart,
habdleCartLsit: this.habdleCartLsit,
deleteCart: this.deleteCart,
noneUserAddCart: this.noneUserAddCart,
}}
>
{this.props.children}
</ProductContext.Provider>
);
}
}
export { ProductProvider, ProductConsumer };
import React, { Component } from 'react';
import { ProductConsumer } from '../../context';
export default class BackpackList extends Component {
render() {
const {
backpackdata,
backdescdata,
isdescOpen,
showDesc,
descClose,
rangenumone,
rangenumtwo,
} = this.props;
return (
<div>
{backdescdata.map((bag, inx) => {
return (
<>
{isdescOpen && bag.id > rangenumone && bag.id < rangenumtwo && (
<div className="listDescContainer" key={inx}>
<div className="listDescBox">
<ProductConsumer>
{value => (
<div
className="cartBtn"
onClick={() => {
const token = value.getToken();
if (token) {
value.handleCart(bag.id, token);
} else {
value.noneUserAddCart(bag.id);
console.log(value.noneUserCart);
// this part. value.noneUserCart is undefined
}
}}
>
add to cart.
</div>
)}
</ProductConsumer>
<span className="descClosebtn" onClick={descClose}>
X
</span>
</div>
</div>
</div>
)}
</>
);
})}
</div>
);
}
}
fetch is asynchronous, this.setState is yet called when console.log
<div
className="cartBtn"
onClick={() => {
const token = value.getToken();
if (token) {
value.handleCart(bag.id, token);
} else {
value.noneUserAddCart(bag.id);
console.log(value.noneUserCart);
// this part. value.noneUserCart is undefined
}
}}
>
add to cart.
{value.noneUserCart}
{/* when finished, result should show here */}
</div>

React-Redux - State object update, re-render the components but the change isn't pass to the child component

Im working on a calendar app using React and Redux.
The calendar app fetch dates and their events from google api, so users could see their google calendars.
In the debugger i can see that the data is ok all the way from the dispatch to the render function. the only problem is that the component inside (MonthlyCalendar) that gets the events getting an empty object.
when not using redux and instead just react state, the component do get the new props with the updated events object.
I added console.log to each life cycle of the component.
When using redux i get this:
using regular state (not redux):
i don't know what I'm missing with redux flow but when debugging it's shown that render is executed with the updated state events,
still the console.log print empty.
The code from dispatch to render :
Dispatch
useEffect(() => {
window.addEventListener(
'message',
e => {
if (e.data && e.data.data) {
dispatch(connectBtnClicked(e.data.data));
}
},
false,
);
if (isFirstRun.current) {
isFirstRun.current = false;
return;
}
}, [])
Action
export const connectBtnClicked = (userName: string) => (dispatch: any) => {
const urlArray = [
'https://calendar-server.codev.co.il/getEvents',
'https://calendar-server.codev.co.il/getCalendarsListIds',
'https://calendar-server.codev.co.il/getSettings',
];
const requestsArray = urlArray.map(url => {
const request = new Request(url, {
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
method: 'GET',
});
return fetch(request).then(res => res.json());
});
Promise.all(requestsArray).then(allResults => {
console.log('***allResults', allResults);
dispatch({
type: CONNECT_BTN_CLICKED,
payload: {
isConnect: true,
userName,
dates: allResults[0].eventsByDates,
calendarsList: allResults[1].calendarsList,
},
});
});
};
Reducer
import {
CONNECT_BTN_CLICKED,
SET_COMP_ID,
DISCONNECT_BTN_CLICKED,
CONNECT_ERROR,
} from '../actions/actionType';
const initState = {
isConnect: false,
isDisconnect: false,
isSettingsLoaded: false,
userName: '',
isLoader: false,
connectError: false,
statusCode: '',
dates: {},
calendarsList: [],
};
export default (state = initState, action: any) => {
console.log('setReducer', action);
switch (action.type) {
case SET_COMP_ID:
return {
...state,
compId: action.payload,
};
case CONNECT_ERROR:
return {
...state,
connectEtsyError: action.payload.err,
statusCode: action.payload.statusCode,
};
case CONNECT_BTN_CLICKED:
return {
...state,
isConnect: action.payload.isConnect,
userName: action.payload.userName,
dates: action.payload.dates,
calendarsList: action.payload.calendarsList,
isSettingsLoaded: true,
};
case DISCONNECT_BTN_CLICKED:
return {
...state,
isConnect: false,
// isDisconnect: action.payload,
dates: {},
calendarsList: [],
userName: '',
};
default:
return state;
}
};
Root Reducer
import { combineReducers } from 'redux';
//#ts-ignore
import settings from './settingsReducer';
const rootReducer = combineReducers({
settings,
});
export default rootReducer;
Store
import { createStore, applyMiddleware, compose } from 'redux';
import rootReducer from '../reducers/rootReducer';
import thunk from 'redux-thunk';
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
Provider Wrapping App.js Component
import { BrowserRouter as Router } from 'react-router-dom';
import React from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';
import { I18nextProvider } from 'react-i18next';
import App from './components/App/App';
// import App from './components/App';
import i18n from './i18n';
import { Provider } from 'react-redux';
import store from './components/settings/store';
const locale = window.__LOCALE__;
const baseURL = window.__BASEURL__;
fedopsLogger.appLoaded();
ReactDOM.render(
<Provider store={store}>
<React.Suspense fallback={<div>Please wait...</div>}>
<Router>
{/* <ExperimentsProvider options={{ experiments }}> */}
<App />
{/* </ExperimentsProvider> */}
</Router>
</React.Suspense>
</Provider>,
document.getElementById('root'),
);
App.js render - MonthlyCalendar is the events receiving component
render() {
const { t } = this.props;
const events = this.props.events;
return (
<Switch>
<Route
path="/index"
render={() => (
<MonthlyCalendar
weekStarter={this.state.weekStarter}
events={events}
handleMonthChange={handleMonthChange}
isTimeZoneShown={this.state.isTimeZoneShown}
isTimeShown={this.state.isTimeShown}
locale={this.state.locale}
//timeZone={this.state.timeZone}
isTodayButtonStyleSeconday={this.state.isTodayButtonStyleSeconday}
/>
)}
></Route>
<Route
path="/settings"
render={() => (
<Settings
fetchEvents={this.fetchEvents}
initialState={"this.props.initialState"}
/>
)}
></Route>
<Route
path="/mobile"
render={() => (
<MonthlyCalendar
weekStarter={this.state.weekStarter}
events={events}
handleMonthChange={handleMonthChange}
isTimeZoneShown={this.state.isTimeZoneShown}
isTimeShown={this.state.isTimeShown}
locale={this.state.locale}
isTodayButtonStyleSeconday={this.state.isTodayButtonStyleSeconday}
/>
)}
></Route>
</Switch>
);
}
}
const mapDispatchToProps = (dispatch: any) => ({});
const mapStateToProps = (state: any) => ({
isConnect: state.settings.isConnect,
userName: state.settings.userName,
events: state.settings.dates,
calendarsList: state.settings.calendarsList,
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(withTranslation()(withEnhancedStyleLoader(App)));
There is not setState in componentDidMount or any other method in App.js I removed it all, so only props change could re-render
Edit 1
i dropped all the HOCS and still in render i see the updated events, but the child component print empty events.
Render on events received
EDIT 2
the child component only render once. even when events props is updated, and re-render MonthlyCalendar( the child component ) doesn't re-render. I tried:
const events = {...this.props.events};
also:
const events = JSON.parse(JSON.stringfy(this.props.events);
didn't work...
EDIT 3 - MonthlyCalendar Component
constructor(props: any) {
super(props);
console.log('[constructor] props.events: ',props.events)
const timezone = moment.tz.guess();
const dateObject = moment().tz(timezone, true);
this.state = {
dateObject,
timezone,
isTimezonesOpen: false,
};
}
shouldComponentUpdate(nextProps, nextState) {
console.log('[shouldComponentUpdate] props.events: ',this.props.events)
return true
}
....
getCalendar() {
const { events } = this.props;
const { dateObject } = this.state;
const beforeFillers = this.getMonthBeforFillers(dateObject, events);
const days = this.getDays(dateObject, events);
const afterFillers = this.hasAfterFillers(beforeFillers, days) ?
this.getAfterMonthFillers(dateObject, events) : {};
return { days, beforeFillers, afterFillers };
}
async componentDidUpdate(prevProps) {
console.log('[componentDidUpdate] props.events: ',this.props.events)
this.props.locale !== prevProps.locale && await this.updateLocale();
}
updateLocale = async () => {
const { locale, i18n } = this.props;
await i18n.changeLanguage(locale);
moment.locale(locale);
const { timezone, dateObject } = this.state;
const dateObjectToSet = moment(dateObject.format()).tz(timezone, true);
this.setState({ dateObject: dateObjectToSet });
}
async componentDidMount() {
console.log('[componentDidMount] props.events: ',this.props.events)
this.props.locale !== 'en' && await this.updateLocale();
}
render() {
const { t, weekStarter, isTodayButtonStyleSeconday, isTimeZoneShown, isTimeShown } = this.props;
const { dateObject, timezone, isTimezonesOpen } = this.state;
const { days, beforeFillers, afterFillers } = this.getCalendar();
const month = dateObject.format(t('Google_Calendar_Picker_Month'));
const timezoneSelected = moment().tz(timezone).format(t('Google_Calendar_Timezone_Selected'));
const timezoneSelectedTitle = t('Google_Calendar_Timezone_Selected_Title', { timezoneSelected });
console.log('[render] props.events: ',this.props.events)
return (
<TPAComponentsProvider value={{ mobile: false, rtl: false }}>
<div className={classes.MonthlyCalendar}>
<CalendarControllers
isTodayButtonStyleSeconday={isTodayButtonStyleSeconday}
todayClicked={this.todayClickedHander}
onPreviousClicked={() => this.timePickerClickedHandler(false)}
timeToDisplay={month}
onNextClicked={() => this.timePickerClickedHandler(true)}
onTimezoneChange={this.timezoneChangeHandler}
timezone={timezoneSelectedTitle}
isTimezonesOpen={isTimezonesOpen}
openTimezones={this.openTimezones}
closeTimezones={this.closeTimezones}
isTimeZoneShown={isTimeZoneShown}
/>
<MonthTable
weekStarter={weekStarter}
days={days}
beforeFillers={beforeFillers}
dateObject={dateObject}
afterFillers={afterFillers}
renderCell={(
time: any,
events: any,
cellRef: any,
handleEventClick: any,
setExpendedEvent: any,
expendedEvent: any,
isOutsideClicked: any,
) => (
<MonthlyCell
events={events}
handleEventClick={handleEventClick}
time={time}
cellRef={cellRef}
expendedEvent={expendedEvent}
isOutsideClicked={isOutsideClicked}
setExpendedEvent={setExpendedEvent}
isTimeShown={isTimeShown}
/>
)}
/>
</div>
</TPAComponentsProvider>
);
}
}
export default withTranslation()(MonthlyCalendar);
EDIT 4
after searching for solutions i added key to the div of MonthlyCalendar and also i added destructor {...this.props.events}. still no re-render
Following is the updated MonthlyCalendar:
getCalendar() {
const mutableEvents = {...this.props.events};
const { dateObject } = this.state;
const beforeFillers = this.getMonthBeforFillers(dateObject, mutableEvents);
const days = this.getDays(dateObject, mutableEvents);
const afterFillers = this.hasAfterFillers(beforeFillers, days) ?
this.getAfterMonthFillers(dateObject, mutableEvents) : {};
return { days, beforeFillers, afterFillers };
}
async componentDidUpdate(prevProps) {
console.log('[componentDidUpdate] props.events: ',this.props.events)
this.props.locale !== prevProps.locale && await this.updateLocale();
}
updateLocale = async () => {
const { locale, i18n } = this.props;
await i18n.changeLanguage(locale);
moment.locale(locale);
const { timezone, dateObject } = this.state;
const dateObjectToSet = moment(dateObject.format()).tz(timezone, true);
this.setState({ dateObject: dateObjectToSet });
}
async componentDidMount() {
console.log('[componentDidMount] props.events: ',this.props.events)
this.props.locale !== 'en' && await this.updateLocale();
}
render() {
const { t, weekStarter, isTodayButtonStyleSeconday, isTimeZoneShown, isTimeShown, events: propEvents } = this.props;
const eventsKey = Object.keys(propEvents).length;
const { dateObject, timezone, isTimezonesOpen } = this.state;
const { days, beforeFillers, afterFillers } = this.getCalendar();
const month = dateObject.format(t('Google_Calendar_Picker_Month'));
const timezoneSelected = moment().tz(timezone).format(t('Google_Calendar_Timezone_Selected'));
const timezoneSelectedTitle = t('Google_Calendar_Timezone_Selected_Title', { timezoneSelected });
console.log('[render] props.events: ',this.props.events)
return (
<TPAComponentsProvider value={{ mobile: false, rtl: false }}>
<div key={eventsKey} className={classes.MonthlyCalendar}>
<CalendarControllers
isTodayButtonStyleSeconday={isTodayButtonStyleSeconday}
todayClicked={this.todayClickedHander}
onPreviousClicked={() => this.timePickerClickedHandler(false)}
timeToDisplay={month}
onNextClicked={() => this.timePickerClickedHandler(true)}
onTimezoneChange={this.timezoneChangeHandler}
timezone={timezoneSelectedTitle}
isTimezonesOpen={isTimezonesOpen}
openTimezones={this.openTimezones}
closeTimezones={this.closeTimezones}
isTimeZoneShown={isTimeZoneShown}
/>
<MonthTable
weekStarter={weekStarter}
days={days}
beforeFillers={beforeFillers}
dateObject={dateObject}
afterFillers={afterFillers}
renderCell={(
time: any,
events: any,
cellRef: any,
handleEventClick: any,
setExpendedEvent: any,
expendedEvent: any,
isOutsideClicked: any,
) => (
<MonthlyCell
events={events}
handleEventClick={handleEventClick}
time={time}
cellRef={cellRef}
expendedEvent={expendedEvent}
isOutsideClicked={isOutsideClicked}
setExpendedEvent={setExpendedEvent}
isTimeShown={isTimeShown}
/>
)}
/>
</div>
</TPAComponentsProvider>
);
}
}
export default withTranslation()(MonthlyCalendar);

Get data from React props

How to get data from props and be sure that data arrived properly. My issue is that in my class component I receive alarms from props and then I want to render table with the alarms, props sets asynchronously and I want to be sure that the data arrived and that I will be able to set a state. Below is my not nice try to solve it, but does not work (both console.log shows empty array):
componentDidMount() {
this.setState({
startDate: moment()
.subtract(30, 'days')
.startOf('day'),
endDate: moment().endOf('day'),
selectedSeverity: null,
isChecked: true,
});
if(this.state.tableAlerts.length === 0){
console.log('tableAlerts', this.state.tableAlerts)
console.log('householdAlerts', this.props.householdAlerts)
this.setState({ tableAlerts: this.props.householdAlerts })
}
}
The whole component:
import React, { useState } from 'react';
import T from 'prop-types';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { moment } from 'react-moment';
import { t } from 'i18next';
import _ from 'lodash';
import { TimezoneLabel } from 'cloud-modules-user/build/TimezoneLabel';
import { Table, Radio } from 'semantic-ui-react';
import { RangeDatePicker } from 'cloud-modules-user/build/RangeDatePicker';
import { SeverityFilter } from 'cloud-modules-user/build/Alerts';
import { fetchHouseholdAlerts } from '../Redux/Household.actions';
const alertSeverityText = ({ severity }) =>
severity < 3 ? 'error' : 'warning';
const alertsBySeverity = alerts => {
const groupedAndCounted = _.countBy(alerts, alertSeverityText);
return severity => groupedAndCounted[severity] || 0;
};
const alertSeverityColor = {
error: '#e05567',
warning: '#f9b233',
};
export class HouseholdAlertsPure extends React.Component {
constructor(props) {
super(props);
this.state = {
tableAlerts: props.householdAlerts
}
}
static propTypes = {
householdAlerts: T.arrayOf(T.object).isRequired,
};
componentDidMount(prevProps) {
this.setState({
startDate: moment()
.subtract(30, 'days')
.startOf('day'),
endDate: moment().endOf('day'),
selectedSeverity: null,
isChecked: true,
});
console.log('prevprops', prevProps)
// if(this.props.householdAlerts !== prevProps.householdAlerts){
// this.setState({ tableAlerts: this.props.householdAlerts })
// }
// if(this.state.tableAlerts.length === 0){
// console.log('tableAlerts', this.state.tableAlerts)
// console.log('householdAlerts', this.props.householdAlerts)
// this.setState({ tableAlerts: this.props.householdAlerts })
// }
}
onChangeDate = e => {
const startDate = moment(e.startDate).startOf('day');
const endDate = moment(e.endDate).endOf('day');
this.setState({ startDate, endDate });
const {
// eslint-disable-next-line no-shadow
fetchHouseholdAlerts,
match: { params },
} = this.props;
fetchHouseholdAlerts(params.deviceGroupId, startDate, endDate);
};
selectOngoingAlerts = (e, data) => {
const { isChecked } = this.state;
this.setState( {isChecked : !isChecked} );
const { householdAlerts } = this.props;
// check whether alarm is ongoing
if(isChecked){
// filter ongoing alerts
let filteredHouseholdAlerts = householdAlerts.filter((alert) => alert.setTimestamp < alert.clearTimestamp)
this.setState( {tableAlerts: filteredHouseholdAlerts} )
}else{
// show all alerts, without any filter
this.setState({tableAlerts: householdAlerts});
}
}
handleOnChangeSeverity = (e, selectedOption ) => {
console.log('e', e)
this.setState( {selectedSeverity: e.option} )
console.log('selectedSeverity', this.state.selectedSeverity)
};
setAlertForTable = () => {
// if(this.state.alertsInitialised == true) return;
console.log('setAlertForTable')
// this.setState ( {alertsInitialised: true} )
}
render() {
console.log('test', this.props.householdAlerts)
const { startDate, endDate } = this.state;
const datesInitialized = startDate && endDate;
// The dates are not set until the component is mounted.
if (!datesInitialized) return null;
const { householdAlerts } = this.props;
const labels = {
from: t('householdAlertsTimeRangeFrom'),
to: t('householdAlertsTimeRangeTo'),
};
const numberOfAlert = alertsBySeverity(householdAlerts);
return (
<div className="alert-table">
<section className="alerts-buttons">
<div className="ongoing-box">
<Radio toggle className="ongoing-checkbox"
label="Ongoing alerts"
value={ !this.state.isChecked }
onChange={this.selectOngoingAlerts}
></Radio>
</div>
<SeverityFilter className="severity--button"
onChange={this.handleOnChangeSeverity}
value={this.state.selectedSeverity}
option={this.state.selectedSeverity || 'all'}
></SeverityFilter>
{ this.setAlertForTable() }
<div className="time-range">{t('householdAlertsTimeRange')}</div>
<RangeDatePicker
id="rangeDateSelector"
labels={labels}
onChange={e => this.onChangeDate(e)}
selectedDateRange={[startDate, endDate]}
filterDate={date => moment() > date}
/>
</section>
<div className="alert-table--statuses">
<div aria-label="error">
<div
className="alert-table--statuses-item"
style={{ backgroundColor: alertSeverityColor.error }}
/>
<span className="alert-table--statuses-item-number">
{numberOfAlert('error')}
</span>
</div>
<div aria-label="warning">
<div
className="alert-table--statuses-item"
style={{ backgroundColor: alertSeverityColor.warning }}
/>
<span className="alert-table--statuses-item-number">
{numberOfAlert('warning')}
</span>
</div>
</div>
<Table sortable>
<Table.Header>
<Table.Row>
<Table.HeaderCell style={{ width: '116px' }}>
{t('householdAlertsSeverity')}
</Table.HeaderCell>
<Table.HeaderCell textAlign="left">
{t('householdAlertsDevice')}
</Table.HeaderCell>
<Table.HeaderCell textAlign="left">
{t('householdAlertsAlertName')}
</Table.HeaderCell>
<Table.HeaderCell textAlign="left">
{t('householdAlertsAlertsMessage')}
</Table.HeaderCell>
<Table.HeaderCell textAlign="right">
{t('householdAlertsTimestamp')}
</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{householdAlerts && householdAlerts.length !== 0 ? (
_.map(
householdAlerts,
({
id,
severity,
deviceName,
name,
setMessage,
setTimestamp,
}) => (
<Table.Row key={id}>
<Table.Cell
className="alert-table--column-severity"
textAlign="right"
>
<div
className="alert-table--column-severity__status"
style={{
backgroundColor:
alertSeverityColor[alertSeverityText({ severity })],
}}
/>
</Table.Cell>
<Table.Cell
className="alert-table--column-device"
textAlign="left"
>
{deviceName}
</Table.Cell>
<Table.Cell
className="alert-table--column-alert"
textAlign="left"
>
{name}
</Table.Cell>
<Table.Cell
className="alert-table--column-message"
textAlign="left"
>
{setMessage}
</Table.Cell>
<Table.Cell
className="alert-table--column-timestamp"
textAlign="right"
>
{moment(setTimestamp).format('DD-MM-YYYY HH:mm')}
</Table.Cell>
</Table.Row>
),
)
) : (
<Table.Row>
<Table.Cell colSpan={7}>
{t('householdAlertsMessageNoAlerts')}
</Table.Cell>
</Table.Row>
)}
</Table.Body>
</Table>
<section className="timezone-wrapper">
<TimezoneLabel />
</section>
</div>
);
}
}
const mapStateToProps = state => ({
householdAlerts: state.HouseholdReducer.householdAlerts,
});
const mapDispatchToProps = { fetchHouseholdAlerts };
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(HouseholdAlertsPure),
);
It is bad practice to set initial values to state at the constructor, therefore use lifeCycles as componentDidMount or componentDidUpdate instead to manipulate the state.
export class HouseholdAlertsPure extends React.Component {
constructor(props) {
super(props);
// initial values for state
this.state = {
tableAlerts: [],
startDate: null,
endDate: null,
selectedSeverity: null,
isChecked: false
}
}
componentDidMount() {
// values declared for state at component first load
this.setState({
startDate: moment()
.subtract(30, 'days')
.startOf('day'),
endDate: moment().endOf('day'),
selectedSeverity: null,
isChecked: true,
tableAlerts: this.props.householdAlerts
});
componentDidUpdate() {
// called whenever prop value changed
if(this.props.householdAlerts !== this.state.tableAlerts)
this.setState({ tableAlerts: this.props.householdAlerts })
}
selectOngoingAlerts = (e, data) => {
const { isChecked, tableAlerts } = this.state;
this.setState( {isChecked : !isChecked} );
// right now, { isChecked } still refer to construct abouve methond and prev value...
// for more consist aproach, consider do the filtering logic direct at render without manipulate state
if(!isChecked) {
this.setState( { tableAlerts: tableAlerts.filter((alert) => alert.setTimestamp < alert.clearTimestamp)} )
} else { this.setState({tableAlerts}) }
}
...rest class...
}
Now at render, { this.state.tableAlerts } should contain the correct information
You should probably use componentDidUpdate. It runs on each state and prop change.
This is an example of how it can be used:
componentDidUpdate(prevProps) {
if(this.props.householdAlerts !== prevProps.householdAlerts) {
this.setState({ tableAlerts: this.props.householdAlerts })
}
}

Update State in function outside class component

I have a class component, within that calling a function. I have a state variable and want to update the state in function. Since it is different function I'm not able to update the value. How can I get the selected items details and update the state? when I do setState, receiving following error as 'TypeError: this.setState is not a function'
any help appreciated
Component
import React, { Component } from 'react'
import PropTypes from "prop-types";
import statedist from "./StateDistrict.json";
const suggestions = statedist.states;
function DownshiftMultiple(props) {
const { classes } = props;
const [inputValue, setInputValue] = React.useState("");
const [selectedItem, setSelectedItem] = React.useState([]);
function handleKeyDown(event) {
if (
selectedItem.length &&
!inputValue.length &&
event.key === "Backspace"
) {
setSelectedItem(selectedItem.slice(0, selectedItem.length - 1));
}
}
function handleInputChange(event) {
setInputValue(event.target.value);
}
function handleChange(item) {
let newSelectedItem = [...selectedItem];
if (newSelectedItem.indexOf(item) === -1) {
newSelectedItem = [...newSelectedItem, item];
}
setInputValue("");
setSelectedItem(newSelectedItem);
this.setState({ SelectedState: newSelectedItem }); // here i want to update selected items
}
const handleDelete = item => () => {
const newSelectedItem = [...selectedItem];
newSelectedItem.splice(newSelectedItem.indexOf(item), 1);
setSelectedItem(newSelectedItem);
};
return (
<Downshift
id="downshift-multiple"
inputValue={inputValue}
onChange={handleChange}
selectedItem={selectedItem}
>
{({
getInputProps,
getItemProps,
getLabelProps,
isOpen,
inputValue: inputValue2,
selectedItem: selectedItem2,
highlightedIndex
}) => {
const { onBlur, onChange, onFocus, ...inputProps } = getInputProps({
onKeyDown: handleKeyDown,
// placeholder: "Select multiple State"
});
return (
<div className={classes.container}>
{renderInput({
fullWidth: true,
classes,
// label: "States",
InputLabelProps: getLabelProps(),
InputProps: {
startAdornment: selectedItem.map(item => (
<Chip
key={item}
tabIndex={-1}
label={item}
className={classes.chip}
onDelete={handleDelete(item)}
/>
)),
onBlur,
onChange: event => {
handleInputChange(event);
onChange(event);
},
onFocus
},
inputProps
})}
{isOpen ? (
<Paper className={classes.paper} square>
{getSuggestions(inputValue2).map((suggestion, index) =>
renderSuggestion({
suggestion,
index,
itemProps: getItemProps({ item: suggestion.state }),
highlightedIndex,
selectedItem: selectedItem2
})
)}
</Paper>
) : null}
</div>
);
}}
</Downshift>
);
}
class autoCompleteState extends Component {
constructor(props) {
super(props);
this.state = {
SelectedState:'',
}
// this.showProfile = this.showProfile.bind(this)
}
render() {
const { classes, } = this.props;
return (
<div>
<DownshiftMultiple classes={classes} />
</div>
)
}
}
export default withStyles(Styles)(autoCompleteState);
You can't and shouldn't access the context (this) of other components directly to update its state, especially not with a functional component.
What you have to do is pass a function as a prop to your DownshiftMultiple component which itself gets the value with which you want to update the state.
function DownshiftMultiple(props) {
/* ... */
function handleChange(item) {
let newSelectedItem = [...selectedItem];
if (newSelectedItem.indexOf(item) === -1) {
newSelectedItem = [...newSelectedItem, item];
}
setInputValue("");
setSelectedItem(newSelectedItem);
this.props.onChange(newSelectedItem); // Use the new function prop
}
/* ... */
}
class autoCompleteState extends Component {
/* ... */
onDMChange = (newSelectedItem) => this.setState({ SelectedState: newSelectedItem });
render() {
const { classes, } = this.props;
return (
<div>
<DownshiftMultiple classes={classes} onChange={this.onChange} />
</div>
)
}
}
Also on a sidenote I would recommend to encapsulate your event handling functions inside your functional DownshiftMultiple component with the useCallback hook. Something like const newSelectedItem = [...selectedItem]; would always use the value that the state has been initialised with without a hook.
// For example your handle delete
const handleDelete = React.useCallback(item => () => {
const newSelectedItem = [...selectedItem];
newSelectedItem.splice(newSelectedItem.indexOf(item), 1);
setSelectedItem(newSelectedItem);
}, [selectedItem]);
You pass on a handler to the child component, which it will invoke with the value to update and the update action happens in the parent
import React, { Component } from 'react'
import PropTypes from "prop-types";
import statedist from "./StateDistrict.json";
const suggestions = statedist.states;
function DownshiftMultiple(props) {
const { classes } = props;
const [inputValue, setInputValue] = React.useState("");
const [selectedItem, setSelectedItem] = React.useState([]);
function handleKeyDown(event) {
if (
selectedItem.length &&
!inputValue.length &&
event.key === "Backspace"
) {
setSelectedItem(selectedItem.slice(0, selectedItem.length - 1));
}
}
function handleInputChange(event) {
setInputValue(event.target.value);
}
function handleChange(item) {
let newSelectedItem = [...selectedItem];
if (newSelectedItem.indexOf(item) === -1) {
newSelectedItem = [...newSelectedItem, item];
}
setInputValue("");
setSelectedItem(newSelectedItem);
props.setSelectedState(newSelectedItem);
}
const handleDelete = item => () => {
const newSelectedItem = [...selectedItem];
newSelectedItem.splice(newSelectedItem.indexOf(item), 1);
setSelectedItem(newSelectedItem);
};
return (
<Downshift
id="downshift-multiple"
inputValue={inputValue}
onChange={handleChange}
selectedItem={selectedItem}
>
{({
getInputProps,
getItemProps,
getLabelProps,
isOpen,
inputValue: inputValue2,
selectedItem: selectedItem2,
highlightedIndex
}) => {
const { onBlur, onChange, onFocus, ...inputProps } = getInputProps({
onKeyDown: handleKeyDown,
// placeholder: "Select multiple State"
});
return (
<div className={classes.container}>
{renderInput({
fullWidth: true,
classes,
// label: "States",
InputLabelProps: getLabelProps(),
InputProps: {
startAdornment: selectedItem.map(item => (
<Chip
key={item}
tabIndex={-1}
label={item}
className={classes.chip}
onDelete={handleDelete(item)}
/>
)),
onBlur,
onChange: event => {
handleInputChange(event);
onChange(event);
},
onFocus
},
inputProps
})}
{isOpen ? (
<Paper className={classes.paper} square>
{getSuggestions(inputValue2).map((suggestion, index) =>
renderSuggestion({
suggestion,
index,
itemProps: getItemProps({ item: suggestion.state }),
highlightedIndex,
selectedItem: selectedItem2
})
)}
</Paper>
) : null}
</div>
);
}}
</Downshift>
);
}
class autoCompleteState extends Component {
constructor(props) {
super(props);
this.state = {
SelectedState:'',
}
// this.showProfile = this.showProfile.bind(this)
}
setSelectedState = (newState) => {
this.setState({ SelectedState: newState });
}
render() {
const { classes, } = this.props;
return (
<div>
<DownshiftMultiple classes={classes} setSelectedState={this.setSelectedState}/>
</div>
)
}
}
export default withStyles(Styles)(autoCompleteState);

Changing state of child component from parent with refs

I have a TaskList. I'm trying to display a child component TaskEdit (edit box for the task) several components lower, onClick of a button.
TaskEdit has a state variable hidden, set to true by default. How do I change the state inside the child component from within the child component?
React documentation mentions a forwardRef() function in order to reference the child component, but I haven't any luck so far with it.
Here's my code:
import TaskEdit from '../TaskEdit';
class TasksBase extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
tasks: [],
};
this.markTaskAsCompleted = this.markTaskAsCompleted.bind(this);
this.deleteTask = this.deleteTask.bind(this);
this.incrementDate = this.incrementDate.bind(this);
this.toggleTaskEdit = this.toggleTaskEdit.bind(this);
}
componentDidMount() {
this.onListenForTasks();
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
const { tasks, loading } = this.state;
return (
<div>
{loading && <div>Loading ...</div>}
{tasks ? (
<TaskList tasks={tasks}
handleToggleEdit={this.toggleTaskEdit}
handleMarkCompleted={this.markTaskAsCompleted}
handleDeleteTask={this.deleteTask}
taskEditElement={this.editTaskElement}
/>
):(
<div>There are no tasks ...</div>
)}
</div>
);
}
onListenForTasks() {
this.setState({ loading: true });
this.unsubscribe = this.props.firebase
.tasks()
.orderBy('importance', 'desc')
.orderBy('urgency', 'desc')
.orderBy('created', 'desc')
.onSnapshot(snapshot => {
if (snapshot.size) {
let tasks = [];
snapshot.forEach(doc =>
tasks.push({ ...doc.data(), uid: doc.id }),
);
this.setState({
tasks: tasks,
loading: false
});
} else {
this.setState({ tasks: null, loading: false });
}
});
}
markTaskAsCompleted(task){
// Some code
}
deleteTask(id){
// Some code
}
toggleEditTask(){
this.editTaskElement.current.toggle();
}
}
const Tasks = withFirebase(TasksBase);
const TaskList = ({ tasks, handleToggleEdit, handleMarkCompleted, handleDeleteTask }) => (
<ul className="tasks">
{tasks.map( task => (
<Task key={task.uid}
task={task}
handleToggleEdit={handleToggleEdit}
handleMarkCompleted={handleMarkCompleted}
handleDeleteTask={handleDeleteTask}
/>
))}
</ul>
);
const Task = ({ task, handleToggleEdit, handleMarkCompleted, handleDeleteTask}) => (
(!task.completed && !task.obsolete && !task.waitingForDependencies) && (!task.start || task.start.toMillis() <= Date.now()) &&
<li className="task">
<strong>{task.userId}</strong> {task.name}
{ task.estimate &&
<span>{task.estimate.amount + task.estimate.unit}</span>
}
<div className="icons">
<FontAwesomeIcon icon="edit" onClick={() => handleToggleEdit(task)} />
<FontAwesomeIcon icon="check-circle" onClick={() => handleMarkCompleted(task)} />
<FontAwesomeIcon icon="times-circle" onClick={() => handleDeleteTask(task.uid)}/>
</div>
<TaskEdit task={task} />
</li>
);
const condition = authUser => !!authUser;
export default compose(
withEmailVerification,
withAuthorization(condition),
)(Tasks);
TaskEdit:
import React, { Component } from 'react';
import { withFirebase } from '../Firebase';
const INITIAL_STATE = {
hidden: true,
};
class TaskEditBase extends Component {
constructor(props) {
super(props);
this.state = { ...INITIAL_STATE };
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
console.log("name " + name);
console.log("value " + value);
this.setState({
[name]: value
});
}
toggle(){
this.setState({
hidden: !this.prevState.hidden
})
}
onSumbitTaskEdit = (event) => {
event.preventDefault();
};
render(){
return(
!this.state.hidden &&
<div className="task-edit">
<form onSubmit={this.onSumbitTaskEdit}>
<div>A bunch of inputs</div>
<input type="submit" value="Edit"/>
</form>
</div>
)
}
}
const TaskEdit = withFirebase(TaskEditBase);
export default TaskEdit;

Categories