Get data from React props - javascript

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 })
}
}

Related

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

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})
}

Cannot use redux data in ComponentDidMount

I am making a react-redux site.
I am accessing data called from an api via redux.
I understand that ComponentDidMount will not wait for this data to be called so I was wondering on a better way to split this data within a parent component into arrays for children components (or if this method is a bad choice).
This is the component and will hopefully shed some light on what is going on.
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { fetchPosts, fetchItins } from "../../../actions/postActions";
import TravelAlerts from "./TravelAlerts//travelAlert";
import IntelligenceAlerts from "./IntelligenceAlerts/IntelligenceAlert";
import AllAlerts from "./AllAlerts/AllAlerts";
class Alerts extends Component {
state = {
showAllAlerts: true,
showAllIntelligenceAlerts: false,
showAllTravellers: false,
currentPage: 1,
alertsPerPage: 20,
travelAlerts: [],
intelligenceAlerts: [],
};
componentDidMount() {
this.props.fetchPosts();
console.log(this.props.posts);
for (var key in this.props.posts) {
if (this.props.posts.hasOwnProperty(key)) {
if (key === "travelAlerts") {
alert("travel ALerts Hit");
} else if (key === "intelligenceAlerts") {
alert("intelligenceAlertsHIts");
} else {
}
console.log(key + " -> " + this.props.posts[key]);
}
}
}
//navigation helper
DisableAlerts() {
this.setState({
showAllAlerts: false,
showAllIntelligenceAlerts: false,
showAllTravellers: false,
});
}
//pagination change page
handleClick(number) {
this.setState({
currentPage: number,
});
}
ToogleAlertType(name) {
this.DisableAlerts();
if (name === "All") {
this.setState({ showAllAlerts: true });
} else if (name === "Intelligence") {
this.setState({ showAllIntelligenceAlerts: true });
} else if (name === "Travellers") {
this.setState({ showAllTravellers: true });
} else {
this.setState({ showAllAlerts: true });
}
}
render() {
return (
<div>
<button
style={{ width: "30%" }}
onClick={() => this.ToogleAlertType("ALL")}
>
ALL{" "}
</button>
<button
style={{ width: "30%" }}
onClick={() => this.ToogleAlertType("Intelligence")}
>
Intelligence{" "}
</button>
<button
style={{ width: "30%" }}
onClick={() => this.ToogleAlertType("Travellers")}
>
Travellers
</button>
<br />
<hr />
<div>
{this.state.showAllAlerts ? (
<>{/* <AllAlerts alerts={this.props.posts} /> */}</>
) : (
<></>
)}
</div>
<>
{this.state.showAllTravellers ? (
<>
<></>
{/* <TravelAlerts alerts={this.props.posts} /> */}
</>
) : (
<></>
)}
</>
<>
{this.state.showAllIntelligenceAlerts ? (
<>{/* <IntelligenceAlerts alerts ={this.props.posts}/> */}</>
) : (
<></>
)}
</>
</div>
);
}
}
Alerts.propTypes = {
fetchPosts: PropTypes.func.isRequired,
posts: PropTypes.object.isRequired,
// newPost: PropTypes.object
};
const mapStateToProps = (state) => ({
posts: state.posts.items,
// newPost: state.posts.item
});
export default connect(mapStateToProps, { fetchPosts })(Alerts);
The component is mapped to redux and is working fine however the data being retrieved is within an object and I would like this to be two separate arrays which I could then pass down to the child components etc.
This is what I am currently trying to do in the component did mount just to see if it can find the keys.
componentDidMount() {
this.props.fetchPosts();
console.log(this.props.posts);
for (var key in this.props.posts) {
if (this.props.posts.hasOwnProperty(key)) {
if (key === "travelAlerts") {
alert("travel ALerts Hit");
} else if (key === "intelligenceAlerts") {
alert("intelligenceAlertsHIts");
} else {
}
console.log(key + " -> " + this.props.posts[key]);
}
}
}
However the data does not show when mounted(works in render method but I feel that is not a good place to put it).
Is this a good direction to head in or should I have split these into two separate arrays before they have even reached the component? If so should this be done so in the reducer or in the actions?
Really appreciate any help as I am new to redux.
EDIT
This is my fetch posts
export const fetchPosts = () => (dispatch) => {
fetch(
"the url im using"
)
.then((res) => res.json())
.then((posts) =>
dispatch({
type: FETCH_POSTS,
payload: posts,
})
);
};
My reducer
import { FETCH_POSTS, NEW_POST, FETCH_ITINS } from "../actions/types";
const initialState = {
items: {},
item: {},
itins: [],
};
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_POSTS:
return {
...state,
items: action.payload,
};
case NEW_POST:
return {
...state,
item: action.payload,
};
case FETCH_ITINS:
return {
...state,
itins: action.payload,
};
default:
return state;
}
}

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;

How do I add column sort functionality to my table?

I have tried now for a couple of hours to add sort functionality on a react-virtualized table, but can't get my head around it... here is the example and source code. Sorry, I was not able to create a plnkr, so I have attached a link to my github repo.
I get this error, when I submit the form with a input text SCREW for example:
TypeError: list.sortBy is not a function
at PartTable._sortList (PartTable.js:136)
at new PartTable (PartTable.js:18)
In the example on the site they uses context (const { list } = this.context;) instead of props. Maybe this is the issue?
When I console log this.props.list I get the correct list (see the two example documents)
_sortList({ sortBy, sortDirection }) {
const { list } = this.props;
// console.log(list);
return list
.sortBy(item => item[sortBy])
.update(
list => (sortDirection === SortDirection.DESC ? list.reverse() : list)
);
}
Here are two objects coming from my server and into props.list
[
{
customsTariff: "73181568",
facility: "SDC",
netWeight: "0,07",
partName: "CAPSCREW",
partNumber: "3121210233",
__v: 0,
_id: "59a9429ac0b7467bf084eb6e"
},
{
customsTariff: "73481568",
facility: "SDC",
netWeight: "0,08",
partName: "CAPSCREW2",
partNumber: "3121210333",
__v: 0,
_id: "59a9429ac0b7463bf084eb6e"
}
];
Here is the code from PartTable.js
import React, { PureComponent } from "react";
import { AutoSizer, Column, Table } from "react-virtualized";
import { CSVLink, CSVDownload } from "react-csv";
import Button from "material-ui/Button";
import PropTypes from "prop-types";
import SortDirection from "./SortDirection";
import SortIndicator from "./SortIndicator";
import Checkbox from "material-ui/Checkbox";
import "react-virtualized/styles.css";
import "../../styles/App.css";
import styles from "./Table.example.css";
export default class PartTable extends PureComponent {
constructor(props) {
super(props);
const sortBy = "partNumber"; // I want to sort by partNumber by default
const sortDirection = SortDirection.ASC;
const sortedList = this._sortList({ sortBy, sortDirection });
this.state = {
sortBy,
sortDirection,
rowCount: 1000,
sortedList
};
this._noRowsRenderer = this._noRowsRenderer.bind(this);
this._generateCheckbox = this._generateCheckbox.bind(this);
this._sort = this._sort.bind(this);
}
render() {
// console.log(this.props.list);
const { sortBy, sortDirection, sortedList } = this.state;
const rowGetter = ({ index }) => this._getDatum(sortedList, index);
const { list, headers } = this.props;
return (
<div>
<AutoSizer disableHeight>
{({ width }) => (
<Table
width={width}
height={500}
headerHeight={50}
rowHeight={50}
rowCount={list.length}
rowGetter={rowGetter}
noRowsRenderer={this._noRowsRenderer}
sort={this._sort}
sortBy={sortBy}
sortDirection={sortDirection}
>
{headers.map(header => {
return (
<Column
key={header.id}
label={header.label}
dataKey={header.id}
disableSort
width={100}
flexGrow={1}
cellRenderer={
header.index ? this._generateCheckbox : undefined
}
/>
);
})}
</Table>
)}
</AutoSizer>
<CSVLink data={list}>
<Button color="accent">Export {list.length} records to CSV</Button>
</CSVLink>
</div>
);
}
_noRowsRenderer() {
return <div>No Parts here...</div>;
}
_generateCheckbox(event) {
// console.log(event);
return (
<div className="table-check-box">
{this.props.activeCheckboxes && (
<Checkbox
onChange={() => this.props._activeCheckbox(event.rowData._id)}
checked={this.props.activeCheckboxes.includes(event.rowData._id)}
/>
)}
{event.cellData}
</div>
);
}
_isSortEnabled() {
const { list } = this.props;
const { rowCount } = this.state;
return rowCount <= list.size;
}
_getDatum(list, index) {
return list.get(index % list.size);
}
_sort({ sortBy, sortDirection }) {
const sortedList = this._sortList({ sortBy, sortDirection });
this.setState({ sortBy, sortDirection, sortedList });
}
_sortList({ sortBy, sortDirection }) {
const { list } = this.props;
// console.log(list);
return list
.sortBy(item => item[sortBy])
.update(
list => (sortDirection === SortDirection.DESC ? list.reverse() : list)
);
}
_rowClassName({ index }) {
if (index < 0) {
return styles.headerRow;
} else {
return index % 2 === 0 ? styles.evenRow : styles.oddRow;
}
}
}
PartTable.PropTypes = {
list: PropTypes.arrayOf({}).isRequired,
activeCheckboxes: PropTypes.arrayOf({}),
_activeCheckbox: PropTypes.func,
headers: PropTypes.arrayOf({}.isRequired)
};
Based on the fact that your code has references to list.length- it seems like the list prop you're accepting might be an Array? The sortBy method (assuming you lifted this from the react-virtualized docs) belongs to an Immutable JS List. To sort a JavaScript Array you'll want to use Array.prototype.sort.
PS. The code you pasted has a couple of other references to List methods (eg list.get(index)) that you'll want to replace with brackets (eg list[index]) if I'm correct that you're using an Array.
Full code solution for PartTable.js
(Provided by author)
import React, { PureComponent } from "react";
import {
AutoSizer,
Column,
Table,
SortDirection,
SortIndicator
} from "react-virtualized";
import { CSVLink, CSVDownload } from "react-csv";
import Button from "material-ui/Button";
import PropTypes from "prop-types";
import Checkbox from "material-ui/Checkbox";
import "react-virtualized/styles.css";
import "../../styles/App.css";
import styles from "./Table.example.css";
export default class PartTable extends PureComponent {
constructor(props) {
super(props);
const sortBy = "partNumber";
const sortDirection = SortDirection.ASC;
const sortedList = this._sortList({ sortBy, sortDirection });
this.state = {
list: this.props.list,
sortBy,
sortDirection,
sortedList,
rowCount: 1000
};
this._noRowsRenderer = this._noRowsRenderer.bind(this);
this._generateCheckbox = this._generateCheckbox.bind(this);
this._sort = this._sort.bind(this);
}
render() {
const { headers } = this.props;
const { list, sortBy, sortDirection, sortedList, rowCount } = this.state;
const rowGetter = ({ index }) => this._getDatum(sortedList, index);
return (
<div>
<AutoSizer disableHeight>
{({ width }) => (
<Table
width={width}
height={500}
headerHeight={50}
rowHeight={50}
rowClassName={this._rowClassName}
rowCount={rowCount}
rowGetter={({ index }) => list[index]} // ({ index }) => list[index]
noRowsRenderer={this._noRowsRenderer}
onHeaderClick={this._sortByClickedHeader}
sort={this._sort}
sortBy={sortBy}
sortDirection={sortDirection}
>
{headers.map(header => {
return (
<Column
key={header.id}
label={header.label}
dataKey={header.id}
width={100}
flexGrow={1}
cellRenderer={
header.index ? this._generateCheckbox : undefined
}
/>
);
})}
</Table>
)}
</AutoSizer>
<CSVLink data={list}>
<Button color="accent">Export {list.length} records to CSV</Button>
</CSVLink>
</div>
);
}
_getDatum(list, index) {
return list[index];
}
_sort({ sortBy, sortDirection }) {
const sortedList = this._sortList({ sortBy, sortDirection });
this.setState({ sortBy, sortDirection, sortedList });
}
_sortList({ sortBy, sortDirection }) {
const { list } = this.props;
if (sortBy) {
let updatedList =
// sort by name
list.sort(function(a, b) {
var nameA = a[sortBy].toUpperCase(); // ignore upper and lowercase
var nameB = b[sortBy].toUpperCase(); // ignore upper and lowercase
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
// names must be equal
return 0;
});
sortDirection === SortDirection.DESC
? updatedList.reverse()
: updatedList;
}
}
_noRowsRenderer() {
return <div>No Parts here...</div>;
}
_generateCheckbox(event) {
// console.log(event);
return (
<div className="table-check-box">
{this.props.activeCheckboxes && (
<Checkbox
onChange={() => this.props._activeCheckbox(event.rowData._id)}
checked={this.props.activeCheckboxes.includes(event.rowData._id)}
/>
)}
{event.cellData}
</div>
);
}
}
PartTable.PropTypes = {
list: PropTypes.arrayOf({}).isRequired,
activeCheckboxes: PropTypes.arrayOf({}),
_activeCheckbox: PropTypes.func,
headers: PropTypes.arrayOf({}.isRequired)
};

Categories