I need a bit of help.
I am new to react, so I have stuck here. I have shared a sandbox box link. That Contains a Table. as below
| Toy | Color Available | Cost Available |
Now everything works perfectly. But I want to save the data of the table as below
The detail state should contain a list of row values of the table and the columnsValues should contain the checkbox value of Color Available and Cost Available
Example:
this.state.detail like
detail: [
{
toy : ...
color : ...
cost : ...
}
{
toy : ...
color : ...
cost : ...
}
...
...
...
]
this.state.columnsValues like
columnsValues: {
color : boolean
cost : boolean
}
Any experts please help me out. I am struggling from past few hours.
Thank you.
Sandbox link: https://codesandbox.io/s/suspicious-microservice-qd3ku?file=/index.js
just paste this code it is working .
check your console you'll get your desired output .
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Table, Checkbox, Input } from "antd";
import { PlusCircleOutlined, MinusCircleOutlined } from "#ant-design/icons";
const { Column } = Table;
class ToyTable extends React.Component {
constructor(props) {
super(props);
this.state = {
dataSource: [
{
key: 0,
toy: "asdf",
color: "black",
cost: "23"
}
],
count: 0,
colorSwitch: false,
costSwitch: false,
columnsValues: {
color: true,
cost: true
},
detail: []
};
}
componentDidMount(){
const count = this.state.dataSource.length;
this.setState({
count
})
}
handleAdd = () => {
const { dataSource } = this.state;
let count = dataSource.length;
const newData = {
key: count,
toy: "",
color: "",
cost: ""
};
this.setState({
dataSource: [...dataSource, newData],
count
});
};
handleDelete = key => {
const dataSource = [...this.state.dataSource];
this.setState({ dataSource: dataSource.filter(item => item.key !== key) });
};
onChangecolor = (e, record) => {
let dataSource = this.state.dataSource;
let key = record.key;
dataSource[key].color = e.target.value;
this.setState({
dataSource
});
};
onChangeCost = (e, record) => {
let dataSource = this.state.dataSource;
let key = record.key;
dataSource[key].cost = e.target.value;
this.setState({
dataSource
});
};
onChangeToy = (e, record) => {
console.log("I am inside handleInputChange", e.target.value, record);
let dataSource = this.state.dataSource;
let key = record.key;
dataSource[key].toy = e.target.value;
this.setState({
dataSource
});
};
checkColor = e => {
this.setState({ colorSwitch: e.target.checked });
};
checkCost = e => {
this.setState({ costSwitch: e.target.checked });
};
render() {
const { dataSource } = this.state;
console.log(dataSource);
return (
<Table bordered pagination={false} dataSource={dataSource}>
<Column
title="Toy"
align="center"
key="toy"
dataIndex="toy"
render={(text, record) => (
<Input
component="input"
className="ant-input"
type="text"
value={record.toy}
onChange={e => this.onChangeToy(e, record)}
/>
)}
/>
<Column
title={() => (
<div className="row">
Color Available
<div className="col-md-5">
<Checkbox size="small" onChange={this.checkColor} />
</div>
</div>
)}
align="center"
dataIndex="color"
render={(text, record) => (
<Input
disabled={!this.state.colorSwitch}
value={record.color}
onChange={e => this.onChangecolor(e, record)}
component="input"
className="ant-input"
type="text"
/>
)}
/>
<Column
title={() => (
<div className="row">
Cost Available
<div className="col-md-5">
<Checkbox size="small" onChange={this.checkCost} />
</div>
</div>
)}
align="center"
dataIndex="color"
render={(text, record) => (
<Input
disabled={!this.state.costSwitch}
value={record.cost}
onChange={e => this.onChangeCost(e, record)}
component="input"
className="ant-input"
type="text"
/>
)}
/>
<Column
render={(text, record) =>
this.state.count !== 0 && record.key + 1 !== this.state.count ? (
<MinusCircleOutlined
onClick={() => this.handleDelete(record.key)}
/>
) : (
<PlusCircleOutlined onClick={this.handleAdd} />
)
}
/>
</Table>
);
}
}
ReactDOM.render(<ToyTable />, document.getElementById("container"));
This isn't an exact answer, but just as a general direction - you need something in the state to capture the values of the currently edited row contents, that you can then add to the final list. This is assuming once committed, you don't want to modify the final list.
Firstly, have an initial state that stores the values in the current row being edited
this.state = {
currentData: {
toy: '',
color: '',
..other props in the row
}
...other state variables like dataSource etc
}
Secondly, when the value in an input box is changed, you have to update the corresponding property in the currentData state variable. I see that you already have a handleInputChange function
For eg, for the input box corresponding to toy, you'd do
<input onChange={e => handleInputChange(e, 'toy')} ...other props />
and in the function itself, you'd update the currentData state variable, something like:
handleInputChange = (e, property) => {
const data = this.state.currentData
data[property] = e.target.value
this.setState({ currentData: data })
}
Finally, when you press add, in your handleAddFunction, you want to do two things:
1) use the currentData in state, that's been saving your current values and push them into the dataSource or details array
2) restore the currentData to the blank state, ready to track updates for the next row.
handleAdd = () => {
const { count, dataSource } = this.state;
const newData = {
key: count,
...this.state.newData,
};
this.setState({
dataSource: [...dataSource, newData],
count: count + 1,
currentData: {
toy: '',
// other default values
}
});
};
Related
I am doing a React search where user can add multiple filters. The idea is that at first there is only one filter (select and input field) and if user wishes to add more, he can add one more row of (select and input) and it will also take that into account.
I cannot figure out the part on how to add more rows of (select, input) and furthermore, how to read their data as the list size and everything can change.
So I have multiple options in the select array:
const options = [
{ label: "foo", value: 1 },
{ label: "bar", value: 2 },
{ label: "bin", value: 3 }
];
Now if user selects the first value from the Select box and then types a text in the input box I will get their values and I could do a search based on that.
const options = [
{ label: "foo", value: 1 },
{ label: "bar", value: 2 },
{ label: "bin", value: 3 }
];
class App extends React.Component {
state = {
selectedOption: null,
textValue: null
};
handleOptionChange = selectedOption => {
this.setState({ selectedOption: selectedOption.value });
};
handleTextChange = event => {
this.setState({ textValue: event.target.value });
};
handleSubmit = () => {
console.log(
"SelectedOption: " +
this.state.selectedOption +
", textValue: " +
this.state.textValue
);
};
addNewRow = () => {
console.log("adding new row of filters");
};
render() {
const { selectedOption } = this.state;
return (
<div>
<div style={{ display: "flex" }}>
<Select
value={selectedOption}
onChange={this.handleOptionChange}
options={options}
/>
<input
type="text"
value={this.state.textValue}
onChange={this.handleTextChange}
/>
</div>
<button onClick={this.addNewRow}>AddNewRow</button>
<button onClick={this.handleSubmit}>Submit</button>
</div>
);
}
}
export default App;
I have also created a CodeSandBox for this.
If user clicks on the addNewRow a new row should appear and the previous (search, input) should be selectable without the row that was previously selected.
I don't even really know how I should approach this.
To add new row of inputs on click of button you need to add new input item into the list of inputs, like I have mention below::
import React, { Component } from 'react'
import Select from "react-select";
const options = [
{ label: "foo", value: 1 },
{ label: "bar", value: 2 },
{ label: "bin", value: 3 }
];
class App extends Component {
constructor(props) {
super(props);
this.state = { inputGroups: ['input-0'] };
}
handleSubmit = () => {
console.log("form submitted");
};
AddNewRow() {
var newInput = `input-${this.state.inputGroups.length}`;
this.setState(prevState => ({ inputGroups: prevState.inputGroups.concat([newInput]) }));
}
render() {
return (
<div>
<div>
<div>
{this.state.inputGroups.map(input =>
<div key={input} style={{ display: "flex" }}>
<Select
options={options}
/>
<input
type="text"
// value={this.state.textValue}
// onChange={this.handleTextChange}
/>
</div>
)}
</div>
</div>
<button onClick={() => this.AddNewRow()}>AddNewRow</button>
<button onClick={this.handleSubmit()}>Submit</button>
</div>
);
}
}
export default App;
After click on "AddNewRow" button it will add new input group for you. Now you need to wrap this inputGroup inside "Form" to get data of each inputGroup on click of submit.
I hope it will resolve your issue.
import { CircularProgress, FormControl, Input, InputLabel } from
'#material-ui/core';
function toKey(s) {
return s.split("_").map((s, i) => i > 0 ? s.slice(0,1).toUpperCase() +
s.slice(1, s.length) : s).join("")
}
Function to split the returned json object:
function toLabel(s) {
return s.split("_").map((s, i) => s.slice(0,1).toUpperCase() +
s.slice(1, s.length)).join(" ")
}
My class:
class Reports extends Component {
constructor(props) {
super(props);
this.state = {
report: '',
filename: 'my-data.csv',
isLoading: false,
tableHeaderData: [],
reports: [
{ name: 'C3 Report', id: 1, actOn: 'c3'},
{ name: 'C4 Report', id: 2, actOn: 'c4'},
{ name: 'C5 Report', id: 3, actOn: 'c5'}
],
categories: {name: 'Cat 1'},
catSelection: 'Select a Category',
repSelection: 'Select Report Type',
isReportSelected: false,
c4RptFirstInput: '',
c4RptSecondInput: ''
}
}
Not sure about this but went with convention:
componentDidMount () {
const {dispatch, id} = this.props;
}
handleChange (e) {
// this.setState({ input: e.target.value });
}
This is the plugin that I'm using to convert the page into a csv file:
csvHeader () {
const data = this.reportData()
if(data.length === 0) return []
const keys = Object.keys(data[0])
return keys.map((k) => {
const label = toLabel(k)
const key = toKey(k)
return { label, key }
})
}
csvData () {
const data = this.reportData()
if(data.length === 0) return []
const values = Object.entries(data);
const keys = Object.keys(data[0])
const rows = values.map(entries => {
const record = entries[1];
return keys.reduce((acc, key, i) => {
acc[toKey(key)] = record[key]
return acc
}, {})
});
return rows
}
Checks if report or package:
reportData(){
switch(this.state.report) {
case 'channels':
return this.props.channels
case 'packages':
return this.props.packages
default:
return []
}
}
Not sure about this placeholder function but copied it from somewhere:
placeholder () {
return (
<div>
<h1 className="display-3">Reports</h1>
<p className="lead" cursor="pointer" onClick=
{this.loadChannelData}>Svc Configuration</p>
</div>
);
}
Was experimenting with this function but wasn't sure how to use it:
componentWillReceiveProps() {
}
handleCategorySwitch = (e) => {
const name = e.target.name;
const value = e.target.value;
this.setState({ [name]: value});
console.log(`name ${name}, value ${value}`);
}
This is where the 'subselection' of the second set of drop downs happens:
handleSubselection = (e) => {
this.setState({c4RptSecondInput: e.target.value, })
switch( e.target.value) {
case 'input3':
return this.props.ReportGetAllPackages()
}
}
handleReportSwitch = (e) => {
const selectedAction = e.target.value;
if (selectedAction == 'c3') {
this.setState(prevState => ({
report: 'channels'
,isLoading: true
}), this.props.ReportGetAllChannels)
}
if (selectedAction == 'c4') {
this.setState(prevState => ({
report: 'packages'
}))
}
}
Render function:
render () {
const {filename, reports, catSelection, repSelection, isReportSelected,
c4RptFirstInput, c4RptSecondInput} = this.state;
return (
<div className="reports">
{this.placeholder()}
<div className="flexMode">
<span className="spanFlexMode">
<InputLabel htmlFor="catSelection"></InputLabel>
<Select value={catSelection} name={'catSelection'}
onChange={(e) => this.handleCategorySwitch(e)}>
<MenuItem value="select">Select Category</MenuItem>
<MenuItem value={'Cat1'}>Cat 1</MenuItem>
<MenuItem value={'Cat2'}>Cat 2 </MenuItem>
<MenuItem value={'Cat3'}>Cat 3 </MenuItem>
</Select>
</span>
<span className="spanFlexMode">
<label>Report Name:</label>
<Select value={repSelection} name="repSelection"
onChange={(e) => this.handleReportSwitch(e)}>
<MenuItem defaultValue={'select'}>Select
Report</MenuItem>
{reports && reports.map((report, index) => <MenuItem
key={index} value={report.actOn}>{report.name}</MenuItem>)}
</Select>
</span>
</div>
Below are the second set of drop downs that show up conditionally based on selection of a particular field from above select boxes:
{ this.state.report === 'packages' ? (
<div>
<span>
<label>Input 1:</label>
<Select name="c4RptFirstInput" value={c4RptFirstInput}
placeholder={'Select Provider'} onChange={(e) =>
this.handleSubselection(e)}>
<MenuItem value={'Def'}>Select</MenuItem>
<MenuItem value={'Provider'}>Provider</MenuItem>
<MenuItem value={'Region'}>Region</MenuItem>
<MenuItem value={'Zone'}>Zone</MenuItem>
</Select>
</span>
<span className="spanFlexMode">
<label>Input 2:</label>
<Select name="c4RptSecondInput" defaultValue=
{c4RptSecondInput} value={c4RptSecondInput} onChange={(e) =>
this.handleSubselection(e)}>
<MenuItem value={'Def'}>Select</MenuItem>
<MenuItem value={'input2'}>Input 2</MenuItem>
<MenuItem value={'input3'}>Input 3</MenuItem>
<MenuItem value={'input4'}>Input 4</MenuItem>
</Select>
</span>
</div>
) : null}
<div>
<CSVLink data={this.csvData()} headers={this.csvHeader()}
filename={filename} target={'_blank'}>
<GetAppIcon />
</CSVLink>
Here is where the spinning loader should do it's thing and disappear once the data is loaded - currently it just keeps on spinning and the data never gets loaded even though I can see that the data has successfully come back from the reducer:
{isLoading
? <CircularProgress />
: (
<Table id="t1">
<TableHeaders data={this.csvHeader()} />
<TableContent data={this.csvData()} />
</Table>
)}
</div>
</div>
)
}
}
const mapDispatchToProps = dispatch => {
return {
ReportGetAllChannels: () => dispatch(ReportGetAllChannels()),
ReportGetAllPackages: () => dispatch(ReportGetAllPackages()),
}
}
const defaultState = ({
state: {},
channels: [],
packages: []
,isLoading: false
})
const mapStateToProps = (state=defaultState) => {
return ({
state: state,
channels: state.RptDetailsReducer.data,
packages: state.RptPackagesReducer.data
,isLoading: false
})
}
isLoading variable is not defined in your render method. I see that you defined it in your component's state and inside your reducer. I assume you are referencing one in your state (Since you said it was keep spinning it is probably the case). You set component's isLoading to true in handleSubselection you have this snippet:
if (selectedAction == 'c3') {
this.setState(prevState => ({
report: 'channels',
isLoading: true
}), this.props.ReportGetAllChannels)
}
This code will set isLoading to true than dispatch ReportGetAllChannels. However your component's state won't be updated. I don't know what ReportGetAllChannels does but I am guessing it sets its own isLoading to false. Which is different variable.
Also you may want to read this https://overreacted.io/writing-resilient-components/#principle-1-dont-stop-the-data-flow. Once you map your state to props you usually want to pass them directly to child components.
Edit:
Quick fix: use this.props.isLoading instead of state, and set isLoading to true inside your dispatched action
I have this pseudo code for my form. Where I would like to display just fields with canAccess=true.
const initialValues = {
firstName: { canAccess: true, value: 'Mary' },
surName: { canAccess: false, value: 'Casablanca' }
}
<Form initialValues={initialValues}>
{props =>
<>
<div className="nestedItem">
<Field name="firstName" />
</div>
<Field name="surName" />
</>
}
</Form>
With this code I would like to see rendered just field with firstName.
I know that I can iterate through React.Children.map() but I don't know how to iterate children when using render props.
Also there can be nested elements, so I would like to find specific type of component by name.
Thanks for help.
const initialValues = {
firstName: { canAccess: true, value: 'Mary' },
surName: { canAccess: false, value: 'Casablanca' }
}
<Form initialValues={initialValues}>
{props =>
<>
{
Object.keys(props.initialValues).map(k => (
k.canAccess && <Field name={k} />
));
}
</>
}
</Form>
Edit: Your form can perform some logic and pass back filtered items to your component.
getFilteredItems = items => Object.keys(items).reduce((acc, key) => {
const item = items[key];
const { canAccess } = item;
if(!canAccess) return acc;
return {
...acc,
[key]: item
}
}, {}));
render() {
const { initialValues, children } = this.props;
const filteredItems = this.getFilteredItems(initialValues);
return children(filteredItems);
}
<Form initialValues={initialValues}>
{ props =>
<>
{
Object.keys(props).map(k => <Field name={k} />)
}
</>
</Form>
This is what I was looking for.
const Form = ({initialValues, children}) =>
props =>
<Authorized initialValues={initialValues}>
{typeof children === 'function' ? children(props) : children}
</Authorized>
const Authorized = ({initialValues, children}) => {
// Do check
React.Children.map(chidlren, x => {
if(x.type === Field ) // && initialValues contains x.props.name
return x
return null
... })
}
I use react-table npm package and i store all the data required by the table in the state
componentDidMount() {
this.props.client
.query({
query: ALL_SKUS
})
.then(({ data }) => {
const skus = removeTypename(data.allSkuTypes);
const newData = skus.map((sku, index) => ({
serial: index + 1,
...sku
}));
this.setState({ data: newData });
})
.catch(err => {
console.log(err);
});
}
this is how the 'name' field of my column looks like
{
Header: 'SKU Name',
headerClassName: 'vt-table-header',
accessor: 'name',
maxWidth: 350,
Cell: this.renderEditable
},
where this is the event handler
renderEditable = ({ index, column }) => {
const { data } = this.state;
return (
<InputGroup
onChange={e => {
const newData = [...data];
newData[index][column.id] = e.target.value;
this.setState({ data: newData });
}}
value={data[index][column.id]}
/>
);
};
finally this is how all that data goes in the react table
<ReactTable
loading={data.length === 0 ? true : false}
showPagination={false}
className="mt-3 text-center"
data={data}
columns={columns}
/>
I have tried removing the value attribute from the Input and then added an onBlur to it while that solved the performance issue it was enable to fetch the data from the query initially.
I am also facing this issue in many complex forms in my application any help will be highly appreciated
Here is an idea.
instead of doing this, which provides a component InputGroup for your Cells in the table as I suppose:
renderEditable = ({ index, column }) => {
const { data } = this.state;
return (
<InputGroup
onChange={e => {
const newData = [...data];
newData[index][column.id] = e.target.value;
this.setState({ data: newData });
}}
value={data[index][column.id]}
/>
);
};
What about creating a separate component that controlls it's own state, instead of changing the whole table state for each cell change, why not have a component CELL which changes it's own state with any other possible POST requests ...etc.
This will narrow-down the rendering to Cell based instead of the whole Table gets rendered.
It will almost be the same:
class Cell extends React.Component {
state = { ...yourCellData }
componentDidMount() {
this.setState({ ...this.props.youCellData }); //any properties passed from the main component.
}
render(){
const { index, column } = this.props;
return (
<InputGroup
onChange={e => {
const newData = [...this.state.data];
newData[index][column.id] = e.target.value;
this.setState({ data: newData }); //this now modifies the Cell data instead of the whole table
}}
value={data[index][column.id]}
/>
);
}
}
Applicable possible as:
{
Header: 'SKU Name',
headerClassName: 'vt-table-header',
accessor: 'name',
maxWidth: 350,
Cell: (index, column) => <YourNewCellComponent index={index} column={column} />
},
I hope this somehow provides you of an overall of an approach that might solve the problem
I'm fairly new to react. What I'm trying to do is to delete a component from an array given an index when the "[x]" button is clicked.
From what I understood react re-renders the Component whenever the state has changed.
The labels variable on the render() method is an array of components and also has other child components. When the user clicks the [x] on one of the components, it would pass the index of that to the class function closeLabelHandler() and assign the state objects, indexToDelete and isDeletingLabel to the index of the element and true, respectively.
Since I am changing a state object, I was thinking that this during the re-render an element would be deleted from the labels array but alas, it doesn't.
Here's my code:
import React, { Component } from 'react';
import "./ImageContainer.css";
import { Stage, Layer, Image, Tag, Text, Label } from 'react-konva';
import ToolsContainer from '../ToolsContainer/ToolsContainer';
class ImageContainer extends Component {
state = {
showImageContainer: true,
canvasHeight: 0,
canvasWidth: 0,
canvasImageUrl: "",
image: null,
commentsArray: [],
labelCount: 0,
isDeletingLabel: false,
indexToDelete: -1
}
componentDidMount() {
let stage = this.refs.stage
const imgObj = new window.Image();
imgObj.src = this.props.selectedImageURI
imgObj.onload = () => {
this.setState({ image: imgObj })
this.setState({ canvasWidth: imgObj.width, canvasHeight: imgObj.height });
let canvasImageUrl = stage.toDataURL();
this.setState({ canvasImageUrl: canvasImageUrl })
}
}
getLabelCount = (count) => {
this.setState({ labelCount: count })
}
displayContainerHandler = () => {
this.props.showImageContainer(!this.state.showImageContainer)
}
downloadImageHandler = () => {
let a = document.createElement('a');
a.href = this.state.canvasImageUrl.replace("image/png", "image/octet-stream");
a.download = 'shot.png';
a.click();
}
closeLabelHandler = (index) => {
this.setState({
isDeletingLabel: true,
indexToDelete: index
})
console.log("[closeLabelHandler] " + index)
}
render() {
console.log("[ImageContainer][render]" + this.state.commentsArray)
let labels = [];
for (let i = 0; i < this.state.labelCount; i++) {
labels.push(
<Label key={i} draggable={true} x={150 + i * 2} y={150 + i * 2} >
<Tag
fill="black"
pointerWidth={10}
pointerHeight={10}
lineJoin='round'
shadowColor='black'
/>
<Text
text="Insert Comment Here"
fontFamily='Calibri'
fontSize={18}
padding={5}
fill='white'
/>
<Text
text="[x]"
fontFamily='Calibri'
fontSize={18}
x={170}
fill='black'
onClick={() => this.closeLabelHandler(i)}
/>
</Label>
)
if (this.state.isDeletingLabel) {
labels.splice(this.state.indexToDelete, 1)
this.setState({
isDeletingLabel: false,
indexToDelete: -1
})
}
}
return (
<div className="ImageContainer" >
<button className="ImageContainer-close-button " onClick={this.displayContainerHandler}>[x]</button>
<Stage ref="stage" height={this.state.canvasHeight * .5} width={this.state.canvasWidth * .5} >
<Layer>
<Image image={this.state.image} scaleX={0.5} scaleY={0.5} />
{labels}
</Layer>
</Stage>
<ToolsContainer getLabelCount={count => this.getLabelCount(count)} />
<button className="pure-button" onClick={() => this.downloadImageHandler()}>Download</button>
</div>
)
}
}
Any help would be appreciated.
This is a simple way to achive what you want. You should not change your state directly, it could cause inconsistency to your application.
import React, { Component } from "react";
class Books extends Component {
state = {
books: [{ id: 1, title: "Book 1" }, { id: 2, title: "Book 2" }]
};
handleDelete = book => {
const books = this.state.books.filter(b => b.id !== book.id);
this.setState({ books });
};
render() {
return (
<ul>
{this.state.books.map(book => (
<li key={book.id}>
{book.title}{" "}
<button onClick={() => this.handleDelete(book)}>x</button>
</li>
))}
</ul>
);
}
}
export default Books;