DOM Not Updating although function changes var - reactjs

Even though the variable value changes to a value that would cause the element not to render, the page does not update and the element remains rendered.
Tried moving inside component, did not work.
function clickHandler(item)
{
object[item].active = 0;
}
let object = [{data:
<p onClick={() => clickHandler(0)}> Data </p>,
active:1},
{data:
<p onClick={() => clickHandler(1)}> Data2 </p>,
active:1}
];
class Objects extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div class="notifications">
{object[0].active == 1 ? object[0].data : " "}
{object[1].active == 1 ? object[1].data : " "}
</div>
);
}
}
ReactDOM.render(<Objects />, document.querySelector('#object_display'));"
Expects to disappear but it does not.

Changing external data isn't going to trigger an update of your component. You need to either change the props passed to the component or keep track of it in state inside the component itself.
Consider this:
// data declared outside the component; gets passed as a prop
// in the ReactDOM.render call below.
const data = [
{
title: "Object 1"
},
{
title: "Object 2"
},
{
title: "Object 3"
},
]
class Objects extends React.Component {
// initial state; start with the first item
state = {index: 0}
// onClick handler
switch = () => {
// get the current index out of this.state
const {index} = this.state;
// get the number of items in data so
// we can loop back to 0 when we get to
// the last item
const {data: {length}} = this.props;
// increment the index, don't go beyond length
const newIndex = (index + 1) % length;
// calling setState triggers a re-render
// with the new index value
this.setState({index: newIndex});
}
render () {
const {data} = this.props;
const {index} = this.state;
const item = data[index];
return (
<div onClick={this.switch}>{item.title} (Click for next item)</div>
);
}
}
// data passed as a prop
ReactDOM.render(<Objects data={data} />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app" />

Related

React: updating one item of the list (setState) instead of all

I have the following code. When changing the value of one element, the entire list is re-render. How can it be avoided by redrawing only one element? I use setState and class components.
import React from "react";
import "./styles.css";
class ListItem extends React.Component {
handleUpdate = () => {
this.props.onUpdate(this.props.index);
};
totalRenders = 0;
render() {
const { data: { id, value } } = this.props;
this.totalRenders += 1;
console.log("Row rendered");
return (
<div>
<strong>id: </strong>{id}:
<strong>value: </strong>{value}
<strong>renders count: </strong>{this.totalRenders}
<button className="button" onClick={this.handleUpdate}>
Change row value
</button>
</div>
);
}
}
export default class App extends React.Component {
state = { list: [{id: 'id 1', value: '11'}, {id: 'id 2', value: '22'}]};
handleUpdate = (index) => {
let newState = this.state.list.slice()
newState[index].value = Math.round(Math.random() + 10);
this.setState(() => ({
list: newState
}));
};
render() {
console.log("App rendered");
return (
<div>
{this.state.list.map((el, index) => (
<ListItem
key={el.id}
data={el}
index={index}
onUpdate={this.handleUpdate}
/>
))}
</div>
);
}
}
Sandbox: https://codesandbox.io/s/autumn-architecture-ubgh51?file=/src/App.js
If you update your app state then that component is supposed to be updated. That the expected behaviour and that's how it should be behave. Now coming to your question. If changes in a row should not make your entire app re-render. Then you should manage your state in your item and any changes in that should be maintained in that to unnecessary re-render.
Here's an example of how to achieve it. You can replace your ListItem with this and check it yourself.
function UpdatedListItem({ data }) {
const [row, setRow] = React.useState(data);
React.useEffect(() => setRow(data), [data]);
const { id, value } = row;
console.log("Changes in: ", data);
function handleChange() {
setRow({
...row,
value: Math.round(100 + Math.random() * 5)
});
}
return (
<div>
<strong>id: </strong>
{id}:
<strong>value: </strong>
{value}
<strong>renders count: </strong>
{this.renders}
<button className="button" onClick={handleChange}>
Change row value
</button>
</div>
);
}
One way you can fix this is by using a implementing shouldcomponentupdate
but that method should be implemented when you need some performance improvement not just to stop re renderings
This method only exists as a performance optimization. Do not rely on it to “prevent” a rendering, as this can lead to bugs.
Next thing you can consider is using PureComponent
to implemented in that way you need to change the way you handle state update
handleMyChange = (index) => {
// Don't mutate the state in react it could lead in to unexpected results
// let newState = [...this.state.list];
// newState[index].value = Math.round(Math.random() + 10);
const newState = this.state.list.map((item, idx) => {
if (idx === index) {
// Replacing with a new item, so the other component can understand it's a new item and re-render it.
return {
...item,
value: Math.round(100 + Math.random() * 5)
};
}
// Return the same item, so the other component do not render since it's the same item.
return item;
});
this.setState(() => ({
list: newState
}));
};
class ListItem extends React.PureComponent {
......
React.PureComponent implements it with a shallow prop and state comparison, inorder to detect it as a new update we have to tell the ListItem component the props are different, to do that we should avoid mutating object in the array, instead we have to create a new item and replace the old array element.
Here is an good example of mutating state in react
Here is the updated sandbox
In your case, you can use the shouldComponentUpdate, reference: React document.
Simple sample:
shouldComponentUpdate(nextProps, nextState) {
// Custom update conditions.
return nextProps.data.id !== this.props.data.id;
}
Full sample:
※Additional Notes
If using function component, you can use memo to do it, reference: React document.

How to scroll to bottom when props changed in react-virtualized?

I have component App with List from react-virtualized library.
And I need on initial render, that my List scroll to bottom.
And I did it, when added scrollToIndex option. But when I add new object in my list array, it does not scroll to my last added object. How can I fix it? And is it good solution to use "forceUpdate()" function?
import { List } from "react-virtualized";
import loremIpsum from 'lorem-ipsum';
const rowCount = 1000;
const listHeight = 600;
const rowHeight = 50;
const rowWidth = 800;
class App extends Component {
constructor() {
super();
this.renderRow = this.renderRow.bind(this);
this.list = Array(rowCount).fill().map((val, idx) => {
return {
id: idx,
name: 'John Doe',
image: 'http://via.placeholder.com/40',
text: loremIpsum({
count: 1,
units: 'sentences',
sentenceLowerBound: 4,
sentenceUpperBound: 8
})
}
});
}
handle = () => {
this.list = [...this.list, { id: 1001, name: "haha", image: '', text: 'hahahahahaha' }];
this.forceUpdate();
this.refs.List.scrollToRow(this.list.length);
};
renderRow({ index, key, style }) {
console.log('____________', this.list.length);
return (
<div key={key} style={style} className="row" >
<div className="image">
<img src={this.list[index].image} alt="" />
</div>
<div onClick={this.handle}>{this.state.a}</div>
<div className="content">
<div>{this.list[index].name}</div>
<div>{this.list[index].text}</div>
</div>
</div>
);
}
render() {
return (
<div className="App">
<div className="list">
<List
ref='List'
width={rowWidth}
height={listHeight}
rowHeight={rowHeight}
rowRenderer={this.renderRow}
rowCount={this.list.length}
overscanRowCount={3}
scrollToIndex={this.list.length}
/>
</div>
</div>
);
}
}
export default App;
You mentioning you need to scroll to the bottom when the list item is changed and to be honest i don't like to use forceUpdate. As mentioned on the React docs:
Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render().
Luckily, one of React lifecycle method is suitable for this case, it is call componentDidUpdate. But you need to do some refactor of your code. Instead using private field, i suggest to put it on state/props.
This method will invoked immediately after updating props/state occurs. However, This method is not called for the initial render.
What you need to do is, compare the props, is it change or not? Then call this.refs.List.scrollToRow(this.list.length);
Sample code
class App extends Component {
constructor() {
this.state = {
list: [] // put your list data here
}
}
// Check the change of the list, and trigger the scroll
componentDidUpdate(prevProps, prevState) {
const { list } = this.state;
const { list: prevList } = prevState;
if (list.length !== prevList.length) {
this.refs.List.scrollToRow(list.length);
}
}
render() {
// usual business
}
}
more reference for React lifecyle methods:
https://reactjs.org/docs/react-component.html#componentdidupdate

Trigger Re-Render of Child component

I'm new to React and am running into the same problem a few times. In this particular situation, I'm trying to get an option in a select dropdown to update when I update a text input.
I have a parent, App, with the state attribute "directions", which is an array. This gets passed as a property to a child, GridSelector, which creates the text field and dropdown. When the text field is changed, a function triggers to update the parent state. This in turn causes the GridSelector property to update. However, the dropdown values, which are originally generated from that GridSelector property, do not re-render to reflect the new property value.
I'm trying to figure out the most React-ful way to do this and similar manuevers. In the past, I've set a state in the child component, but I think I've also read that is not proper.
My working site is at amaxalaus.bigriverwebdesign.com
Here's the pertinent code from each file:
App.js
class App extends React.Component {
constructor(props){
super(props);
this.state = {
directions: [],
dataRouteDirections: '/wp-json/wp/v2/directions',
currentDirectionsIndex: 0
}
this.addImageToGrid = this.addImageToGrid.bind(this);
this.changeTitle=this.changeTitle.bind(this);
}
componentDidMount(){
fetch(this.state.dataRouteDirections)
.then(data => data=data.json())
.then(data => this.setState({directions:data}));
}
addImageToGrid(image) {
this.refs.grid.onAddItem(image); //passes image add trigger from parent to child
}
createNewDirections(){
var directions= this.state.directions;
var index = directions.length;
var lastDirections = directions[directions.length-1];
var emptyDirections= {"id":0,"acf":{}};
emptyDirections.acf.grid="[]";
emptyDirections.acf.layout="[]";
emptyDirections.title={};
emptyDirections.title.rendered="New Directions";
if (lastDirections.id!==0 ) { ///checks if last entry is already blank
this.setState({
directions: directions.concat(emptyDirections), //adds empty directions to end and updates currentdirections
currentDirectionsIndex: index
});
}
}
changeTitle(newTitle){
var currentDirections = this.state.directions[this.state.currentDirectionsIndex];
currentDirections.title.rendered = newTitle;
}
render() {
var has_loaded; //has_loaded was added to prevent double rendering during loading of data from WP
this.state.directions.length > 0 ? has_loaded = 1 : has_loaded = 0;
if (has_loaded ) {
/* const currentGrid = this.state.directions;*/
return ( //dummy frame helpful for preventing redirect on form submit
<div>
<div className="fullWidth alignCenter container">
<GridSelector
directions={this.state.directions}
currentDirectionsIndex={this.state.currentDirectionsIndex}
changeTitle={this.changeTitle}
/>
</div>
<Grid ref="grid"
currentGrid={this.state.directions[this.state.currentDirectionsIndex]}
/>
<ImageAdd addImageToGrid={this.addImageToGrid}/>
<div className="fullWidth alignCenter container">
<button onClick={this.createNewDirections.bind(this)}> Create New Directions </button>
</div>
</div>
)
} else {
return(
<div></div>
)
}
}
}
GridSelector.js
class GridSelector extends React.Component {
constructor(props) {
super(props);
var currentDirections = this.props.directions[this.props.currentDirectionsIndex];
this.state = {
currentTitle:currentDirections.title.rendered
}
}
createOption(direction) {
if (direction.title) {
return(
<option key={direction.id}>{direction.title.rendered}</option>
)
} else {
return(
<option></option>
)
}
}
handleChangeEvent(val) {
this.props.changeTitle(val); //triggers parent to update state
}
render() {
return(
<div>
<select name='directions_select'>
{this.props.directions.map(direction => this.createOption(direction))}
</select>
<div className="fullWidth" >
<input
onChange={(e)=>this.handleChangeEvent(e.target.value)}
placeholder={this.state.currentTitle}
id="directionsTitle"
/>
</div>
</div>
)
}
}
You made a very common beginner mistake. In React state should be handled as an immutable object. You're changing the state directly, so there's no way for React to know what has changed. You should use this.setState.
Change:
changeTitle(newTitle){
var currentDirections = this.state.directions[this.state.currentDirectionsIndex];
currentDirections.title.rendered = newTitle;
}
To something like:
changeTitle(newTitle){
this.setState(({directions,currentDirectionsIndex}) => ({
directions: directions.map((direction,index)=>
index===currentDirectionsIndex? ({...direction,title:{rendered:newTitle}}):direction
})

Get updated state of the component outside the render

Sorry, this could be very basic, but I've this sutuation...
class MyComponent extends React.Component {
constructor () {
super()
this.state = {
selected: -1
}
}
this.list = []
for (let i = 0; i < myArray.length; i++) {
let item = <div>selected on this list : {this.state.selected}</div>
this.list.push(item)
}
render () {
return (
<div>selected: {this.state.selected}</div>
<div onClick={() => this.state.selected++}>
{this.list}
</div>
}
}
This supposed to generate:
selected : -1
on this list: -1
on this list: -1
on this list: -1
on this list: -1
...
Basically all things returning the value of the selected.
But when I click on the div to increment the selected, I'm receiving something like...
selected : 2
on this list: -1
on this list: -1
on this list: -1
on this list: -1
...
and as longs it's updates...
selected : 3,4,5,6,7...
on this list: -1
on this list: -1
on this list: -1
on this list: -1
...
In other words: the values of my list created by the for/loop is always displaying the initial state while the first div inside the render shows the state properly updated.
Any help ?
You have made a couple of mistakes:
You use the state of component directly to update it's value. You should use setState function (read here)
You store components in variable (list). Instead of doing this, you should store only data and render it in render function, or separate component.
Try not to use for loops. React behaves more predictably when you use map and other Array prototype methods.
If you want to handle state changes outside the render see compinentWillUpdate and componentDidUpdate methods.
Working example (fiddle):
class MyComponent extends React.Component {
constructor() {
super();
this.state = {
selected: -1
}
this.handleOnClick = this.handleOnClick.bind(this);
}
render() {
const selected = this.state.selected;
const myArray = [1, 2, 3, 4];
return (
<div>
<div>selected: {selected}</div>
<div onClick={this.handleOnClick}>
{myArray.map((_, index) => <div key={index}>selected on this list: {selected}</div>)}
</div>
</div>
)
}
handleOnClick() {
const selected = this.state.selected;
this.setState({ selected: selected + 1 });
}
}
The main issue is that you're mutating your state instead of using setState (https://facebook.github.io/react/docs/react-component.html)
This would be an example:
class MyComponent extends React.Component {
constructor (props) {
super(props);
this.state = {
selected: -1
};
}
updateSelected = () => {
this.setState({
selected: this.state.selected + 1
});
};
render() {
let list = [];
//do whatever you need with list
return (
<div>
<div>selected: {this.state.selected}</div>
<div onClick={this.updateSelected}>
{list}
</div>
</div>)
}
}
Because you are storing the ui-items inside a global variable and that is not getting updates, for loop will run only once during the initial rendering so ui items stored in global variable will have the old state value, another thing is the way you are updating the state value is not correct.
Changes:
1. Never mutate this.state directly, Use setState to update the state value.
2. Storing the ui component in state variable or in global variable is not a good idea, all ui part must be inside render method.
3. Instead of using arrow function inside render to handle events, bind the method in constructor for the performance reason.
4. Assign unique key to element inside loop, check the DOC for details.
Working Code:
class MyComponent extends React.Component {
constructor () {
super()
this.state = {
selected: -1
}
this._handleClick = this._handleClick.bind(this);
}
_handleClick(){
this.setState(prevState => ({
selected: prevState.selected + 1,
}))
}
render () {
let items = [];
for (let i = 0; i < ([1,2,3,4,5]).length; i++) {
items.push(<div key={i}>selected on this list : {this.state.selected}</div>)
}
return (
<div>
<div onClick={ this._handleClick }>Increment + </div>
<div>selected: {this.state.selected}</div>
{items}
</div>
)
}
}
ReactDOM.render(<MyComponent/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app'/>

How to render properties of an Array of objects in React?

So, I have the exact same problem as our friend here :
How to render properties of objects in React?
The below (upvoted) solution by Nahush Farkande :
render() {
let user = this.props.user || {};
....
{user.email}
....
}
works for me... if user is an object. However, in my specific case, the data I fetch and want to render is an array of objects.
So, I return something like that :
<ul>
{
user.map( (el, idx) => {
return (
<li key = {idx}>
<div className="panel-body clearfix">
{el.title}
</div>
</li>
)
})
}
</ul>
That doesn't work. I get an error message that tells me that user.map is not a function (before [HMR] connected).
I expected that once the API fetches the user array of objects, the component would re-render and then the component would show the list of titles from each object of the user array (after [HMR] connected).
If your user (I recommend to rename to users) is an array, then you cannot use {} as the default. You should use [] as the default value:
const user = this.props.user || []
or, you can use a completely different branch to handle the loading case:
if (!this.props.user) {
return (
<div> ... my loading placeholder ... </div>
);
}
You already have correct answer but just wanted to give a running example.
Initialize your data in your state with the default values e.g
in case of object -> {}
in case or array -> []
Obviously in each case your rendering logic should be different e.g in case of array you need the map to loop over array and generate jsx element.
So when ever your component receives the updated data ( either it can be empty data or complete data) either via api call or via prop changes use the appropriate life cycle method such as componentWillReceiveProps or componentDidMount to get the latest data and again set the state with latest data.
For example when data is received via api call ->
constructor() {
this.state = {
data : []
}
}
componentDidMount()
{
this.getFunction();
}
getFunction = () => {
this.ApiCall()
.then(
function(data){
console.log(data);
// set the state here
this.setState({data:data});
},
function(error){
console.log(error);
}
);
}
So at the time of initial render your data will be either empty object or empty array and you will call appropriate rendering method for that accordingly.
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
data : []
}
}
componentWillMount() {
this.setState({ data : this.props.dp });
}
renderFromProps() {
return this.state.data
.map((dpElem) =>
<h3>{dpElem.name}</h3>
);
}
render() {
return (
<div>
<h1> Rendering Array data </h1>
<hr/>
{ this.renderFromProps()}
</div>
);
}
}
const dynamicProps = [{ name:"Test1", type:"String", value:"Hi1" },
{ name:"Test2", type:"String", value:"Hi2" },
{ name:"Test3", type:"String", value:"Hi3" }
];
ReactDOM.render(
<Test dp={dynamicProps} />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root">
</div>

Resources