onChange event re-renders the Entire Table - reactjs

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

Related

Best way to use useMemo/React.memo for an array of objects to prevent rerender after edit one of it?

I'm struggling with s performance issue with my React application.
For example, I have a list of cards which you can add a like like facebook.
Everything, all list is rerendering once one of the child is updated so here I'm trying to make use of useMemo or React.memo.
I thought I could use React.memo for card component but didn't work out.
Not sure if I'm missing some important part..
Parent.js
const Parent = () => {
const postLike= usePostLike()
const listData = useRecoilValue(getCardList)
// listData looks like this ->
//[{ id:1, name: "Rose", avararImg: "url", image: "url", bodyText: "text", liked: false, likedNum: 1, ...etc },
// { id:2, name: "Helena", avararImg: "url", image: "url", bodyText: "text", liked: false, likedNum: 1, ...etc },
// { id: 3, name: "Gutsy", avararImg: "url", image: "url", bodyText: "text", liked: false, likedNum: 1, ...etc }]
const memoizedListData = useMemo(() => {
return listData.map(data => {
return data
})
}, [listData])
return (
<Wrapper>
{memoizedListData.map(data => {
return (
<Child
key={data.id}
data={data}
postLike={postLike}
/>
)
})}
</Wrapper>
)
}
export default Parent
usePostLike.js
export const usePressLike = () => {
const toggleIsSending = useSetRecoilState(isSendingLike)
const setList = useSetRecoilState(getCardList)
const asyncCurrentData = useRecoilCallback(
({ snapshot }) =>
async () => {
const data = await snapshot.getPromise(getCardList)
return data
}
)
const pressLike = useCallback(
async (id) => {
toggleIsSending(true)
const currentList = await asyncCurrentData()
...some api calls but ignore now
const response = await fetch(url, {
...blabla
})
if (currentList.length !== 0) {
const newList = currentList.map(list => {
if (id === list.id) {
return {
...list,
liked: true,
likedNum: list.likedNum + 1,
}
}
return list
})
setList(newList)
}
toggleIsSending(false)
}
},
[setList, sendYell]
)
return pressLike
}
Child.js
const Child = ({
postLike,
data
}) => {
const { id, name, avatarImg, image, bodyText, likedNum, liked } = data;
const onClickPostLike = useCallback(() => {
postLike(id)
})
return (
<Card>
// This is Material UI component
<CardHeader
avatar={<StyledAvatar src={avatarImg} />}
title={name}
subheader={<SomeImage />}
/>
<Image drc={image} />
<div>{bodyText}</div>
<LikeButton
onClickPostLike={onClickPostLike}
liked={liked}
likedNum={likedNum}
/>
</Card>
)
}
export default Child
LikeButton.js
const LikeButton = ({ onClickPostLike, like, likedNum }) => {
const isSending = useRecoilValue(isSendingLike)
return (
<Button
onClick={() => {
if (isSending) return;
onClickPostLike()
}}
>
{liked ? <ColoredLikeIcon /> : <UnColoredLikeIcon />}
<span> {likedNum} </span>
</Button>
)
}
export default LikeButton
The main question here is, what is the best way to use Memos when one of the lists is updated. Memorizing the whole list or each child list in the Parent component, or use React.memo in a child component...(But imagine other things could change too if a user edits them. e.g.text, image...)
Always I see the Parent component is highlighted with React dev tool.
use React.memo in a child component
You can do this and provide a custom comparator function:
const Child = React.memo(
({
postLike,
data
}) => {...},
(prevProps, nextProps) => prevProps.data.liked === nextProps.data.liked
);
Your current use of useMemo doesn't do anything. You can use useMemo as a performance optimization when your component has other state updates and you need to compute an expensive value. Say you have a collapsible panel that displays a list:
const [expand, setExpand] = useState(true);
const serverData = useData();
const transformedData = useMemo(() =>
transformData(serverData),
[serverData]);
return (...);
useMemo makes it so you don't re-transform the serverData every time the user expands/collapses the panel.
Note, this is sort of a contrived example if you are doing the fetching yourself in an effect, but it does apply for some common libraries like React Apollo.

Set value to state React js

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

How to show pictures from Array to table row in react

I already asked this question but got advice to ask again with more details.
I have project to load data from firebase in react-table, and that is done. Working perfectly. Problem is that from that database, there are pictures which need to be showed in table too. From first Picture you can see how data in firebase is organized.
firebase data
And here is code to load that data in react:
class App extends Component {
constructor(props) {
super(props);
this.state = {
vehicles: []
};
}
componentWillMount() {
this.getvehicles();
}
getvehicles() {
let vehicles = [];
firebase
.database()
.ref(`vehicles`)
.once('value', snapshot => {
snapshot.forEach(level1 => {
level1.forEach(level2 => {
const vehicle = level2.val();
vehicle.pictures && vehicles.push(vehicle);
});
});
this.setState({
vehicles
});
});
}
From second picture you can see that data is loaded from firebase
Data loaded from Firebase
And Render code is here:
render() {
const vehiclesColumns = [
{
columns: [
{
Header: 'Vehicle ID',
id: 'vehicleID',
accessor: d => d.vehicleID,
filterMethod: (filter, row) =>
row[filter.id].startsWith(filter.value)
},
{
Header: 'Terminal',
id: 'terminal',
accessor: d => d.terminal,
filterMethod: (filter, row) =>
row[filter.id].startsWith(filter.value)
},
{
Header: 'Time',
id: 'timestamp',
accessor: d => {
return Moment(d.timestamp)
.local()
.format('DD-MMMM-YYYY', 'at', true);
}
},
{
Header: 'User',
id: 'user',
accessor: d => d.user,
filterMethod: (filter, row) =>
row[filter.id].startsWith(filter.value)
}
]
}
];
return (
<div style={style}>
<div>
<ReactTable
style={{ marginLeft: '-80%', marginRight: '-80%' }}
data={this.state.vehicles}
filterable
defaultFilterMethod={(filter, row) =>
String(row[filter.id]) === filter.value
}
columns={vehiclesColumns}
SubComponent={row => {
return <div>PICTURES IN ROW</div>;
}}
/>
</div>
</div>
);
}
}
So my question is, anyone to help me, or rewrite the code, "pictures" array what you can see on second screenshot, render in "row" of "react-table" example:
SubComponent={row => {
return <div><img src={remoteUri} key={vehicle.picture} /></div>;
}}
As you can see on the last screenshot, how sould be and where to show "pictures" from Firebase.
REACT-TABLE DATA WITH PICTURES IN ROW
Already found solution:
Before "render" there is "chain" method to connect all pictures from one vehicle
getvehicles() {
firebase
.database()
.ref(`pictures`)
.once('value', snapshot => {
const data = snapshot.val();
const vehicles = _.chain(data)
.values()
.groupBy('vehicleId')
.map((rows, vehicleId) => ({
vehicleId,
pictures: _.map(rows, 'remoteUri')
}))
.value();
console.log(vehicles);
this.setState({ vehicles });
});
}
At "render"
const storage = firebase.storage();
const storageRef = storage.ref();
<div>
{row.original.pictures.map(ref => (
<Async
promiseFn={() => storageRef.child(ref).getDownloadURL()}
>
{({ isLoading, error, data }) => {
if (error) {
return 'FATALL ERROR';
}
if (isLoading) {
return 'Loading...';
}
if (data) {
return <img src={data} alt={data} key={data} />;
}
}}
</Async>
))}
</div>
With this code Im getting pictures in row of "Subcomponent" in React-table

React filtering a list

I can't figure out the logic for the callback function in the below codepen.
As it stands the list gets filtered as the user types in a value to the input field. I can't figure out how to then bring the list back, if the filter gets removed.
https://codepen.io/benszucs/pen/BPqMwL?editors=0010
class Application extends React.Component {
state = {
options: ['Apple', 'Banana', 'Pear', 'Mango', 'Melon', 'Kiwi']
}
handleFilter = (newFilter) => {
if (newFilter !== "") {
this.setState(() => ({
options: this.state.options.filter(option => option.toLowerCase().includes(newFilter.toLowerCase()))
}));
}
};
render() {
return (
<div>
<Filter handleFilter={this.handleFilter} />
{this.state.options.map((option) => <p>{option}</p>)}
</div>
);
};
}
const Filter = (props) => (
<div>
<input name="filter" onChange={(e) => {
props.handleFilter(e.target.value);
}}/>
</div>
);
ReactDOM.render(<Application />, document.getElementById('app'));
Since you are overwriting the original value of the state, you won't able to reverse it. I would recommend to create a new state called filter, and update its value in the onChangeHandler(). And in the render() method you should filter the results before displaying them.
Example:
// the state
this.state = {
users: ['abc','pdsa', 'eccs', 'koi'],
filter: '',
}
// the change handler
onChangeHandler(e) {
this.setState({
filter: e.target.value,
});
}
// displaying the results
const list = this.state.users.filter(u => u.includes(this.state.filter)).map(u => (
<li>{u}</li>
));
In your handle Filter you can set the state to its default value
handleFilter = (newFilter) => {
if (newFilter !== "") {
this.setState(() => ({
options: this.state.options.filter(option => option.toLowerCase().includes(newFilter.toLowerCase()))
}));
} else {
this.setState(() => ({
options: ['Apple', 'Banana', 'Pear', 'Mango', 'Melon', 'Kiwi']
}));
}
https://codepen.io/RACCH/pen/pZxMRJ

ReactJS Render Table for each value on Array

I have a MultiSelect and a React Table..
the Select stores the values into value Array..
The way it is now i´m able to select ONE option and the table displays the data correctly. But, i´m looking to render a table for each selected option. How could i achieve something like this?
handleSelectChange (value) {
console.log('You\'ve selected:', value);
this.setState({ value: value }, () => this.fetchTable());
}
fetchTable() {
const url = 'http://localhost:8000/issues/from/';
const value = this.state.value;
const string = url+value;
fetch(string)
.then(function(response) {
return response.json();
})
.then((myJson) => this.setState({data: myJson.issues}));
}
componentDidMount() {
this.fetchData();
}
render() {
const filteredResult = this.state.boards.map(item => (
{
value: item.key,
label: item.name,
}
));
const filteredResult1 = this.state.data.map(item => (
{
name: item.fields,
id: item.id,
key: item.key,
}
));
return (
<div>
<Select
closeOnSelect={!stayOpen}
disabled={disabled}
multi
onChange={this.handleSelectChange}
options={filteredResult}
placeholder="Select Assignee(s)..."
removeSelected={this.state.removeSelected}
rtl={this.state.rtl}
simpleValue
value={value}
/>
<ResponseTable data={filteredResult1} />
</div>
);
}
}
How does your ResponseTable component look like? I guess you can just use the map function to loop and display the table rows. Sth like this:
const data = [{name: 'Test 1', id: 1, key: 'key_1'}, {name: 'Test 2', id: 2, key: 'key_2'}, {name: 'Test 3', id: 3, key: 'key_3'}];
_renderTableBody = () => {
return data.map((item) => (
return (
<TableRow>
<TableCell>item.name</TableCell>
<TableCell>item.id</TableCell>
<TableCell>item.key</TableCell>
</TableRow>
)
))
}
Then inside your render function, you can just replace this
<ResponseTable data={filteredResult1} />
into the code like this:
{this._renderTableHead()} // same method as _renderTableBody() to generate the table head title
{this._renderTableBody()}
Hope this can help!
Just keep some dummy key in state which as empty array initially. It will push the selected value of select option in to it. like below
constructor(props){
this.state = {
selectedValues: []
}
}
Alter your handleSelectChange like below. It needs to update the current selected value in this array
handleSelectChange (value) {
console.log('You\'ve selected:', value);
//this.setState({ value: value }, () => this.fetchTable());
let currentSelectedValue = this.state.selectedValues.filter(selected => selected == value)
//it will return a value if it is found otherwise empty array will be returned
if(currentSelectedValue.length == 0){
let updatedSelectedValue = this.state.selectedValues.push(value)
this.setState({ selectedValues: updatedSelectedValues }, () => this.fetchTable());
}
}
removeSelected (value) {
console.log('You\'ve selected:', value);
//this.setState({ value: value }, () => this.fetchTable());
let currentSelectedValue = this.state.selectedValues.filter(selected => selected !== value) //this will delete the removed option from array
this.setState({ selectedValues: currentSelectedValue }, () => this.fetchTable());
}
fetchTable() {
if( this.state.selectedValues.length > 0 ){
this.state.selectedValues.map((value)=>{
const url = 'http://localhost:8000/issues/from/';
const string = url+value;
fetch(string)
.then(function(response) {
return response.json();
})
.then((myJson) => this.setState({data: [...this.state.data, myJson.issues]})); //use spread operator to combine the result of each selectedValue
});
}
}
render() {
const filteredResult = this.state.boards.map(item => (
{
value: item.key,
label: item.name,
}
));
const filteredResult1 = this.state.data.map(item => (
{
name: item.fields,
id: item.id,
key: item.key,
}
));
return (
<div>
<Select
closeOnSelect={!stayOpen}
disabled={disabled}
multi
onChange={this.handleSelectChange}
options={filteredResult}
placeholder="Select Assignee(s)..."
removeSelected={this.state.removeSelected}
rtl={this.state.rtl}
simpleValue
value={value}
/>
this.state.data.map((item) => { // item here will hold the json object of { id: item.id, key: item.key, name: item.fields }
<ResponseTable data={item} />
})
</div>
);
}
}

Resources