How to handle input field in React Table - javascript

I am recently developing an email list using React Table.
There is a BCC field that allows users to insert an email address.
I have implemented App.tsx as the Root component and EmailForm.tsx as the child one.
After every action from Child (Insert characters in BCC, select the checkboxes), an update email list is sent from the Child to Parent component where all main activities will be handled.
I am facing a problem that every time I try to insert a new character in BCC input, "onChange" event just takes one character and not the whole input text area.
I followed this thread, but it did not help.
My repo:
https://codesandbox.io/s/thirsty-ellis-2981iv
My Parent component: App.tsx
import React, { Component } from "react";
import { Formik } from "formik";
import './App.css';
import EmailForm from "./EmailForm";
interface IEmail {
"title": number;
"checkList": ICheckList[];
"bcc": IBcc;
}
interface ICheckList {
"isEnable": boolean;
"email": string
}
interface IBcc {
"isEnable": boolean;
"email": string
}
const defaults = [
{
title: "title-1",
checkList: [{
isEnable: true,
email: "title-1.1#mail.com"
},
{
isEnable: true,
email: "title-1.2#mail.com"
}],
bcc: {
isEnable: true,
email: ""
}
},
{
title: "title-2",
checkList: [{
isEnable: true,
email: "title-2#mail.com"
}],
bcc: {
isEnable: true,
email: ""
}
},
{
title: "title-3",
checkList: [{
isEnable: true,
email: "title-3#mail.com"
}],
bcc: {
isEnable: true,
email: ""
}
}
];
class App extends Component {
state = {
data: defaults,
}
getInitialValues = () => {
const initialValues = {
...defaults
};
return initialValues;
}
handleBccInput = (index: number, event: string) => {
console.log('handleBccInput index: ' + index + ' bccInput : ' + event)
let data = [...this.state.data];
//console.log('data: ' + JSON.stringify(data));
data[index].bcc.email = event;
console.log('data[index].bcc.email: ' + data[index].bcc.email);
this.setState({ data });
}
onSubmit = () => {
console.log('onSubmit clicked')
}
handleCheckboxSelected = (emailIdx: number, addressIdx: number) => {
let data = [...this.state.data];
data[emailIdx].checkList[addressIdx].isEnable = !data[emailIdx].checkList[addressIdx].isEnable
this.setState({ data });
}
render() {
const initialValues = this.getInitialValues();
const renderForm = (props: any) => (
<EmailForm
{...props}
data={this.state.data}
handleBccInput={this.handleBccInput}
handleCheckboxSelected={this.handleCheckboxSelected}
/>
);
return (
<React.Fragment >
<Formik
// tslint:disable-next-line jsx-no-lambda
render={props => renderForm(props)}
initialValues={initialValues}
onSubmit={this.onSubmit}
validateOnBlur={true}
validateOnChange={true} />
</React.Fragment>
);
}
}
export default App;
My Child component: EmailForm.tsx:
import React, { Component } from "react";
import { Form, FormikProps } from "formik";
import { WithTranslation, withTranslation } from "react-i18next";
import ReactTable, { Column } from "react-table";
import "react-table/react-table.css";
interface IEmail {
"title": number;
"checkList": ICheckList[];
"bcc": IBcc;
}
interface ICheckList {
"isEnable": boolean;
"email": string
}
interface IBcc {
"isEnable": boolean;
"email": string
}
interface IState {
data: IEmail[],
handleBccInput(index: any, event: string): any,
handleCheckboxSelected(emailIndex: number, addressIndex: number): any,
}
class EmailForm extends Component<IEmail & IState & WithTranslation> {
renderCheckbox = (title: string) => {
return (
<div>{title}</div>
);
}
overrideValue = (index: number, override: any) => {
//console.log('index: ' + index + ' override: ' + override)
this.props.handleBccInput(index, override)
}
onCheckBoxItemSelected = (emailIndex: number, addressIndex: number) => {
this.props.handleCheckboxSelected(emailIndex, addressIndex)
}
renderHeader = (title: string) => {
return (
<div
style={{
textAlign: "center",
}}
>{title}</div>
);
}
tableHeader = (): Array<Column<IEmail>> => {
// Extract transalation variable from props
return [
{
Header: this.renderHeader('Title'),
id: "title",
accessor: "title",
width: 200,
Cell: props => {
return (
<input value={props.value} readOnly></input>
)
},
},
{
Header: this.renderHeader("Check List"),
id: "checkList",
accessor: "checkList",
sortable: false,
width: 200,
resizable: true,
Cell: props => {
const cellValues = props.value
//console.log('cell.value : ' + JSON.stringify(cellValues))
return cellValues.map((item: ICheckList, index: number) => {
return (
<div>
<input type="checkbox" checked={item.isEnable} onChange={() => this.onCheckBoxItemSelected(props.index, index)} />
<a>{item.email}</a>
</div>
)
})
}
},
{
Header: this.renderHeader("BCC"),
id: "bcc",
accessor: "bcc",
Cell: props => {
return (
<input disabled={!props.value.isEnable} value={props.value.email} onChange={e => { this.overrideValue(props.index, e.target.value) }} type="text" ></input>
)
},
width: 200,
}
];
}
/*
Component render function
*/
render() {
const {
t,
data,
} = this.props
const emptyElement = () => null;
const tableHeader = this.tableHeader();
return (
<Form className="email-container-form">
<ReactTable
columns={tableHeader}
resizable={false}
data={data}
loading={false}
showPagination={false}
NoDataComponent={emptyElement}
defaultPageSize={Number.MAX_SAFE_INTEGER}
minRows={1}
/>
</Form>
)
}
}
export default withTranslation()(EmailForm);

Related

Converting a class component to function component to use react hooks

Can the following class component be converted into a function component by any chance?
The main issue I'm having is inside the following cellsrenderer function
cellsrenderer: (row, columnfield, value, defaulthtml, columnproperties): string => {
axios
.get("api/personnels/"+value)
.then(response => {
this.setState({
createdByName: response.data.displayedValues
}, ()=> {
console.log('Inside axios response after setting the state to the name of the project creater')
})
}).catch(err => console.log(err));
return this.state.createdByName;
}
}
I am running into an issue of infinite loop because of setState re-rendering issue and want to avoid it by using useEffect() hook maybe if I could but since this is a class component, I am not able to proceed forward.
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import {FormikApp} from './forms/AddProjectForm'
import JqxGrid, {IGridProps, jqx} from 'jqwidgets-scripts/jqwidgets-react-tsx/jqxgrid';
import JqxButton from 'jqwidgets-scripts/jqwidgets-react-tsx/jqxbuttons'
import {RouteComponentProps} from 'react-router-dom'
import 'jqwidgets-scripts/jqwidgets/styles/jqx.base.css'
import 'jqwidgets-scripts/jqwidgets/styles/jqx.material.css'
import 'jqwidgets-scripts/jqwidgets/styles/jqx.arctic.css'
import {Dialog} from "primereact/dialog";
import {Button} from "primereact/button";
import {properties} from "../properties";
import {Card} from "primereact/card";
import axios from "axios";
import {Messages} from "primereact/messages";
import _ from 'lodash'
export interface IState extends IGridProps {
projects: [],
selectedProject: [],
createdByName :string,
addDialogVisible: boolean,
blazerId: string,
username: string,
selectedRowIndex: number,
deleteDialogVisible: boolean
}
class Projects extends React.PureComponent<RouteComponentProps<{}>, IState> {
private baseUrl = properties.baseUrlWs
private myGrid = React.createRef<JqxGrid>()
private messages = React.createRef<Messages>()
private editrow: number = -1;
constructor(props: RouteComponentProps) {
super(props);
this.selectionInfo = this.selectionInfo.bind(this)
this.gridOnSort = this.gridOnSort.bind(this);
const columns: IGridProps['columns'] = [
{ text: 'Project Name', datafield: 'name', width: 390 },
{ text: 'Project Description', datafield: 'description', width: 390 },
{ text: 'Owner Assigned', datafield: 'institutionId', width: 180,hidden:true },
{ text: 'Created By', datafield: 'createdBy',
cellsrenderer: (row, columnfield, value, defaulthtml, columnproperties): string => {
axios
.get("api/personnels/"+value)
.then(response => {
this.setState({
createdByName: response.data.displayedValues
}, ()=> {
console.log('Inside axios response after setting the state to the name of the project creater')
})
}).catch(err => console.log(err));
return this.state.createdByName;
}
}
]
const source:any = {
dataFields: [
{ name: 'id', type: 'long'},
{ name: 'name', type: 'string' },
{ name: 'description', type: 'string' },
{ name: 'url', type: 'string'},
{ name: 'irbProtocol', type: 'string'},
{ name: 'institutionId', type: 'long' },
{ name: 'projectType', type: 'string' },
{ name: 'priority', type: 'string'},
{ name: 'researchDataSetType', type: 'string'},
{ name: 'statusIndicatorId', type: 'long'},
{ name: 'createdBy', type: 'string' }
],
dataType: 'json',
root: 'projects',
sortColumn: 'name',
sortdirection: 'asc',
url: this.baseUrl + 'api/projects/search/getProjectsById',
data: {
value: ''
}
}
const dataAdapter:any = new jqx.dataAdapter(source,
{
autoBind: true,
downloadComplete: (data:any, status:any, xhr:any):void => {
// if (!source.totalrecords) {
source.totalrecords = parseInt(data['page'].totalElements);
// }
},
formatData: (data:any):any => {
data.page = data.pagenum
data.size = data.pagesize
if (data.sortdatafield && data.sortorder) {
data.sort = data.sortdatafield + ',' + data.sortorder;
}
return data;
},
loadError (xhr, status, error) {
throw new Error('Error occurred in getting Projects for user ' + error.toString());
}
}
);
this.state = {
projects: [],
selectedProject: [],
createdByName : '',
blazerId: '',
username: '',
addDialogVisible: false,
selectedRowIndex: null,
deleteDialogVisible: false,
columns: columns,
rendergridrows: (params: any): any[] => {
const data = params.data
return data;
},
source: dataAdapter,
};
}
setValueProperty = (data:any):any => {
if (this.state && this.state.blazerId) {
data.value = this.state.blazerId
}
}
private gridOnSort(event: any): void {
const sortinformation = event.args.sortinformation;
let sortdirection = sortinformation.sortdirection.ascending ? 'ascending' : 'descending';
if (!sortinformation.sortdirection.ascending && !sortinformation.sortdirection.descending) {
sortdirection = 'null';
}
this.myGrid.current.updatebounddata('sort')
};
selectionInfo = (event: any): void => {
const selection = this.myGrid.current.getrowdata(event.args.rowindex)
this.setState({
selectedProject: selection
}, () => {
console.log('pushing ' + this.state.selectedProject)
this.props.history.push({
pathname: '/project',
state: {
project: this.state.selectedProject,
blazerId: this.state.blazerId
}
})
});
}
componentDidMount() {
console.log('In Projects.componentDidMount....' + sessionStorage.getItem('loggedInUser'))
if (sessionStorage.getItem('loggedInUser') != null) {
const loggedInUser = JSON.parse(sessionStorage.getItem('loggedInUser') as string)
this.setState({ employeeId: loggedInUser.employeeId})
}
}
render() {
const defaultView = this.state.addDialogVisible ? null : (this.state.employeeId && !_.isEmpty(this.state.employeeId)) ? (
<div style={{width: '100%', margin: '0 auto', display: 'table'}}>
<JqxGrid
// #ts-ignore
ref={this.myGrid}
theme={'arctic'}
altrows={true}
width="100%"
autoheight={true}
source={this.state.source}
columns={this.state.columns}
pageable={true}
sortable={true}
onSort={this.gridOnSort}
pagesize={20}
virtualmode={true}
rendergridrows={this.state.rendergridrows}
showtoolbar={true}
rendertoolbar={this.state.rendertoolbar}
columnsresize={true}/>
</div>
) : null
return (
<div className="project-page-main">
<Messages ref={this.messages} style={{width: '100%', margin: 'auto' }}/>
<div className="content">
{defaultView}
</div>
</div>
);
}
}
export default Projects;

Getting value of SetState not updated in react

I am having a callback function which updates my array in the SetState and it works fine as I can see in the console log in the handleDaysValueChange all is good. But when I try to access it in another function i.e handleValuesChange the values are not up to date. I am missing a async or something.
import React from 'react';
import PropTypes from 'prop-types';
import AppService from 'services/app-service';
import Enum from 'enum';
import { ApiData } from 'data';
import { isEmpty, boolValue } from 'core/type-check';
import { notifySuccess } from 'core/errors';
import { withDrawerForm } from 'components/_hoc';
import { ExtendedAvatar } from 'components/extended-antd';
import { Tabs } from 'antd';
import { FormTemplateAudit } from '../templates';
import ShiftRosterFormDetails from './shiftroster-form-details';
import Item from 'antd/lib/list/Item';
const { TabPane } = Tabs;
class ShiftRosterForm extends React.Component {
constructor(props) {
super(props);
this.formInputRef = React.createRef();
this.state = {
daysValue:[]
};
this.handleDaysValueChange = this.handleDaysValueChange.bind(this);
}
handleDaysValueChange(daysValues) {
this.setState({ daysValue: daysValues }, () => {
console.log("Data" + this.props.customData);
console.log("Hello World " + JSON.stringify(this.state.daysValue));
});
}
componentDidMount() {
// Update the parent form state with a reference to the
// formInputRef so we can call this later for validation
// before saving the data to the server.
const { onFormStateChange } = this.props;
onFormStateChange({ formInputRef: this.formInputRef.current });
}
//Shift Roster Detail
handleValuesChange = (props, changedValues, allValues) => {
// Update the parent form state with the changed item
// details and mark the form as dirty
const { itemData, onFormStateChange } = this.props;
console.log("Hey" + this.state.daysValue);
onFormStateChange({
isFormDirty: true,
itemData: {
...itemData,
...changedValues,
...this.state.daysValue
}
});
}
render() {
const { itemData, customData } = this.props;
const isSR = (!isEmpty(itemData) && itemData.id > 0);
return (
<Tabs className="bhp-tabs" defaultActiveKey="1" animated={false}>
<TabPane key="1" tab="Details">
<ShiftRosterFormDetails
ref={this.formInputRef}
dataSource={itemData}
onValuesChange={this.handleValuesChange}
handleDaysValueChange={this.handleDaysValueChange}
/>
</TabPane>
<TabPane key="2" tab="Audit" disabled={!isSR}>
<FormTemplateAudit
itemData={itemData}
/>
</TabPane>
</Tabs>
);
}
}
ShiftRosterForm.propTypes = {
itemId: PropTypes.number, // Passed in by the HOC. The loaded Shift Roster id
itemData: PropTypes.object, // Passed in by the HOC. The loaded Shift Roster data
customData: PropTypes.object, // Temporary store to hold the changed Shift Roster
isFormDirty: PropTypes.bool, // Passed in by the HOC. Flags if the parent form is dirty
isLoading: PropTypes.bool, // Passed in by the HOC. Flags if the parent form is loading
daysValue: PropTypes.object,
onFormStateChange: PropTypes.func // Passed in by the HOC. Callback to update the parent form state.
};
ShiftRosterForm.defaultProps = {
itemId: -1,
itemData: {},
customData: {},
isFormDirty: false,
isLoading: false,
daysValue: {},
onFormStateChange() { }
};
const ShiftRosterFormTitle = ({ data }) => {
const name = (!isEmpty(data) && data.id > 0) ? `${data.name}` : 'New Shift Roster';//`${data.name}`
return isEmpty(data)
? <ExtendedAvatar type="icon" size="large" />
: <span><ExtendedAvatar name={name} type="letter" size="large" />{name}</span>
}
const saveShiftRoster = (shiftrosterId, shiftroster, rosterdays) => {
return ApiData.saveShiftRoster(shiftrosterId, shiftroster, rosterdays)
.then(response => {
notifySuccess('Save Successful', 'Site Work Package saved successfully');
return response;
})
.catch(error => {
throw error;
});
}
const saveForm = (formState, setFormState) => {
const { isFormDirty, itemData, customData, formInputRef } = formState;
const typeName = "Dynamic";
const actualType = itemData.type;
let rosterdays = [];
if (actualType !== typeName) {
rosterdays = GetDaysForRoster(itemData);
console.log("My Values" + JSON.stringify(rosterdays));
}
const shiftRosterId = itemData.id;
const isExistingShiftRoster = shiftRosterId > 0;
return new Promise((resolve, reject) => {
if (isExistingShiftRoster && !isFormDirty) {
// No Changes
notifySuccess('Save Successful', 'Site Work Package saved successfully');
resolve(itemData);
}
else {
// Validate and Save
formInputRef.validateFields((error, values) => {
if (!error) {
// Form validated successfully.
// Save form changes
const shiftrosterRecord = saveShiftRoster(shiftRosterId, values, rosterdays);
resolve(shiftrosterRecord);
}
else {
// Form validation error.
// Return data as is.
resolve(itemData);
}
});
}
});
}
const GetDaysForRoster = (itemsData) => {
const result = [];
const keys = Object.keys(itemsData);
for (const k in keys) {
if (Number(k) == k) {
result[k] = itemsData[k]
}
}
return result.filter(function (el) { return el != null });
}
const WrappedShiftRosterForm = withDrawerForm({
containerClassName: 'bhp-equipment-type-form',
title: (record) => <ShiftRosterFormTitle data={record} />,
onLoad: (itemId, setFormState) => ApiData.getShiftRoster(itemId),
onSave: (formState, setFormState) => { return saveForm(formState, setFormState); },
canView: () => AppService.hasAccess({ [Enum.SecurityModule.EquipmentTypeDetails]: [Enum.SecurityPermission.Read] }),
canCreate: () => AppService.hasAccess({ [Enum.SecurityModule.EquipmentTypeDetails]: [Enum.SecurityPermission.Create] }),
canUpdate: () => AppService.hasAccess({ [Enum.SecurityModule.EquipmentTypeDetails]: [Enum.SecurityPermission.Update] })
})(ShiftRosterForm);
WrappedShiftRosterForm.propTypes = {
containerClassName: PropTypes.string,
itemId: PropTypes.number,
visible: PropTypes.bool,
onSave: PropTypes.func,
onClose: PropTypes.func
};
WrappedShiftRosterForm.defaultProps = {
containerClassName: null,
itemId: -1,
visible: false,
onSave() { },
onClose() { }
};
export default WrappedShiftRosterForm;
//ShiftRosterFormDetails
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { ApiData } from 'data';
import { Form, Input, Select, Button, space, InputNumber } from 'antd';
import ShiftDays from './shiftdays'
const ShiftRosterFormDetails = ({ form, dataSource, onValueChange, handleDaysValueChange }) => {
const { getFieldDecorator } = form;
const formLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 12 },
},
};
console.log("My datasource" + dataSource.shiftRosterDays);
//const daysRoster = dataSource.shiftRosterDays || [{ daysIn: 1, daysOut: 1, category: "Day Shift" }];
const [inputList, setInputList] = useState([{ daysIn: 1, daysOut: 1, category: "Day Shift" }]);
const [selectedType, setSelectedType] = useState(dataSource.type || 'Fixed Single');
const [isTotalDaysRequired, SetTotalDaysRequired] = useState(dataSource.type === 'Dynamic' ? true : false);
const [isTotalDaysRequiredMessage, setIsTotalDaysRequiredMessage] = useState(dataSource.type === 'Dynamic' ? 'Please enter the Total Days' : '');
const handleTypeChanged = (value, e) => {
setSelectedType(value);
if (value === "Dynamic") {
SetTotalDaysRequired(true);
setIsTotalDaysRequiredMessage('Please enter the Total Days');
}
if (value === "Fixed Single") {
if (inputList.length > 1) {
const list = [...inputList];
console.log("Total" + inputList.length);
list.splice(1, inputList.length);
setInputList(list);
console.log("My List" + JSON.stringify(list));
console.log("Input List" + JSON.stringify(inputList));
handleDaysValueChange(list);
}
}
else {
SetTotalDaysRequired(false);
setIsTotalDaysRequiredMessage('');
}
};
return (
<div className='bhp-equipment-type-form-details bhp-content-box-shadow'>
<Form {...formLayout}>
<Form.Item label='Name' hasFeedback>
{getFieldDecorator('name', {
initialValue: dataSource.name,
rules: [{
required: true,
message: 'Please enter the Name'
}],
})(
//disabled={dataSource.id > 0}
<Input placeholder='Name' />
)}
</Form.Item>
<Form.Item label='Status' hasFeedback>
{getFieldDecorator('status', {
initialValue: dataSource.status,
rules: [{
required: true,
message: 'Please enter the Status'
}],
})(
<Select>
<Select.Option value="Active">Active</Select.Option>
<Select.Option value="InActive">InActive</Select.Option>
</Select>
)}
</Form.Item>
<Form.Item label='Type' hasFeedback>
{getFieldDecorator('type', {
initialValue: dataSource.type || 'Fixed Single',
rules: [{
required: true,
message: 'Please select the Type'
}],
})(
<Select onChange={handleTypeChanged}>
<Select.Option value="Fixed Single">Fixed Single</Select.Option>
<Select.Option value="Fixed Multiple">Fixed Multiple</Select.Option>
<Select.Option value="Dynamic">Dynamic</Select.Option>
</Select>
)}
</Form.Item>
<Form.Item label='Total Days' hasFeedback style={selectedType === 'Dynamic' ? { display: '' } : { display: 'none' }}>
{getFieldDecorator('totaldays', {
initialValue: dataSource.totalDays,
rules: [{
required: isTotalDaysRequired,
message: isTotalDaysRequiredMessage
}],
})(
<InputNumber min={1} max={365} />
)}
</Form.Item>
<ShiftDays inputList={inputList} setInputList={setInputList} selectedType={selectedType} handleDaysValueChange={handleDaysValueChange} getFieldDecorator={getFieldDecorator} />
</Form>
</div>
)};
const onFieldsChange = (props, changedFields, allFields) => {
if (props.onFieldsChange) {
props.onFieldsChange(props, changedFields, allFields);
}
};
const onValuesChange = (props, changedValues, allValues) => {
if (props.onValuesChange) {
props.onValuesChange(props, changedValues, allValues);
}
};
ShiftRosterFormDetails.propTypes = {
form: PropTypes.object,
dataSource: PropTypes.object,
onFieldsChange: PropTypes.func,
onValuesChange: PropTypes.func
};
ShiftRosterFormDetails.defaultProps = {
form: {},
dataSource: {},
onFieldsChange() { },
onValuesChange() { }
};
export default Form.create({
onValuesChange,
onFieldsChange
})(ShiftRosterFormDetails);

Need to get all the values of all the child component instances and use it in parents method: ReactJS

I have this scenario where I have a table(react-table), where I am applying column level filtering. I have extracted this as a separate component(DropdDown Component) and this can be attached to any column . I am maintaining a method inside parent component which picks up the union of all the values i.e., selected values of all the dropdowns and then apply server side filtering.
Now the challenge here is , How can i get this consolidated values inside the parent component method?
This DropDown component has list of unique values with respect to that column, there is an Apply button , which applies the server side filtering. Now if I jump onto another column, I need to get the previously checked values and also the current values.
Inside handleSetData() filtering logic is written, I need to get the data from DropDown Component. Everytime I click on Apply on a column filter, I need to get the previously checked values as well.
Can someone help me with this:
Code Sandbox: https://codesandbox.io/s/quizzical-glitter-np8iw
App Component
import * as React from "react";
import { render } from "react-dom";
import ReactTable from "react-table";
import "./styles.css";
import "react-table/react-table.css";
import DropDownComponent from "./DropDown";
interface IState {
data: {}[];
columns: {}[];
}
interface IProps {}
export default class App extends React.Component<IProps, IState> {
constructor(props: any) {
super(props);
this.state = {
data: [
{ firstName: "aaaaa", status: "Pending", visits: 155 },
{ firstName: "aabFaa", status: "Pending", visits: 155 },
{ firstName: "adaAAaaa", status: "Approved", visits: 1785 },
{ firstName: "aAaaaa", status: "Approved", visits: 175 },
{ firstName: "adaSaaa", status: "Cancelled", visits: 165 },
{ firstName: "aaaaa", status: "Cancelled", visits: 157 },
{ firstName: "aaaaa", status: "Approved", visits: 153 },
{ firstName: "aaaaa", status: "Pending", visits: 155 }
],
columns: []
};
}
handleSetState = (columns: any) => {
this.setState({ columns });
};
handleSetData = (value: any) => {
console.log(value); // Here filtering logic is written, I need to get the data from DropDown Component. Everytime I click on Apply on a column filter, I need to get the previously checked values as well
};
componentDidMount() {
let columns = [
{
Header: () => (
<div>
<div style={{ position: "absolute", marginLeft: "10px" }}>
<DropDownComponent
data={this.state.data}
handleSetData={this.handleSetData}
param="firstName"
/>
</div>
<span>First Name</span>
</div>
),
accessor: "firstName",
sortable: false,
show: true,
displayValue: " First Name"
},
{
Header: () => (
<div>
<div style={{ position: "absolute", marginLeft: "10px" }}>
<DropDownComponent
data={this.state.data}
handleSetData={this.handleSetData}
param="status"
/>
</div>
<span>Status</span>
</div>
),
accessor: "status",
sortable: false,
show: true,
displayValue: " Status "
},
{
Header: "Visits",
accessor: "visits",
sortable: false,
show: true,
displayValue: " Visits "
}
];
this.setState({ columns });
}
render() {
const { data, columns } = this.state;
return (
<div>
<ReactTable
data={data}
columns={columns}
defaultPageSize={10}
className="-striped -highlight"
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
DropDown Component
import * as React from "react";
import { Button, Checkbox, Icon } from "semantic-ui-react";
interface IProps {
data: {}[];
handleSetData(arr: any): void;
param: string;
}
interface IState {
showList: boolean;
optionsArr: {}[];
originalState: {}[];
}
export default class DropDownComponent extends React.Component<IProps, IState> {
constructor(props: any) {
super(props);
this.state = {
showList: false,
optionsArr: [],
originalState: []
};
}
toggleList = () => {
this.setState(prevState => ({ showList: !prevState.showList }));
};
handleItemClick = (event: React.FormEvent<HTMLInputElement>, data: any) => {
const index = this.state.optionsArr.findIndex(
(item: any) => item.text === data.name
);
const optionsArr = this.state.optionsArr.map((prevState: any, i: any) =>
i === index
? {
key: prevState.key,
text: prevState.text,
checked: !prevState.checked
}
: prevState
);
this.setState({ optionsArr });
};
submitSelection = () => {
console.log(this.state.optionsArr.filter((item: any) => item.checked)); // This gives me selecte ones
let checkedValues: any = this.state.optionsArr.filter(
(item: any) => item.checked
);
this.setState({ originalState: this.state.optionsArr }, () =>
this.props.handleSetData(checkedValues)
);
};
componentDidMount() {
if (this.props.data) {
let arr = this.props.data;
let uniqueValues = Array.from(
new Set(arr.map((arr: any) => arr[this.props.param]))
);
var optionsArr = [];
for (let i = 0; i < uniqueValues.length; i++) {
var options: any = {};
options["key"] = uniqueValues[i];
options["text"] = uniqueValues[i];
options["checked"] = false;
optionsArr.push(options);
}
this.setState({ optionsArr: optionsArr, originalState: optionsArr });
}
}
clearSelection = (event: any) => {
// Push it to previous state, before cancel was clicked
this.setState({ showList: false, optionsArr: this.state.originalState });
};
render() {
let { showList } = this.state;
let visibleFlag: string;
if (showList === true) visibleFlag = "visible";
else visibleFlag = "";
return (
<div>
<div style={{ position: "absolute" }}>
<div
className={
"ui scrolling dropdown column-settings customized " +
visibleFlag +
" " +
this.props.menuDirection
}
>
<Icon className="filter" onClick={this.toggleList} />
{this.state.optionsArr.length > 0 ? (
<>
<div className="menu-item-holder">
{this.state.optionsArr.map((item: any, i: number) => (
<div className="menu-item" key={i}>
<Checkbox
name={item.text}
onChange={this.handleItemClick}
checked={item.checked}
label={item.text}
/>
</div>
))}
</div>
<div className="menu-btn-holder">
<Button size="small" onClick={this.submitSelection}>
Apply
</Button>
<Button size="small" onClick={this.clearSelection}>
Cancel
</Button>
</div>
</>
) : (
""
)}
</div>
</div>
</div>
</div>
);
}
}
I have modified your DropDown component. I make it receive a list of items to generate the checkboxes. Instead of sending the whole data object to the DropDown component, I think it makes more sense send a ready list to them, the Main component should generate the right data structure (I haven't done that, you have to create those functions). In the component, I create three states to manage the component.
Obs: I removed typescript to make faster for me.
condesandbox

React: How to add an array of Ids to the state object from nested objects

I have one form with some textboxes, in another component I have a table with a row selection.
When the button in the bottom is clicked I should send the parameters I already sent but additionally in the webapi I should receive a List with the ids selected.
My main component is this:
import React, { Component } from 'react';
import { Input} from 'antd';
import Form from '../../components/uielements/form';
import Button from '../../components/uielements/button';
import Notification from '../../components/notification';
import { adalApiFetch } from '../../adalConfig';
import ListPageTemplatesWithSelection from './ListPageTemplatesWithSelection';
const FormItem = Form.Item;
class CreateCommunicationSiteCollectionForm extends Component {
constructor(props) {
super(props);
this.state = {Title:'',Url:'', SiteDesign:'', Description:'',Owner:'',Lcid:''};
this.handleChangeTitle = this.handleChangeTitle.bind(this);
this.handleValidationCommunicationSiteUrl = this.handleValidationCommunicationSiteUrl.bind(this);
this.handleChangeCommunicationSiteUrl = this.handleChangeCommunicationSiteUrl.bind(this);
this.handleChangeSiteDesign = this.handleChangeSiteDesign.bind(this);
this.handleChangeDescription = this.handleChangeDescription.bind(this);
this.handleChangeOwner = this.handleChangeOwner.bind(this);
this.handleChangelcid = this.handleChangelcid.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChangeTitle(event){
this.setState({Title: event.target.value});
}
handleValidationCommunicationSiteUrl(rule, value, callback){
const form = this.props.form;
const str = form.getFieldValue('communicationsiteurl');
var re = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]#!\$&'\(\)\*\+,;=.]+$/i;
if (str && !str.match(re)) {
callback('Communication site url is not correctly formated.');
}
else {
callback();
}
}
handleChangeCommunicationSiteUrl(event){
this.setState({Url: event.target.value});
}
handleChangeSiteDesign(event){
this.setState({SiteDesign: event.target.value});
}
handleChangeDescription(event){
this.setState({Description: event.target.value});
}
handleChangeOwner(event){
this.setState({Owner: event.target.value});
}
handleChangelcid(event){
this.setState({Lcid: event.target.value});
}
handleSubmit(e){
e.preventDefault();
this.props.form.validateFieldsAndScroll((err, values) => {
if (!err) {
let data = new FormData();
//Append files to form data
//data.append(
const options = {
method: 'post',
body: JSON.stringify(
{
"Title": this.state.Title,
"Url": this.state.Url,
"SiteDesign": this.state.SiteDesign,
"Description": this.state.Description,
"Owner": this.state.Owner,
"Lcid": this.state.Lcid
}),
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
adalApiFetch(fetch, "/SiteCollection/CreateCommunicationSite", options)
.then(response =>{
if(response.status === 201){
Notification(
'success',
'Communication Site created',
''
);
}else{
throw "error";
}
})
.catch(error => {
Notification(
'error',
'Site collection not created',
error
);
console.error(error);
});
}
});
}
render() {
const { getFieldDecorator } = this.props.form;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 14 },
},
};
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0,
},
sm: {
span: 14,
offset: 6,
},
},
};
return (
<Form onSubmit={this.handleSubmit}>
<FormItem {...formItemLayout} label="Title" hasFeedback>
{getFieldDecorator('Title', {
rules: [
{
required: true,
message: 'Please input your communication site title',
}
]
})(<Input name="title" id="title" onChange={this.handleChangeTitle} />)}
</FormItem>
<FormItem {...formItemLayout} label="Communication Site Url" hasFeedback>
{getFieldDecorator('communicationSiteUrl', {
rules: [
{
required: true,
message: 'CommunicationSite site collection url',
},
{
validator: this.handleValidationCommunicationSiteUrl
}
]
})(<Input name="communicationsSiteUrl" id="communicationsSiteUrl" onChange={this.handleChangeCommunicationSiteUrl} />)}
</FormItem>
<FormItem {...formItemLayout} label="Site Design" hasFeedback>
{getFieldDecorator('sitedesign', {
rules: [
{
required: true,
message: 'Please input your site design',
}
]
})(<Input name="sitedesign" id="sitedesign" onChange={this.handleChangeSiteDesign} />)}
</FormItem>
<FormItem {...formItemLayout} label="Description" hasFeedback>
{getFieldDecorator('description', {
rules: [
{
required: true,
message: 'Please input your description',
}
],
})(<Input name="description" id="description" onChange={this.handleChangeDescription} />)}
</FormItem>
<FormItem {...formItemLayout} label="Owner" hasFeedback>
{getFieldDecorator('owner', {
rules: [
{
required: true,
message: 'Please input your owner',
}
],
})(<Input name="owner" id="owner" onChange={this.handleChangeOwner} />)}
</FormItem>
<FormItem {...formItemLayout} label="Lcid" hasFeedback>
{getFieldDecorator('lcid', {
rules: [
{
required: true,
message: 'Please input your lcid',
}
],
})(<Input name="lcid" id="lcid" onChange={this.handleChangelcid} />)}
</FormItem>
<ListPageTemplatesWithSelection />
<FormItem {...tailFormItemLayout}>
<Button type="primary" htmlType="submit">
Create communication site
</Button>
</FormItem>
</Form>
);
}
}
const WrappedCreateCommunicationSiteCollectionForm = Form.create()(CreateCommunicationSiteCollectionForm);
export default WrappedCreateCommunicationSiteCollectionForm;
and the nested component is this
import React, { Component } from 'react';
import { Table, Radio} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
class ListPageTemplatesWithSelection extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
fetchData = () => {
adalApiFetch(fetch, "/PageTemplates", {})
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
const results= responseJson.map(row => ({
key: row.Id,
Name: row.Name
}))
this.setState({ data: results });
}
})
.catch(error => {
console.error(error);
});
};
componentDidMount(){
this.fetchData();
}
render(){
const columns = [
{
title: 'Id',
dataIndex: 'key',
key: 'key',
},
{
title: 'Name',
dataIndex: 'Name',
key: 'Name',
}
];
// rowSelection object indicates the need for row selection
const rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
},
getCheckboxProps: record => ({
type: Radio
}),
};
return (
<Table rowSelection={rowSelection} columns={columns} dataSource={this.state.data} />
);
}
}
export default ListPageTemplatesWithSelection;
So basically every time the checkbox on each row is selected or unselected, then on the Parent component state I should add/remove the ID.
So that I can send it to the server when the button is pressed.
But I am not sure how to achieve this.
The best way to pass props from a child to a parent is by lifting the state up.
So the parent component would define a handleRowSelect(ids) function which handles taking the IDs of the currently selected rows. These can then be set in the state.
constructor(props) {
super(props);
this.state = {
selectedRows: [],
....
};
handleRowSelect(ids) {
this.setState({ selectedRows: ids });
}
It would pass the function and the selectedRows to the child component:
<ListPageTemplatesWithSelection onRowSelect={this.handleRowSelect) selectedRows={this.state.selectedRows} />
The child component would then have props called selectedRows and onRowSelect, which would call the handleRowSelect function of the parent. :
const rowSelection = {
selectedRowKeys: this.props.selectedRows,
onChange: (selectedRowKeys) => {
this.props.onRowSelect(selectedRowKeys);
}
};

HowTo: update the value of an element in a property

So, I have a property (fields), within which I wish to change the value of an element (countries). Alerting the value of countries currently displays the value 2, but I want to change the value to 100, so that re-alerting fields.countries.value, after the change, displays the new value.
How do I do this?
import type { State } from '../../common/types';
import DynamicField from './DynamicField';
import R from 'ramda';
import React from 'react';
import buttonsMessages from '../../common/app/buttonsMessages';
import linksMessages from '../../common/app/linksMessages';
import { FormattedMessage } from 'react-intl';
import { ValidationError } from '../../common/lib/validation';
import { connect } from 'react-redux';
import { fields } from '../../common/lib/redux-fields';
import {
Block,
Box,
Button,
Checkbox,
FieldError,
Flex,
Form,
Heading,
Input,
PageHeader,
Pre,
Radio,
Select,
Space,
Title,
View,
} from '../app/components';
// The example of dynamically loaded editable data.
// cato.org/publications/commentary/key-concepts-libertarianism
const keyConceptsOfLibertarianism = [
'Individualism',
'Individual Rights',
'Spontaneous Order',
'The Rule of Law',
'Limited Government',
'Free Markets',
'The Virtue of Production',
'Natural Harmony of Interests',
'Peace',
].map((concept, index) => ({
id: index,
name: concept,
}));
// Proof of concept. Country list will be read from firebase
const countryArray = [
{ label: 'Select Country', value: 0 },
{ label: 'France', value: 2 },
{ label: 'England', value: 4 },
{ label: 'Swizterland', value: 8 },
{ label: 'Germany', value: 16 },
{ label: 'Lithuania', value: 32 },
{ label: 'Romania', value: 64 },
].map((countryName, index) => ({
id: index,
name: countryName,
}));
// Dynamically create select list
const countryOptions = [];
countryArray.map(countryItem =>
countryOptions.push({ label: countryItem.name.label, value: countryItem.name.value }),
);
// Proof of concept. Country list will be read from firebase
const cityArray = [
{ label: 'Select City', value: 0 },
{ label: 'London', value: 50 },
{ label: 'Paris', value: 75 },
].map((cityName, index) => ({
id: index,
name: cityName,
}));
// Dynamically create select list
const cityOptions = [];
cityArray.map(cityItem =>
cityOptions.push({ label: cityItem.name.label, value: cityItem.name.value }),
);
// Proof of concept. Country list will be read from firebase
const gymArray = [
{ label: 'Select Gym', value: 0 },
{ label: 'Virgin Sport', value: 23 },
{ label: 'Sports Direct', value: 45 },
].map((gymName, index) => ({
id: index,
name: gymName,
}));
// Dynamically create select list
const gymOptions = [];
gymArray.map(gymItem =>
gymOptions.push({ label: gymItem.name.label, value: gymItem.name.value }),
);
type LocalState = {
disabled: boolean,
error: ?Object,
submittedValues: ?Object,
};
class FieldsPage extends React.Component {
static propTypes = {
fields: React.PropTypes.object.isRequired,
dynamicFields: React.PropTypes.object,
// getCities: React.PropTypes.object,
};
state: LocalState = {
disabled: false,
error: null,
submittedValues: null,
};
onFormSubmit = () => {
const { dynamicFields, fields } = this.props;
const values = {
...fields.$values(),
concepts: {
...dynamicFields,
},
};
// This is just a demo. This code belongs to Redux action creator.
// Disable form.
this.setState({ disabled: true });
// Simulate async action.
setTimeout(() => {
this.setState({ disabled: false });
const isValid = values.name.trim();
if (!isValid) {
const error = new ValidationError('required', { prop: 'name' });
this.setState({ error, submittedValues: null });
return;
}
this.setState({ error: null, submittedValues: values });
fields.$reset();
}, 500);
};
handleSelectedCountryChange = () => {
// Pass in the selected country value to get associated cites
const { fields, getCities } = this.props;
getCities('country', fields.$values());
};
/*
handleSelectedCityChange = (event => {
// Pass in the selected city value to get associated gyms
this.setState({secondLevel: event.target.value});
});
*/
render() {
const { fields } = this.props;
const { disabled, error, submittedValues } = this.state;
return (
<View>
<Title message={linksMessages.fields} />
<PageHeader
description="New clients enter their gym details here."
heading="New user entry form."
/>
<Form onSubmit={this.onFormSubmit}>
<Input
{...fields.name}
aria-invalid={ValidationError.isInvalid(error, 'name')}
disabled={disabled}
label="Your Name"
maxLength={100}
type="text"
/>
<FieldError error={error} prop="name" />
<Heading alt>Key Concepts of Libertarianism</Heading>
<Block>
<Flex wrap>
{keyConceptsOfLibertarianism.map(item =>
<Box mr={1} key={item.id}>
<DynamicField
disabled={disabled}
item={item}
path={['fieldsPage', 'dynamic', item]}
/>
</Box>,
)}
</Flex>
</Block>
<Block>
<Checkbox
{...fields.isLibertarian}
checked={fields.isLibertarian.value}
disabled={disabled}
label="I'm libertarian"
/>
<Checkbox
{...fields.isAnarchist}
checked={fields.isAnarchist.value}
disabled={disabled}
label="I'm anarchist"
/>
</Block>
<Block>
<Flex>
<Radio
{...fields.gender}
checked={fields.gender.value === 'male'}
disabled={disabled}
label="Male"
value="male"
/>
<Space x={2} />
<Radio
{...fields.gender}
checked={fields.gender.value === 'female'}
disabled={disabled}
label="Female"
value="female"
/>
<Space x={2} />
<Radio
{...fields.gender}
checked={fields.gender.value === 'other'}
disabled={disabled}
label="Other"
value="other"
/>
</Flex>
</Block>
<Block>
<Select
{...fields.countries}
disabled={disabled}
label="Countries"
onChange={this.handleSelectedCountryChange}
options={countryOptions}
/>
</Block>
<Block>
<Select
{...fields.cities}
disabled={disabled}
label="Cities"
// onChange={this.handleSelectedCityChange}
options={cityOptions}
/>
</Block>
<Block>
<Select
{...fields.gyms}
disabled={disabled}
label="Gyms"
// onChange={this.handleSelectedCityChange}
options={gymOptions}
/>
</Block>
{/*
Why no multiple select? Because users are not familiar with that.
Use checkboxes or custom checkable dynamic fields instead.
*/}
<Button disabled={disabled} type="submit">
<FormattedMessage {...buttonsMessages.submit} />
</Button>
{submittedValues &&
<Pre>
{JSON.stringify(submittedValues, null, 2)}
</Pre>
}
</Form>
</View>
);
}
}
FieldsPage = fields({
path: 'fieldsPage',
fields: [
'countries',
'cities',
'gyms',
'gender',
'isAnarchist',
'isLibertarian',
'name',
],
getInitialState: () => ({
countries: '0',
cities: '0',
gyms: '0',
gender: 'male',
isAnarchist: false,
isLibertarian: false,
}),
})(FieldsPage);
export default connect(
(state: State) => ({
dynamicFields: R.path(['fieldsPage', 'dynamic'], state.fields),
}),
)(FieldsPage);
=====================================================================
fields.js
/* #flow weak */
import R from 'ramda';
import React from 'react';
import invariant from 'invariant';
import { resetFields, setField } from './actions';
type Path = string | Array<string> | (props: Object) => Array<string>;
type Options = {
path: Path,
fields: Array<string>,
getInitialState?: (props: Object) => Object,
};
const isReactNative =
typeof navigator === 'object' &&
navigator.product === 'ReactNative'; // eslint-disable-line no-undef
// Higher order component for huge fast dynamic deeply nested universal forms.
const fields = (options: Options) => (WrappedComponent) => {
const {
path = '',
fields = [],
getInitialState,
} = options;
invariant(Array.isArray(fields), 'Fields must be an array.');
invariant(
(typeof path === 'string') ||
(typeof path === 'function') ||
Array.isArray(path)
, 'Path must be a string, function, or an array.');
return class Fields extends React.Component {
static contextTypes = {
store: React.PropTypes.object, // Redux store.
};
static getNormalizePath(props) {
switch (typeof path) {
case 'function': return path(props);
case 'string': return [path];
default: return path;
}
}
static getFieldValue(field, model, initialState) {
if (model && {}.hasOwnProperty.call(model, field)) {
return model[field];
}
if (initialState && {}.hasOwnProperty.call(initialState, field)) {
return initialState[field];
}
return '';
}
static lazyJsonValuesOf(model, props) {
const initialState = getInitialState && getInitialState(props);
// http://www.devthought.com/2012/01/18/an-object-is-not-a-hash
return options.fields.reduce((fields, field) => ({
...fields,
[field]: Fields.getFieldValue(field, model, initialState),
}), Object.create(null));
}
static createFieldObject(field, onChange) {
return isReactNative ? {
onChangeText: (text) => {
onChange(field, text);
},
} : {
name: field,
onChange: (event) => {
// Some custom components like react-select pass the target directly.
const target = event.target || event;
const { type, checked, value } = target;
const isCheckbox = type && type.toLowerCase() === 'checkbox';
onChange(field, isCheckbox ? checked : value);
},
};
}
state = {
model: null,
};
fields: Object;
values: any;
unsubscribe: () => void;
onFieldChange = (field, value) => {
const normalizedPath = Fields.getNormalizePath(this.props).concat(field);
this.context.store.dispatch(setField(normalizedPath, value));
};
createFields() {
const formFields = options.fields.reduce((fields, field) => ({
...fields,
[field]: Fields.createFieldObject(field, this.onFieldChange),
}), {});
this.fields = {
...formFields,
$values: () => this.values,
$setValue: (field, value) => this.onFieldChange(field, value),
$reset: () => {
const normalizedPath = Fields.getNormalizePath(this.props);
this.context.store.dispatch(resetFields(normalizedPath));
},
};
}
getModelFromState() {
const normalizedPath = Fields.getNormalizePath(this.props);
return R.path(normalizedPath, this.context.store.getState().fields);
}
setModel(model) {
this.values = Fields.lazyJsonValuesOf(model, this.props);
options.fields.forEach((field) => {
this.fields[field].value = this.values[field];
});
this.fields = { ...this.fields }; // Ensure rerender for pure components.
this.setState({ model });
}
componentWillMount() {
this.createFields();
this.setModel(this.getModelFromState());
}
componentDidMount() {
const { store } = this.context;
this.unsubscribe = store.subscribe(() => {
const newModel = this.getModelFromState();
if (newModel === this.state.model) return;
this.setModel(newModel);
});
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
return (
<WrappedComponent {...this.props} fields={this.fields} />
);
}
};
};
export default fields;

Categories

Resources