AntD Tables and Input fields per Card - reactjs

I have a cards with tables inside but once an edit button is clicked, some of the columns of the tables will be input fields.
But I'm having a problem with my code. I'm getting same value from input change of the field.
Here's my code
EditableTable.js
function EditableTable({
columns,
dataSource,
pagination = false,
hasEdit = false,
renderColumn,
}) {
const footer = () => (
<table>
<thead className="ant-table-thead">
<tr>
<td colSpan="1" className="ant-table-cell">
<span>Total</span>
</td>
</tr>
</thead>
</table>
);
const tableColumns = () => {
if (hasEdit) {
return columns.map(column => {
if (column.hasFields) {
return {
title: column.title,
dataIndex: column.dataIndex,
key: column.key,
render: (text, record, index) =>
renderColumn(text, record, index, column.key),
};
}
return column;
});
}
return columns;
};
return (
<div>
<Table
columns={tableColumns()}
dataSource={dataSource}
pagination={pagination}
footer={footer}
/>
</div>
);
}
Here's where I call my component
const [formFields, setFormFields] = useState({
targets: ['', ''],
achievements: ['', ''],
ratings: ['', ''],
});
const dataSource = [
{
key: 0,
title: 'I. Operational Excellence',
hasEdit: true,
listing: [
{
key: '0',
competency: 'Edward King 0',
target: '',
achievement: '',
weight: '',
rating: '',
remarks: '',
score: '',
},
{
key: '1',
competency: 'Edward King 1',
target: '',
achievement: '',
weight: '',
rating: '',
remarks: '',
score: '',
},
],
},
{
key: 1,
title: 'II. Financial Contribution',
hasEdit: true,
listing: [
{
key: '0',
competency: 'Edward King 0',
target: '',
achievement: '',
weight: '',
rating: '',
remarks: '',
score: '',
},
{
key: '1',
competency: 'Edward King 1',
target: '',
achievement: '',
weight: '',
rating: '',
remarks: '',
score: '',
},
],
},
];
const handleChange = (e, key, index) => {
const fields = formFields;
fields[`${key}s`][index] = e.target.value;
setFormFields({ ...fields });
};
const renderColumn = (text, record, index, key) => {
if (isEdit) {
return (
<Form.Item>
{getFieldDecorator(`${key}-${index}`, {
rules: [
{
required: true,
},
],
})(
<Input
placeholder="Input text"
onChange={e => handleChange(e, key, index)}
/>,
)}
</Form.Item>
);
}
return formFields.targets && formFields.targets[index];
};
{ dataSource.map(item => (
<Card key={item.key} className="datatable" title={item.title}>
<Form onSubmit={handleSave}>
<EditableTable
hasEdit={item.hasEdit}
dataSource={item.listing}
columns={columns}
renderColumn={(text, record, index, key) =>
renderColumn(text, record, index, key)
}
/>
</Form>
</Card>
))}

You have to adapt your dataSource if you want to persist the changes.
Just have a look at the example in Antd Table docs. The depending Codesandbox has a full featured running version.
Code from the CodeSandbox to handle an input change:
handleSave = row => {
const newData = [...this.state.dataSource];
const index = newData.findIndex(item => row.key === item.key);
const item = newData[index];
newData.splice(index, 1, { ...item, ...row });
this.setState({
dataSource: newData,
});
};

Related

Why is my input field losing focus when typing a character?

I have a form that have several input fields and for some reason my component y re-rendering everytime y change the value of my input field which produces to the input to lose focus.
ContactForm.js:
const ContactForm = () => {
const [values, setValues ] = useState({
name: '',
lastname: '',
email: '',
confirmEmail: '',
message: ''
});
const inputs = [
{
id: Math.random(),
name: 'name',
type: 'text',
placeholder: 'Name'
},
{
id: Math.random(),
name: 'lastname',
type: 'text',
placeholder: 'Last Name'
},
{
id: Math.random(),
name: 'email',
type: 'email',
placeholder: 'Email'
},
{
id: Math.random(),
name: 'confirmEmail',
type: 'email',
placeholder: 'Confirm Email'
},
{
id: Math.random(),
name: 'message',
type: 'text',
placeholder: 'Message'
}
]
const handleSubmit = (e) => {
e.preventDefault();
}
MY child component, FormInput.js:
import React from 'react'
import './FormInput.css';
/* import { Input } from '../atoms/Input'; */
const FormInput = (props) => {
const { id, onChange, ...inputProps } = props;
return (
<div className='formInput'>
{/* <label htmlFor="">Username</label> */}
{/* <Input {...inputProps} onChange={onChange}/> */}
<input {...inputProps} onChange={onChange} />
</div>
)
}
export default FormInput
const onChange = (e) => {
setValues({...values, [e.target.name]: e.target.value});
}
console.log(values);
return (
<form className='contactForm' onSubmit={handleSubmit}>
{inputs.map((input) => (
<FormInput
key={input.id}
{...input}
value={values[input.name]}
onChange={onChange}
/>
))}
<SubmitBtn/>
</form>
)
}
So is there a solution for this, so that my input field doesn´t lose focus after re-rendering? Or should i prevent re-rendering?
you have 3 options here.
move the input array outside of the component so that it is always the same on every iteration. But if you are fetching this from the server, that is not possible.
you can use a useMemo hook on the input array and make sure to pass an empty array as a dependency array.
remove the Math.random function and maybe use a unique id from the server or for the time being you can use the array index (even though it is not advisable).
I have created a small POC. if you remove the useMemo, the input(s) will lose their focus on every re-render.
Following is the code:
import * as React from 'react';
import './style.css';
export default function App() {
const inputs = React.useMemo(
() => [
{
id: Math.random(),
name: 'name',
type: 'text',
placeholder: 'Name',
},
{
id: Math.random(),
name: 'lastname',
type: 'text',
placeholder: 'Last Name',
},
{
id: Math.random(),
name: 'email',
type: 'email',
placeholder: 'Email',
},
{
id: Math.random(),
name: 'confirmEmail',
type: 'email',
placeholder: 'Confirm Email',
},
{
id: Math.random(),
name: 'message',
type: 'text',
placeholder: 'Message',
},
],
[]
);
const [state, setState] = React.useState({
name: '',
email: '',
message: '',
confirmEmail: '',
lastname: '',
});
const handleChange = (e: any) => {
const value = (e.target as HTMLInputElement).value;
const name = (e.target as HTMLInputElement).name;
setState({
...state,
[name]: value,
});
};
const handleSubmit = () => {
console.log('state', state);
};
return (
<div>
{inputs.map((item) => (
<div key={item.id}>
<label>{item.name}: </label>
<input
name={item.name}
onChange={handleChange}
placeholder={item.placeholder}
/>
</div>
))}
<button onClick={handleSubmit}>Submit</button>
</div>
);
}
It's probably because you are calling Math.random in the body of the ContactForm component. You should never call Math.random() during rendering.
In your case, you can probably move the const inputs to outside the component.

How to show length of filtered items using react redux. Im getting data using list. Using list how can i get the length of filtered items length

This is shop page.jsx file here i want to show the list of filtered items at the top of the page. Here im using data list for getting all the details. Here i want to show length of filtered products at showing 9 0f 9 products.
import React, { useEffect, useState } from 'react';
import EmptyView from '../components/common/EmptyView';
import FilterPanel from '../components/Home/FilterPanel';
import List from './Home/List';
// import SearchBar from '../../components/Home/SearchBar';
import { dataList } from '../constants';
// import './styles.css';
import '../App.css';
import ButtonAppBar from "./Header";
const Shop = () => {
// const [selectedCategory, setSelectedCategory] = useState(null);
// const [selectedRating, setSelectedRating] = useState(null);
const [selectedPrice, setSelectedPrice] = useState([0, 150]);
const [cuisines, setCuisines] = useState([
{ id: 1, checked: false, label: 'Furniture' },
{ id: 2, checked: false, label: 'Decoration' },
{ id: 3, checked: false, label: 'Bedding' },
{ id: 4, checked: false, label: 'Lighting' },
{ id: 5, checked: false, label: 'Bath&Shower' },
{ id: 6, checked: false, label: 'Curtains' },
{ id: 7, checked: false, label: 'Toys' },
]);
const [brand, setBrand] = useState([
{ id: 1, checked: false, label: 'Poliform' },
{ id: 2, checked: false, label: 'Rochie Bobois' },
{ id: 3, checked: false, label: 'Edra' },
{ id: 4, checked: false, label: 'Kartell' },
]);
const [availability, setAvailability] = useState([
{ id: 1, checked: false, label: 'Onstock' },
{ id: 2, checked: false, label: 'Outofstock' },
]);
const [list, setList] = useState(dataList);
const [resultsFound, setResultsFound] = useState(true);
// const [searchInput, setSearchInput] = useState('');
// const handleSelectCategory = (event, value) =>
// !value ? null : setSelectedCategory(value);
// const handleSelectRating = (event, value) =>
// !value ? null : setSelectedRating(value);
const handleChangeChecked = (id) => {
const cusinesStateList = cuisines;
const changeCheckedCuisines = cusinesStateList.map((item) =>
item.id === id ? { ...item, checked: !item.checked } : item
);
setCuisines(changeCheckedCuisines);
};
const handleChangeCheckeds = (id) => {
const brandStateList = brand;
const changeCheckedsBrand = brandStateList.map((item) =>
item.id === id ? { ...item, checked: !item.checked } : item
);
setBrand(changeCheckedsBrand);
};
const handleChangeCheckedss = (id) => {
const availabilityStateList = availability;
const changeCheckedssAvailability = availabilityStateList.map((item) =>
item.id === id ? { ...item, checked: !item.checked } : item
);
setAvailability(changeCheckedssAvailability);
};
const handleChangePrice = (event, value) => {
setSelectedPrice(value);
};
const applyFilters = () => {
let updatedList = dataList;
// // Rating Filter
// if (selectedRating) {
// updatedList = updatedList.filter(
// (item) => parseInt(item.rating) === parseInt(selectedRating)
// );
// }
// // Category Filter
// if (selectedCategory) {
// updatedList = updatedList.filter(
// (item) => item.category === selectedCategory
// );
// }
// Cuisine Filter
const cuisinesChecked = cuisines
.filter((item) => item.checked)
.map((item) => item.label.toLowerCase());
if (cuisinesChecked.length) {
updatedList = updatedList.filter((item) =>
cuisinesChecked.includes(item.cuisine)
);
}
// brand filter
const brandChecked = brand
.filter((item) => item.checked)
.map((item) => item.label.toLowerCase());
if (brandChecked.length) {
updatedList = updatedList.filter((item) =>
brandChecked.includes(item.brand)
);
}
// availabilty filter
const availabilityChecked = availability
.filter((item) => item.checked)
.map((item) => item.label.toLowerCase());
if (availabilityChecked.length) {
updatedList = updatedList.filter((item) =>
availabilityChecked.includes(item.availability)
);
}
// // Search Filter
// if (searchInput) {
// updatedList = updatedList.filter(
// (item) =>
// item.title.toLowerCase().search(searchInput.toLowerCase().trim()) !==
// -1
// );
// }
// // Price Filter
const minPrice = selectedPrice[0];
const maxPrice = selectedPrice[1];
updatedList = updatedList.filter(
(item) => item.price >= minPrice && item.price <= maxPrice
);
setList(updatedList);
!updatedList.length ? setResultsFound(false) : setResultsFound(true);
};
useEffect(() => {
applyFilters();
}, [cuisines, brand, availability, selectedPrice]);
return (
<div>
<ButtonAppBar />
<div className='home'>
{/* Search Bar */}
{/* <SearchBar
value={searchInput}
changeInput={(e) => setSearchInput(e.target.value)}
/> */}
<br /><br /><br /> <div className='home_panelList-wrap'>
{/* Filter Panel */}
<div className='home_panel-wrap'>
<FilterPanel
// selectedCategory={selectedCategory}
// selectCategory={handleSelectCategory}
// selectedRating={selectedRating}
selectedPrice={selectedPrice}
// selectRating={handleSelectRating}
cuisines={cuisines}
changeChecked={handleChangeChecked}
brand={brand}
changeCheckeds={handleChangeCheckeds}
availability={availability}
changeCheckedss={handleChangeCheckedss}
changePrice={handleChangePrice}
/>
</div>
{/* List & Empty View */}
<div className='home_list-wrap'>
<h6>Showing
<span style={{ color: "#bd744c" }}><b>{dataList.length}</b></span> of
<span style={{ color: "#bd744c" }}><b>9</b></span>
Products</h6>
{resultsFound ? <List list={list} /> : <EmptyView />}
</div>
</div>
</div>
</div>
);
};
export default Shop;
This is constant.js file from here we are getting all our details in shop.jsx file.
export const dataList = [
{
id: 1,
title: 'AwesomeLamp',
cuisine: 'lighting',
price: 40,
image: '/images/AwesomeLamp.png',
brand: 'poliform',
availability: 'onstock',
name: 'AwesomeLamp',
tagName: 'AwesomeLamp'
},
{
id: 2,
title: 'CozySofa',
cuisine: 'furniture',
price: 150,
image: '/images/CozySofa.png',
brand: 'edra',
availability: 'outofstock',
name: 'CozySofa',
tagName: 'CozySofa'
},
{
id: 3,
title: 'AwesomeCandle',
cuisine: 'lighting',
price: 15,
image: '/images/AwesomeCandle.png',
brand: 'kartell',
availability: 'onstock',
name: 'AwesomeCandle',
tagName: 'AwesomeCandle',
},
{
id: 4,
title: 'FancyChair',
cuisine: 'furniture',
price: 70,
image: '/images/FancyChair.png',
brand: 'poliform',
availability: 'outofstock',
name: 'FancyChair',
tagName: 'FancyChair'
},
{
id: 5,
title: 'ChineseTeapot',
cuisine: 'decoration',
price: 50,
image: '/images/ChineseTeapot.png',
brand: 'rochie bobois',
availability: 'onstock',
name: 'ChineseTeapot',
tagName: 'ChineseTeapot'
},
{
id: 6,
title: 'SoftPillow',
cuisine: 'bedding',
price: 30,
image: '/images/SoftPillow.png',
brand: 'edra',
availability: 'onstock',
name: 'SoftPillow',
tagName: 'SoftPillow'
},
{
id: 7,
title: 'WoodenCasket',
cuisine: 'decoration',
price: 20,
image: '/images/WoodenCasket.png',
brand: 'kartell',
availability: 'onstock',
name: 'WoodenCasket',
tagName: 'WoodenCasket'
},
{
id: 8,
title: 'AwesomeArmChair',
cuisine: 'furniture',
price: 90,
image: '/images/AwesomeArmChair.png',
brand: 'poliform',
availability: 'onstock',
name: 'AwesomeArmchair',
tagName: 'AwesomeArmchair'
},
{
id: 9,
title: 'CoolFlower',
cuisine: 'decoration',
price: 20,
image: '/images/CoolFlower.png',
brand: 'none',
availability: 'onstock',
name: 'CoolFlower',
tagName: 'CoolFlower'
},
];

useState hook losing map data on page refresh

I am using useState hook for my add product form.
When I refresh my page, data is not displaying for the field category (I am trying to display categories, so the user can select category from the list to create a product for that category). But! Data keeping in redux store:
enter image description here
It only shows when I go to another page using react router() and then go back.
This is my code:
export const AddProduct = () => {
const dispatch = useDispatch();
const userId = useSelector(state => state.auth.userId);
const categories = useSelector(state => state.categories.categories);
const [avatar, setAvatar] = React.useState('');
React.useEffect(() => {
dispatch(categoriesActions.fetchCategories());
}, [dispatch]);
const [orderForm, setOrderForm] = React.useState({
title: {
elementType: 'input',
label: 'Title',
elementConfig: {
type: 'text',
placeholder: 'Title'
},
value: '',
validation: {
required: true
},
valid: false,
touched: false
},
price: {
elementType: 'input',
label: 'Price',
elementConfig: {
type: 'text',
placeholder: 'Price'
},
value: '',
validation: {
required: true
},
valid: false,
touched: false
},
description: {
elementType: 'textarea',
label: 'Description',
elementConfig: {
type: 'text',
placeholder: 'Description'
},
value: '',
validation: {
required: true
},
valid: false,
touched: false
},
category: {
elementType: 'select',
label: 'Select category',
elementConfig:
categories.map(category => (
<option key={category._id} value={category.title}>
{category.title}
</option>))
,
value: '',
validation: {
required: true
},
valid: false,
touched: false
},
});
const [formIsValid, setFormIsValid] = React.useState(false);
const addProductData = event => {
event.preventDefault();
const formData = {};
for (let formElementIdentifier in orderForm) {
formData[formElementIdentifier] = orderForm[formElementIdentifier].value;
}
const product = {
userId: userId,
title: formData.title,
price: formData.price,
description: formData.description,
category: formData.category
}
dispatch(productsActions.addProduct(product));
}
const inputChangedHandler = (event, inputIdentifier) => {
const updatedFormElement = updateObject(orderForm[inputIdentifier], {
value: event.target.value,
valid: checkValidity(
event.target.value,
orderForm[inputIdentifier].validation
),
touched: true
});
const updatedOrderForm = updateObject(orderForm, {
[inputIdentifier]: updatedFormElement
});
let formIsValid = true;
for (let inputIdentifier in updatedOrderForm) {
formIsValid = updatedOrderForm[inputIdentifier].valid && formIsValid;
}
setOrderForm(updatedOrderForm);
setFormIsValid(formIsValid);
};
const formElementsArray = [];
for (let key in orderForm) {
formElementsArray.push({
id: key,
config: orderForm[key]
});
}
let form = (
<form onSubmit={addProductData}>
{formElementsArray.map(formElement => (
<Input
key={formElement.id}
elementType={formElement.config.elementType}
elementConfig={formElement.config.elementConfig}
value={formElement.config.value}
label={formElement.config.label}
hint={formElement.config.hint}
invalid={!formElement.config.valid}
shouldValidate={formElement.config.validation}
touched={formElement.config.touched}
changed={event => inputChangedHandler(event, formElement.id)}
/>
))}
<Button btnType="Success" disabled={!formIsValid}>ORDER</Button>
</form>
)
return (
<div class="wrapper">
<Header />
<article class="main">
<div class="row">
<div class="item--1-4 image-block">
<div class="product-image-group">
<img class="product-image-big" src={`/${avatar}`} />
<hr class="border-divider" />
<input type="file" onChange={e => setAvatar(e.target.files[0].name)} name="imageUrl" id="imageUrl" />
</div>
</div>
<div class="item--3-4">
<div class="item-title">
<h3>Add product</h3>
<hr class="border-divider" />
</div>
{form}
</div>
</div>
</article>
<LeftMenu />
</div>
)
}
This is line in my code related to that select field:
elementConfig:
categories.map(category => (
<option key={category._id} value={category.title}>
{category.title}
</option>))
That's because the first time that component loads, there is nothing in the categories and when the categories are set, you're not setting the orderForm data again. this is called stale props you need to do this:
useEffect(() => {
setOrderForm((oldValue) => ({
...oldValue,
category: {
...oldValue.category,
elementConfig: categories.map((category) => (
<option key={category._id} value={category.title}>
{category.title}
</option>
)),
},
}));
}, [categories])
This way every time categories data is changed you changing the orderForm state accordingly

Passing information by a form to 3 separate components using MongoDB and React

I am building a website for a comic shop and I am struggling to pass information to 3 different components (back issues, new comics, trades) via a form. Currently I can post data to back issues, however I would like to pass similar information from a form to each of the components depending on whether it is a back issues, new comics or a trade.
I have tried adding product actions for new comics and trades but still don't understand how these can be recognised in a form
What do I need to do so that the user can select on the form one of these options and the data is then correctly posted to the correct component?
My production action for this thus far is:
export function getProductsToShop(skip, limit, filters = []){
const data ={limit, skip, filters}
const request = axios.post(`${PRODUCT_SERVER}/Shop/back_issues`, data)
.then(response => {
return {
size: response.data.size,
articles: response.data.articles
}
});
return {
type: GET_PRODUCTS_TO_SHOP,
payload: request
}
}
export function getProductsToShop(skip, limit, filters = []){
const data ={limit, skip, filters}
const request = axios.post(`${PRODUCT_SERVER}/Shop/new_comics`, data)
.then(response => {
return {
size: response.data.size,
articles: response.data.articles
}
});
return {
type: GET_PRODUCTS_TO_SHOP,
payload: request
}
}
export function getProductsToShop(skip, limit, filters = []){
const data ={limit, skip, filters}
const request = axios.post(`${PRODUCT_SERVER}/Shop/trades`, data)
.then(response => {
return {
size: response.data.size,
articles: response.data.articles
}
});
return {
type: GET_PRODUCTS_TO_SHOP,
payload: request
}
}
My production action for this thus far is:
import React, { Component } from 'react';
import UserLayout from '../../../hoc/user';
import FormField from '../../utils/Form/formfield';
import { update, generateData, isFormValid, populateOptionFields, resetFields } from '../../utils/Form/formActions';
import FileUpload from '../../utils/Form/fileupload';
import {connect} from 'react-redux'
import {getCharacters, getPublishers, addProduct, clearProduct } from '../../../actions/products_actions'
class AddProduct extends Component {
state={
formError:false,
formSuccess:false,
formdata:{
name: {
element: 'input',
value: '',
config:{
label: 'Product title',
name: 'name_input',
type: 'text',
placeholder: 'Enter title'
},
validation:{
required: true
},
valid: false,
touched: false,
validationMessage:'',
showlabel: true
},
description: {
element: 'textarea',
value: '',
config:{
label: 'Product description',
name: 'description_input',
type: 'text',
placeholder: 'Enter your description'
},
validation:{
required: true
},
valid: false,
touched: false,
validationMessage:'',
showlabel: true
},
price: {
element: 'input',
value: '',
config:{
label: 'Product price',
name: 'price_input',
type: 'number',
placeholder: 'Enter your price'
},
validation:{
required: true
},
valid: false,
touched: false,
validationMessage:'',
showlabel: true
},
character: {
element: 'select',
value: '',
config:{
label: 'Product Character',
name: 'character_input',
options:[]
},
validation:{
required: true
},
valid: false,
touched: false,
validationMessage:'',
showlabel: true
},
issue: {
element: 'input',
value: '',
config:{
label: 'Issue number',
name: 'issue_input',
type: 'number',
placeholder: 'Enter issue number'
},
validation:{
required: true
},
valid: false,
touched: false,
validationMessage:'',
showlabel: true
},
shipping: {
element: 'select',
value: '',
config:{
label: 'Shipping',
name: 'shipping_input',
options:[
{key:true,value:'Yes'},
{key:false,value:'No'},
]
},
validation:{
required: true
},
valid: false,
touched: false,
validationMessage:'',
showlabel: true
},
available: {
element: 'select',
value: '',
config:{
label: 'Available, in stock',
name: 'available_input',
options:[
{key:true,value:'Yes'},
{key:false,value:'No'},
]
},
validation:{
required: true
},
valid: false,
touched: false,
validationMessage:'',
showlabel: true
},
publisher: {
element: 'select',
value: '',
config:{
label: 'Publisher',
name: 'publisher_input',
options:[]
},
validation:{
required: true
},
valid: false,
touched: false,
validationMessage:'',
showlabel: true
},
publish: {
element: 'select',
value: '',
config:{
label: 'Publish',
name: 'publish_input',
options:[
{key:true,value:'Public'},
{key:false,value:'Hidden'},
]
},
validation:{
required: true
},
valid: false,
touched: false,
validationMessage:'',
showlabel: true
},
images:{
value:[],
validation:{
required: false
},
valid: true,
touched: false,
validationMessage:'',
showlabel: false
}
}
}
updateFields = (newFormData) => {
this.setState({
formdata: newFormData
})
}
updateForm = (element) => {
const newFormdata = update(element,this.state.formdata,'products');
this.setState({
formError: false,
formdata: newFormdata
})
}
resetFieldHandler = () => {
const newFormData = resetFields(this.state.formdata,'products');
this.setState({
formdata: newFormData,
formSuccess:true
});
setTimeout(()=>{
this.setState({
formSuccess: false
},()=>{
this.props.dispatch(clearProduct())
})
},3000)
}
submitForm= (event) =>{
event.preventDefault();
var dataToSubmit = generateData(this.state.formdata,'products');
var formIsValid = isFormValid(this.state.formdata,'products')
if(formIsValid){
this.props.dispatch(addProduct(dataToSubmit)).then(()=>{
if( this.props.products.addProduct.success){
this.resetFieldHandler();
}else{
this.setState({formError: true})
}
})
} else {
this.setState({
formError: true
})
}
}
componentDidMount(){
const formdata = this.state.formdata;
this.props.dispatch(getCharacters()).then( response => {
const newFormData = populateOptionFields(formdata,this.props.products.characters, 'character');
this.updateFields(newFormData)
})
this.props.dispatch(getPublishers()).then( response => {
const newFormData = populateOptionFields(formdata,this.props.products.publishers, 'publisher');
this.updateFields(newFormData)
})
}
imagesHandler = (images) => {
const newFormData = {
...this.state.formdata
}
newFormData['images'].value = images;
newFormData['images'].valid = true;
this.setState({
formdata: newFormData
})
}
render() {
return (
<UserLayout>
<div>
<h1>Add product</h1>
<form onSubmit={(event)=> this.submitForm(event)}>
<FileUpload
imagesHandler={(images)=> this.imagesHandler(images)}
reset={this.state.formSuccess}
/>
<FormField
id={'name'}
formdata={this.state.formdata.name}
change={(element)=> this.updateForm(element)}
/>
<FormField
id={'description'}
formdata={this.state.formdata.description}
change={(element)=> this.updateForm(element)}
/>
<FormField
id={'price'}
formdata={this.state.formdata.price}
change={(element)=> this.updateForm(element)}
/>
<div className="form_devider"></div>
<FormField
id={'character'}
formdata={this.state.formdata.character}
change={(element)=> this.updateForm(element)}
/>
<FormField
id={'issue'}
formdata={this.state.formdata.issue}
change={(element)=> this.updateForm(element)}
/>
<FormField
id={'shipping'}
formdata={this.state.formdata.shipping}
change={(element)=> this.updateForm(element)}
/>
<FormField
id={'available'}
formdata={this.state.formdata.available}
change={(element)=> this.updateForm(element)}
/>
<div className="form_devider"></div>
<FormField
id={'publisher'}
formdata={this.state.formdata.publisher}
change={(element)=> this.updateForm(element)}
/>
<FormField
id={'publish'}
formdata={this.state.formdata.publish}
change={(element)=> this.updateForm(element)}
/>
{this.state.formSuccess ?
<div className="form_success">
Success
</div>
:null}
{this.state.formError ?
<div className="error_label">
Nope
</div>
: null}
<button onClick={(event) => this.submitForm(event)}>
Add product
</button>
</form>
</div>
</UserLayout>
);
}
}
const mapStateToProps = (state) => {
return {
products: state.products
}
}
export default connect(mapStateToProps)(AddProduct);

Antd UI Table: Dynamically add/delete columns

Based on ANTD's Table example: https://ant.design/components/table/#components-table-demo-edit-cell, I would like to replicate this, with the addition of having the ability to add/delete new columns. The sample from the link above only illustrates how to add new rows.
Here's the code from the sample:
import { Table, Input, Button, Popconfirm, Form } from 'antd';
const FormItem = Form.Item;
const EditableContext = React.createContext();
const EditableRow = ({ form, index, ...props }) => (
<EditableContext.Provider value={form}>
<tr {...props} />
</EditableContext.Provider>
);
const EditableFormRow = Form.create()(EditableRow);
class EditableCell extends React.Component {
state = {
editing: false,
}
componentDidMount() {
if (this.props.editable) {
document.addEventListener('click', this.handleClickOutside, true);
}
}
componentWillUnmount() {
if (this.props.editable) {
document.removeEventListener('click', this.handleClickOutside, true);
}
}
toggleEdit = () => {
const editing = !this.state.editing;
this.setState({ editing }, () => {
if (editing) {
this.input.focus();
}
});
}
handleClickOutside = (e) => {
const { editing } = this.state;
if (editing && this.cell !== e.target && !this.cell.contains(e.target)) {
this.save();
}
}
save = () => {
const { record, handleSave } = this.props;
this.form.validateFields((error, values) => {
if (error) {
return;
}
this.toggleEdit();
handleSave({ ...record, ...values });
});
}
render() {
const { editing } = this.state;
const {
editable,
dataIndex,
title,
record,
index,
handleSave,
...restProps
} = this.props;
return (
<td ref={node => (this.cell = node)} {...restProps}>
{editable ? (
<EditableContext.Consumer>
{(form) => {
this.form = form;
return (
editing ? (
<FormItem style={{ margin: 0 }}>
{form.getFieldDecorator(dataIndex, {
rules: [{
required: true,
message: `${title} is required.`,
}],
initialValue: record[dataIndex],
})(
<Input
ref={node => (this.input = node)}
onPressEnter={this.save}
/>
)}
</FormItem>
) : (
<div
className="editable-cell-value-wrap"
style={{ paddingRight: 24 }}
onClick={this.toggleEdit}
>
{restProps.children}
</div>
)
);
}}
</EditableContext.Consumer>
) : restProps.children}
</td>
);
}
}
class EditableTable extends React.Component {
constructor(props) {
super(props);
this.columns = [{
title: 'name',
dataIndex: 'name',
width: '30%',
editable: true,
}, {
title: 'age',
dataIndex: 'age',
}, {
title: 'address',
dataIndex: 'address',
}, {
title: 'operation',
dataIndex: 'operation',
render: (text, record) => (
this.state.dataSource.length >= 1
? (
<Popconfirm title="Sure to delete?" onConfirm={() => this.handleDelete(record.key)}>
Delete
</Popconfirm>
) : null
),
}];
this.state = {
dataSource: [{
key: '0',
name: 'Edward King 0',
age: '32',
address: 'London, Park Lane no. 0',
}, {
key: '1',
name: 'Edward King 1',
age: '32',
address: 'London, Park Lane no. 1',
}],
count: 2,
};
}
handleDelete = (key) => {
const dataSource = [...this.state.dataSource];
this.setState({ dataSource: dataSource.filter(item => item.key !== key) });
}
handleAdd = () => {
const { count, dataSource } = this.state;
const newData = {
key: count,
name: `Edward King ${count}`,
age: 32,
address: `London, Park Lane no. ${count}`,
};
this.setState({
dataSource: [...dataSource, newData],
count: count + 1,
});
}
handleSave = (row) => {
const newData = [...this.state.dataSource];
const index = newData.findIndex(item => row.key === item.key);
const item = newData[index];
newData.splice(index, 1, {
...item,
...row,
});
this.setState({ dataSource: newData });
}
render() {
const { dataSource } = this.state;
const components = {
body: {
row: EditableFormRow,
cell: EditableCell,
},
};
const columns = this.columns.map((col) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: record => ({
record,
editable: col.editable,
dataIndex: col.dataIndex,
title: col.title,
handleSave: this.handleSave,
}),
};
});
return (
<div>
<Button onClick={this.handleAdd} type="primary" style={{ marginBottom: 16 }}>
Add a row
</Button>
<Table
components={components}
rowClassName={() => 'editable-row'}
bordered
dataSource={dataSource}
columns={columns}
/>
</div>
);
}
}
ReactDOM.render(<EditableTable />, mountNode);
You can make your columns array a part of the state and update it through setState.
Here is a working codepen: https://codepen.io/gges5110/pen/GLPjYr?editors=0010
// State
this.state = {
dataSource: [{
key: '0',
name: 'Edward King 0',
age: '32',
address: 'London, Park Lane no. 0',
}, {
key: '1',
name: 'Edward King 1',
age: '32',
address: 'London, Park Lane no. 1',
}],
columns: [{
title: 'name',
dataIndex: 'name',
width: '30%',
editable: true,
}, {
title: 'age',
dataIndex: 'age',
}]
};
// Event to add new column
handleAddColumn = () => {
const { columns } = this.state;
const newColumn = {
title: 'age',
dataIndex: 'age',
};
this.setState({
columns: [...columns, newColumn]
});
}
// Render method
render() {
const { dataSource, columns } = this.state;
return (
<Table
dataSource={dataSource}
columns={columns}
/>
);
}

Resources