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);
Related
I am trying to add some 'save preferences' functionality to a DataGrid using https://reactdatagrid.io/docs/miscellaneous but when I add sortInfo makes columns unsortable, the same happens for filterValue (trying to save filtering strings/data). Code here:
import React, { useCallback, useState } from 'react';
import DataGrid from '#inovua/reactdatagrid-enterprise';
import { columnFilters, eeOverviewColumns, filterTypes } from "./overview-columns";
import '#inovua/reactdatagrid-enterprise/index.css';
import { TypeRowProps, TypeRowSelection } from '#inovua/reactdatagrid-community/types';
import { TypeOnSelectionChangeArg } from "#inovua/reactdatagrid-community/types/TypeDataGridProps"
import { Button, FormControl, MenuItem, Select, SelectChangeEvent, TextField } from '#mui/material';
import { TypeColumn, TypeFilterValue, TypeSortInfo } from '#inovua/reactdatagrid-enterprise/types';
interface StoreLayout {
columns: TypeColumn[];
sortInfo: TypeSortInfo;
columnOrder : string[];
filterValue: TypeFilterValue;
}
let STORE: StoreLayout = {
columns: eeOverviewColumns,
sortInfo: [],
columnOrder: eeOverviewColumns.map(ee => ee.name) as string[],
filterValue: columnFilters,
}
let VIEWS= [
{
id: 0,
name: "Default view",
state: STORE
}
]
const EEOverview = (props: any) => {
const dataSource = props.eeData
// Checkbox selection
const [selected, setSelected] = useState<TypeRowSelection>();
const onSelectionChange = useCallback(
(config: TypeOnSelectionChangeArg) => {
setSelected(config.selected)
},
[],
);
const goToEe = useCallback((rowProps: TypeRowProps) => {
window.location.href = `${window.location.href}/${rowProps.data.key}`;
}, [])
const initialState = Object.assign({}, STORE, {});
const [state, setState] = useState(initialState);
const [viewName, setViewName] = useState('')
const sendViewName = (viewName: string) => { setViewName(viewName) }
const saveState = () => {
if (!viewName || viewName.length === 0 ) {
alert("View name not provided")
return
}
STORE = {
columnOrder: state.columnOrder,
columns: state.columns,
sortInfo: state.sortInfo,
filterValue: state.filterValue
}
setState(Object.assign({}, state, {}))
if(VIEWS.map(view => view.name).some(name => name === viewName)) {
const view = VIEWS.find(view => view.name === viewName)!
view.state = state
} else {
VIEWS.push({
id: VIEWS.length,
name: viewName,
state: state
})
}
}
const onSortInfoChange = (sortInfo: TypeSortInfo) => {
setState(Object.assign({}, state, { sortInfo }));
}
const onColumnOrderChange = (columnOrder: string[]) => {
setState(Object.assign({}, state, { columnOrder }));
}
const onFilterValueChange = (filterValue: TypeFilterValue) => {
setState(Object.assign({}, state, { filterValue }));
}
const onBatchColumnResize = (batchColumnInfo: any, { reservedViewportWidth }: any) => {
const colsMap = batchColumnInfo.reduce((acc: any, colInfo: any) => {
const { column, width, flex } = colInfo
acc[column.name] = { width, flex }
return acc
}, {})
const columns = state.columns.map((c: any) => {
return Object.assign({}, c, colsMap[c.name])
})
setState(Object.assign({}, state, {
columns,
reservedViewportWidth
}))
}
return (
<div>
<ViewSelector state = {state} onChange = {setState} ></ViewSelector>
<ViewText onChange = {sendViewName} ></ViewText>
<Button sx={{ mx: 2, my: 2, minWidth: 80 }} variant="contained"
onClick = {saveState}
>
Save view
</Button>
<DataGrid
idProperty="key"
theme="default-light"
className="data-grid"
defaultFilterValue={columnFilters}
filterTypes={filterTypes}
filterValue={state.filterValue} //<- here
onRowClick={goToEe}
columns={state.columns}
sortInfo={state.sortInfo} //<- and here
columnOrder={state.columnOrder}
pagination="local"
dataSource={dataSource}
onSelectionChange={onSelectionChange}
sortable={true}
checkboxColumn
selected={selected}
enableSelection={true}
onSortInfoChange={onSortInfoChange}
onBatchColumnResize={onBatchColumnResize}
onColumnOrderChange={onColumnOrderChange}
onFilterValueChange={onFilterValueChange}
/>
</div>
);
}
export default EEOverview;
const ViewText = ({onChange}: {onChange: any}) => {
const onViewNameChange = (viewName: string) => {
onChange(viewName)
}
return (
<TextField sx={{ mx: 2, my: 2 }}
id="search"
variant="outlined"
size="small"
label="Name current view"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => { onViewNameChange(e.target.value) }}
/>
)
}
const ViewSelector = ({state, onChange}: {state:any, onChange: any}) => {
const [selectedView, setSelectedView] = useState(0)
const handleViewChange = (event: SelectChangeEvent<number>) => {
const selectedView = VIEWS.find(view => view.id===event.target.value)!
setSelectedView(selectedView.id)
const selectedState = selectedView.state
onChange(Object.assign({},selectedState, {}))
}
return (
<FormControl sx={{ m: 2, minWidth: 140 }} >
<Select labelId="view" variant="standard" size="medium" value={selectedView}
renderValue={(selected: any) => { return <div>{VIEWS[selected].name}</div>; }}
onChange={handleViewChange}>
{VIEWS.map((val, key) => (
<MenuItem value={key}>{val.name}</MenuItem>
))}
</Select>
</FormControl>
)
}
If I remove sortInfo/filterValue from passing to DataGrid, it behaves correctly, but it won't be saved to preferences.
Tried to move dataSource from props to state but it has the same behaviour
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})
}
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 })
}
}
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;
I have a JSX element and a counter in the state, the JSX element uses the counter state in the state. I show the JSX element in component 1 with modal and set the JSX element in component2.
when I try to update the counter in component 2 it won't change in the JSX element counter in component 1.
Component 1
class Meeting extends Component {
render() {
const { dispatch, modalVisible, modalContent } = this.props;
return (
<Landing>
<Modal
title="Title"
visible={modalVisible}
onOk={() => { dispatch(showModal(false)); }}
onCancel={() => { dispatch(showModal(false)); }}
okText="Ok"
cancelText="Cancel"
>
<div>
{modalContent}
</div>
</Modal>
</Landing>
);
}
}
function mapStateToProps(state) {
const {
modalVisible,
modalContent,
counter
} = state.meeting;
return {
modalVisible,
modalContent,
counter
};
}
export default connect(mapStateToProps)(Meeting);
Component 2
class MeetingItem extends Component {
state = {
checked: []
}
handleChange = (event) => {
const { dispatch, counter } = this.props;
if (event.target.checked) {
this.setState(() => {
return { checked: [...this.state.checked, event.target.value] };
});
dispatch(setCounter(counter - 1));
} else {
const array = this.state.checked;
const index = array.indexOf(event.target.value);
if (index > -1) {
array.splice(index, 1);
this.setState(() => {
return { checked: array };
});
dispatch(setCounter(counter + 1));
}
}
};
isDisable = (val) => {
const array = this.state.checked;
const index = array.indexOf(val);
if (index > -1) {
return true;
} else {
return false;
}
}
showModal = () => {
const { dispatch, question, counter } = this.props;
const radioStyle = {
display: 'block',
height: '30px',
lineHeight: '30px',
marginTop: '6px'
};
dispatch(setCounter(0));
switch (question.question.TypeAnswer) {
case 'OneAnswer':
const answers = question.answer.map((record, i) => {
return <Radio.Button style={radioStyle} key={i} value={record.Id}>{record.Title}</Radio.Button>
});
const modalContent = <div>
<p>{question.question.Title}</p>
<Radio.Group buttonStyle="solid">
{answers}
</Radio.Group>
</div>
dispatch(setModalContent(modalContent));
break;
case 'MultiAnswer':
dispatch(setCounter(question.question.CountAnswer));
const multiAnswers = question.answer.map((record, i) => {
return <Checkbox key={i} value={record.Id} onChange={this.handleChange}>{record.Title}</Checkbox>
});
const multiModalContent = <div>
<p>{question.question.Title}</p>
<p>counter: {counter}</p>
<Checkbox.Group>
{multiAnswers}
</Checkbox.Group>
</div>
dispatch(setModalContent(multiModalContent));
break;
case 'PercentAnswer':
break;
default: <div></div>
break;
}
dispatch(showModal(true));
};
render() {
const { title, question, fileDoc } = this.props;
return (
<Timeline.Item className='ant-timeline-item-right'>
<span style={{ fontSize: '16px', fontWeight: 'bolder' }}>{title}</span> <span className='textAction' onClick={this.showModal}>showModal</span>
{/* <br /> */}
{/* <span>12:00</span> <Icon type="clock-circle" /> */}
</Timeline.Item>
);
}
}
function mapStateToProps(state) {
const {
visibleModal,
counter
} = state.meeting;
return {
visibleModal,
counter
};
}
export default connect(mapStateToProps)(MeetingItem);
Action
export const setCounter = (counter) => (dispatch) => {
return dispatch({ type: actionTypes.SET_COUNTER, counter })
}
You have to use mapDispatchToProps into your connect function.
...
function mapDispatchToProps (dispatch) {
propAction: (action) => dispatch(reduxAction(action))
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(MeetingItem);
...
Follow this tutorial.
Just using mapDispatchToProps inside the connection.