React Redux table update row - javascript

I'm new to react and redux.
I have a container which initialize a table component with a list of items, and onclick function.
In the table component I have checkbox for each row. When I click the checkbox I want to select the row (change its style and add selected property to its element model).
When I click on the checkbox I call the onclick property function, then find the item on the list by its id, and change its selected property. The view is not refreshing.
I understand that a component is a "stupid" component that only binds the props and rendering.
What am I doing wrong?
// People container
<Table items={this.props.people} columns={this._columns} onRowSelect={this.selectRow} />
this.selectRow(id){
const selectedLead =_.find(this.props.leads.docs, (lead)=>{
return lead._id == id;
})
selectedLead.selected = !selectedLead.selected;
}
// Table Component - inside render()
{this.props.items.map((item, idx) => {
console.log(item.selected);
return <div style={styles.row(item.selected)}>etc...</div>
})}
Thanks :)

A React Component has props and state.
The difference is, that the Component will never change it props. But it can change it's state. This is why a Component will provide you the setState(...) Method, but no setProps(...) Method.
With that said, your approach to change the selected field in this.props is fundamentally not correct. (There also seems to be another problem in your code where you change the selected field in this.props.leads, but provide this.props.people to the table instead of this.props.leads)
Let me give you a basic example as to how I would solve your problem in Pure React (without a state library like Redux):
const Row = ({ item, onClick }) => (
<tr style={styles.row(item.selected)} onClick={() => onClick(item.id)}>...</tr>
)
const Table = ({ items, onRowClick }) => (
<table>
{items.map(item => <Row item={item} onClick={onRowClick} />)}
</table>
)
class PeopleTable extends React.PureComponent {
constructor(props) {
super(props)
this.state = { people: props.people }
}
componentWillReceiveProps(nextProps) {
if (nextProps.people !== this.state.people) {
this.setState({ people: nextProps.people })
}
}
setItemSelectedState(id) {
this.setState((prevState) => {
const people = prevState.people.map(item => ({
...item,
selected: item.id === id ? !item.selected : item.selected,
})
return { people }
})
}
handleRowClick = (id) => this.setItemSelectedState(id)
render() {
return (<Table items={people} onRowClick={this.handleRowClick} />)
}
}
The things to notice here are:
Row and Table are stateless components. They only take props and return jsx. Sometimes they are also referred to as presentational components.
PeopleTable keeps track of the selected state of each item. This is why it needs state and must be a class.
Because we can't change a components props, we have to keep a reference to props.people in this.state.
componentWillReceiveProps makes sure that if our components receives another list of people, the state is updated accordingly.
setItemSelectedState goes to the root of your problem. Instead of search and update of the item (like in your this.selectRow(id) method), we create a complete new list of people with map and call setState. setState will trigger a rerender of the component and because we created a new people list, we can use the !== check in componentWillReceiveProps to check if people has changed.
I hope this answer was helpful to your question.

Related

skip re-render using shouldComponentUpdate and nextState

I have currently a drop-down select to filter some charts after 'Apply'. It works fine.(See screenshot below).
The problem is that when another timespan gets selected, React does a re-render to all charts before I click 'Apply' button.
I want to avoid this unnecessary re-render by implementingshouldComponentUpdate, but I can't figure out how.
Below what I tried but it did not work(still a re-render):
shouldComponentUpdate(nextState) {
if (this.state.timespanState !== nextState.timespanState) {
return true;
}
return false;
}
But it always return true, because nextState.timespanState is undefined. Why?
Drop-down Select
<Select value={this.state.timespanState} onChange={this.handleTimeSpanChange}>
handleTimeSpanChange = (event) => {
this.setState({ timespanState: event.target.value });
};
constructor(props) {
super(props);
this.state = { timespanState: 'Today'};
this.handleTimeSpanChange = this.handleTimeSpanChange.bind(this);
}
You're on the right track with using shouldComponentUpdate, it's just that the first parameter is nextProps and the second is nextState, so in your case, the undefined value is actually nextProps with the wrong name.
Change your code to this,
shouldComponentUpdate(nextProps,nextState) { // <-- tweak this line
if (this.state.timespanState !== nextState.timespanState) {
return true;
}
return false;
}
Finally, I solve the problem by separating drop-down selectbox and charts into two apart components and made the drop-down component as a child component from its parent component, charts components.
The reason is the following statement
React components automatically re-render whenever there is a change in their state or props.
Therefore, React will re-render everything in render() method of this component. So keeping them in two separate components will let them re-render without side effect. In my case, any state changes in drop-down or other states in Filter component, will only cause a re-render inside this component. Then passing the updated states to charts component with a callback function.
Something like below:
Child component
export class Filter extends Component {
handleApplyChanges = () => {
this.props.renderPieChart(data);
}
render(){
return (
...
<Button onClick={this.handleApplyChanges} />
);
}
}
Parent component
export class Charts extends Component{
constructor(props){
this.state = { dataForPieChart: []};
this.renderPieChart = this.renderPieChart.bind(this);
}
renderPieChart = (data) => {
this.setState({ dataForPieChart: data });
}
render(){
return (
<Filter renderPieChart={this.renderPieChart} />
<Chart>
...data={this.state.dataForPieChart}
</Chart>
);
}
}
If still any question, disagreement or suggestions, pls let me know:)

React functional component rerenders despite use of React.memo()

I'm fairly new to React and I'm having some trouble understanding exactly why an unchanging component is getting rerendered, even though I'm using the React.memo higher-order component.
I have a sidebar which contains a number of row elements. Rows contain data that's used in other components; all components share the 'selection' status of the rows. In the sidebar, I change the styling to show the selection state of every element.
Everything behaves as expected, but performance scales poorly as the list gets longer. I think part of this is due to React re-rendering every row element in the sidebar list, including ones whose selection state has not changed. I thought I could prevent this re-rendering by using React.memo, but it doesn't seem to make a difference.
Here is the code for each list entry:
import React from 'react';
// The only props that might change value are the labels string and
// the styles rowStyle and labelStyle, which caller populates
// with 'selected' or 'unselected' styles based on row state
const Row = React.memo(({
rowId, labels = "", rowStyle = {}, labelStyle = {},
onClicked // callback from grandparent, which updates selections (w/ modifier keys)
}) => {
console.log(`Rendering row ${rowId}`) // to report when rows rerender
return (
<div
key={rowId}
style={rowStyle}
onClick={(event) => onClicked(rowId, event)}
>
<span>{rowId}</span>
<span style={labelStyle}>{ labels }</span>
</div>
);
})
export default Row;
This component is called from a parent which represents the entire sidebar list. In order to minimize the amount of needless function calls (and make very clear that there's nothing with any side effects happening within the individual rows), I build a list of tuples for each row that has its id, style, labels, and label-style.
The contents of the list are passed to the Row component, and most of the time should be identical between calls (thus triggering memoization and avoiding the rerender), but don't seem to be.
import React from 'react';
import Row from '../pluginComponents/Row';
import Styles from './common/Styles'; // to ensure the references aren't changing
// onClicked is passed in from the parent component and handles changing the selections
const ListDiv = React.memo(({ rowIds, onClicked, rowLabels, styling, selections }) => {
const tuples = rowIds.reduce((priors, rowId) => {
return {
...priors,
[rowId]: {
'style': Styles.unselectedStyle,
'labelStyle': Styles.unselectedLabelStyle,
'labels': ((rowLabels[rowId] || {}).labels || []).join(", ")
}
}
}, {});
Object.keys(selections).forEach((rowId) => {
if (!tuples[rowId]) return;
tuples[rowId]['style'] = Styles.selectedStyle;
tuples[rowId]['labelStyle'] = Styles.selectedLabelStyle;
});
return (
<div style={styling}>
{rowIds.map((rowId) => (
<Row
key={rowId}
rowId={rowId}
labels={tuples[rowId]['labels']}
rowStyle={tuples[rowId]['style']}
labelStyle={tuples[rowId]['labelStyle']}
onClicked={onClicked}
/>
))}
</div>
)
})
const RowList = ({ list, selections = {}, onClicked, labels={}, styling }) => {
if (!list) return (<div>Empty list</div>);
return (
<div>
<ListDiv
rowIds={list}
onClicked={onClicked}
rowLabels={labels}
styling={styling}
selections={selections}
/>
</div>
);
}
export default RowList;
which is itself called from a grandparent class that manages all the state:
const Grandparent = (props) => {
...
return (
...
<div>
{
(status !== 'complete') ? (
<div><CircularProgress /></div>
) : (
<RowList list={data.list}
selections={selections} // tracked with useState
onClicked={handleClicked} // calls some functions defined in this component
labels={data.labels || {}}
styling={foo}
/>
)
}
...
);
...
Why are my ought-to-be-memoized entries of the Row component getting rerendered, and what can I do to fix it?
The onClicked function in the Grandparent could be getting recreated on each render, so making your row component re-render as well.
The solution is to use React.useCallback in the Grandparent.
const handleClicked = React.useCallback(() => {
...
}, [a, b])
Where a and b are dependencies that if change will require a re-render.
React useCallback docs

How to update a component after updating props react

how to update a component after updating props?
I have the following component structure:
MyList.js
render() {
return(
<ComponentList products={props.products}>
<ComponentBoard product={props.product[i]} //send choosen product(1) from list(100 products)
)
}
and next components have 2 similar component contentRow
ComponentList.js (
same(
<contentRow > list .map() //100 contentRow
)
ComponentBoard.js
same(
<contentRow > // 1 contentRow
)
in the component componentRow there are fields that display and through redux change the data in the store, for example, the username.
And when I open the ComponentBoard component and change the value in the ComponentRov field, the value in the ComponentList> componentRow should also change.
For a better understanding:
ComponentList is a table of ComponentsRow, when clicked to row, That opens the ComponentBoard window, in which there is also a componentRow.
Question: how to update the data that is displayed in componentList from componentBoard? they have similar props from 1 store
When serializing props as initial state you should listen for changes in props and update the state accordingly. In class based components you use componentDidUpdate in functional components you can achieve the same result with an useEffect listening for changes in a given prop
const Component = ({ foo }) =>{
const [state, setState] = useState(foo) //initial state
useEffect(() =>{
setState(foo) //everytime foo changes update the state
},[foo])
}
The class equivalent
class Component extends React.Component{
state = { foo : this.props.foo } // initial state
componentDidUpdate(prevProps){
if(prevProps.foo !== this.props.foo)
this.setState({ foo : this.props.foo }) //everytime foo changes update the state
}
}
for a better understanding of React, I recommend you read
React Life Cycle
the general idea is to make your list into the state of the MyList.js , that way, u can update it through a function in Mylist.js and pass it as a prop to the ComponentBoard . Now you can change the state of MyList and when that changes, so does ComponentList.
class MyList extends Component {
constructor(){
super();
this.state = {
// an array of objects
}}
handleBoardChange = () => { some function using this.setState }
// the rest of your Mylist class
}

React/Redux rendering a list that's updating every second

I have a react component that receives props from the redux store every second. The new state has an array that's different than the last array. To be specific, every second an element is added to the array. For example:
in one state the array is:
[1, 2, 3, 4, 5, 6]
the next state
[1, 2, 3, 4, 5, 6, 7]
My reducer:
return {
...state,
myList: [ payload, ...state.myList.filter(item => payload.id !== item.id).slice(0, -1) ]
}
Now, in my react component I am subscribing to this state and for every change, the list is re-rendered.
import React, { Component } from 'react';
import MyRow from './MyRow';
class MyList extends Component {
render() {
return (
<div>
{this.props.myList.map((list, index) => (
<MyRow key={list.id} data={list}/>
))}
</div>
);
}
}
function select({ myList }) {
return { myList };
}
export default connect(select)(MyList);
In MyRow.js
import { PureComponent } from 'react';
class MyRow extends PureComponent {
render() {
const data = this.props.data;
return (
<div>
{data.id} - {data.name}
</div>
);
}
}
export default MyRow;
Now, my problem is: It's costly for me to re-render every element that has been already rendered. The MyRow heavily uses styled components and other expensive operations.
This is causing react to re-render the whole list every second when the state is updated. This gets worst if updates come in less than 1 seconds, like 4 updates per second. The react app simply crashes in this case.
Is there any way to only add the newly added item to the list and not re-render the whole list?
Thanks
You're using PureComponent, that do shallow comparison, then your component MyRow should not be rerendered on each new item being added (Please follow my code example below).
Is there any way to only add the newly added item to the list and not re-render the whole list?
According to your question - Yes, using PureComponent should render only 1 time the new item:
Here's what the React's docs says:
If your React component’s render() function renders the same result given the same props and state, you can use React.PureComponent for a performance boost in some cases.
Code example of PureComponent:
You can check out the code sample, that I did for you.
You will see that the Item component is always rendered only 1 time, because we use React.PureComponent. To prove my statement, each time the Item is rendered, I added current time of rendering. From the example you will see that the Item Rendered at: time is always the same, because it's rendered only 1 time.
const itemsReducer = (state = [], action) => {
if (action.type === 'ADD_ITEM') return [ ...state, action.payload]
return state
}
const addItem = item => ({
type: 'ADD_ITEM',
payload: item
})
class Item extends React.PureComponent {
render () {
// As you can see here, the `Item` is always rendered only 1 time,
// because we use `React.PureComponent`.
// You can check that the `Item` `Rendered at:` time is always the same.
// If we do it with `React.Component`,
// then the `Item` will be rerendered on each List update.
return <div>{ this.props.name }, Rendered at: { Date.now() }</div>
}
}
class List extends React.Component {
constructor (props) {
super(props)
this.state = { intervalId: null }
this.addItem = this.addItem.bind(this)
}
componentDidMount () {
// Add new item on each 1 second,
// and keep its `id`, in order to clear the interval later
const intervalId = setInterval(this.addItem, 1000)
this.setState({ intervalId })
}
componentWillUnmount () {
// Use intervalId from the state to clear the interval
clearInterval(this.state.intervalId)
}
addItem () {
const id = Date.now()
this.props.addItem({ id, name: `Item - ${id}` })
}
renderItems () {
return this.props.items.map(item => <Item key={item.id} {...item} />)
}
render () {
return <div>{this.renderItems()}</div>
}
}
const mapDispatchToProps = { addItem }
const mapStateToProps = state => ({ items: state })
const ListContainer = ReactRedux.connect(mapStateToProps, mapDispatchToProps)(List)
const Store = Redux.createStore(itemsReducer)
const Provider = ReactRedux.Provider
ReactDOM.render(
<Provider store={Store}>
<ListContainer />
</Provider>,
document.getElementById('container')
)
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.0/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.7/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.26.0/polyfill.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
Solutions:
If the performance problem is caused by MyRow rerending, please find out what's the reason of rerending, because it should not happen, because of PureComponent usage.
You can try to simplify your reducer, in order to check / debug, is the reducer causing the problem. For instance, just add the new item to the list (without doing anything else as filtrations, slice, etc): myList: [ ...state.myList, payload ]
Please make sure you always pass the same key to your item component <MyRow key={list.id} data={list} />. If the key or data props are changed, then the component will be rerendered.
Here are some other libraries, these stand for efficient rendering of lists. I'm sure they will give us some alternatives or insights:
react-virtualized - React components for efficiently rendering large lists and tabular data
react-infinite - A browser-ready efficient scrolling container based on UITableView
PureComponent will shallowly compare the props and state. So my guess here is that the items are somehow new objects than the previous passed props, thus the rerendering.
I would advice, in general, to only pass primitive values in pure components :
class MyList extends Component {
render() {
return (
<div>
{this.props.myList.map((item, index) => (
<MyRow key={item.id} id={item.id} name={data.name} />
//or it's alternative
<MyRow key={item.id} {...item} />
))}
</div>
);
}
}
//...
class MyRow extends PureComponent {
render() {
const {id, name} = this.props;
return (
<div>
{id} - {name}
</div>
);
}
}
The problem really exists in the reducer.
myList: [ payload, ...state.myList.filter(item => payload.id !== item.id).slice(0, -1) ]
What is the logic implemented using slice(0,-1)?
It is the culprit here.
From your question I understood the next state after [1,2,3] will be [1,2,3,4].
But your code will be giving [4,1,2], then [5,4,1] then [6,5,4].
Now all the elements in the state are new, not in the initial state. See state is not just getting appended it is completely changing.
Please see if you are getting the desired result by avoiding slice.
myList: [ payload, ...state.myList.filter(item => payload.id !== item.id)]
There is quite an easy solution for this. React VDOM is just a diffing algorithm. The only piece missing with your JSX is something called key which is like an id that the diffing algo uses and renders the particular element. Just tag the element with a KEY something like this https://reactjs.org/docs/lists-and-keys.html#keys
<li key={number.toString()}>
{number} </li>
it looks like you are creating a new array each time in the reducer in which all array indices need to be re-calculated. have you tried appending the new node to the end of the list instead of prepending?

react loop update state

I'm new to react and what I'm doing is loop to get to show the each element form the props and I want form the picture component update that props, I try to find a way to do it but I didn't know how to do it.
Code for the loop is this:
const pictureItems = this.props.imgFiles.map((img, index) => {
return <picture key={index} imgFile={img} pictureDataUpdate={this.onUpdatPicture} />;
});
The question is how can I update the props that are been pass to the picture component? (I'm already passing the information from picture to the component that is looping). I have so far this.
onUpdatPicture(data) {
console.log(data);
// this.setState(data);
}
The simplest method for manipulating props sent to a child component would be to store the data in the parent component's state. Doing so would allow you to manipulate the data and send the updated version to your child component.
Assuming our parent component is sent an array of image urls as the images prop, we'll need two main pieces in our code: our update function for our child to call and to map over our images and create our children.
class Gallery extends React.Component {
constructor(props) {
super(props)
//Setting our props to the state of the parent allows us to manipulate the data before sending it back to our child.
this.state = {
images: this.props.images || []
}
}
update = (key, value) => {
// Our update function is sent the {key} of our image to update, and the new {value} we want this key to hold.
// After we are passed our data, we can simply map over our array and return the new array to our state.
this.setState({
images: this.state.images.map( (img, i) => i === key ? value : img)
})
};
render() {
return (
<div className="gallery"> // Since we are going to have multiple children, we need to have a wrapper div so we don't get errors.
{
// We map over our data and send our child the needed props.
// We send our child the {src} of our image, our {update} function, the id our child will use to update our parent, and a key for React to keep track of our child components
images.map( (img, i) => <Picture src={img} update={this.update} id={i} key={'picture_' + i} />)
}
</div>
)
}
}
After we have our update function setup and our parent is mapping over our images to create the child component, all that's left to do is setup our child component to handle our data.
class Picture extends React.Component {
render() {
return (
// Notice our onClick is an arrow function that calls our update method. This is so we only call our update function once the onClick is fired, not when the component is being rendered.
<div className="picture" onClick={() => this.props.update(this.props.id, 'https://static.pexels.com/photos/189463/pexels-photo-189463.png')}>
<img src={this.props.src} />
</div>
)
}
}
Given the above code, once we render our gallery component, anytime an image is clicked, the child's image is replaced with a new image.
Here is a link to a working example on CodePen.

Categories