Auto Update value in react component - javascript

I'm trying to build a component with auto-updating value based on cookies:
let cookies = 0;
(function count() {
cookies = document.cookie.split("?");
setTimeout(count, 10);
return cookies;
})();
class CartButton extends React.Component {
state = {quantity: cookies.length}
render() {
return (
<Cart onClick={e=>{show_cart()}}>
<Mfont>{this.state.quantity}</Mfont>
<Icon>shopping_cart</Icon>
</Cart>
);
}
}
'count' function works as expected, component is rendered with the latest value returned. Unfortunately, it does not auto-update when 'cookies' are changed. It returns this error:
Warning: render(...): Replacing React-rendered children with a new root component. If you intended to update the children of this node, you should instead have the existing children update their state and render the new components instead of calling ReactDOM.render.
I have tried various variations here but still can't figure it out :/

componentDidMount will get execute only once when your component loads first time. This is the correct place to write any logic which we need to execute after page load.
Try this,
class CartButton extends React.Component {
//It is good to have a constructor for the component which has state
constructor(props){
super(props);
this.state = {quantity: cookies.length}
this.updateQuantity;
}
componentDidMount(){
this.updateQuantity = setInterval(()=> {
cookies = document.cookie.split("?");
this.setState({quantity: cookies.length})
},10)
}
//Don't forget to clear any setInterval like below
componentWillUnmount(){
clearInterval(this.updateQuantity);
}
render() {
return (
<Cart onClick={e=>{show_cart()}}>
<Mfont>{this.state.quantity}</Mfont>
<Icon>shopping_cart</Icon>
</Cart>);
}
}

Here your CartButton is not updating even though count is working fine because CartButton is not listening to your cookies variable. React component updates only when there is either props or state change.
You can something like this..
class CartButton extends React.Component {
state = {quantity: cookies.length}
componentDidMount(){
setInterval(function count() {
cookies = document.cookie.split("?");
this.setState({quantity: cookies})
}.bind(this), 10)
}
render() {
return (
<Cart onClick={e=>{show_cart()}}>
<Mfont>{this.state.quantity}</Mfont>
<Icon>shopping_cart</Icon>
</Cart>);
}
}

Related

ref undefined React

So the problem I am having is that I am trying to print the textContent of my ref every 5 seconds, and this works the very first time typeWrite() is called from componentDidMount(), but when it is called recursively (using setTimeout()), I get an error saying this.intro.current is undefined, even though it was defined the first time the function ran.
I want to keep the structure relatively similar (I don't want to change it too much) because there are other things I have left out that rely on this structure.
export default class Home extends Component {
constructor(props) {
super(props);
this.intro = React.createRef();
}
componentDidMount() {
this.typeWrite();
}
typeWrite() {
console.log(this.intro.current.textContent);
setTimeout(this.typeWrite, 5000);
}
render() {
return (
<div className="intro" ref={this.intro}>Text</div>
)
}
}
You need to bind your function to your component.
constructor(props) {
super(props);
this.intro = React.createRef();
this.typeWrite = this.typeWrite.bind(this);
}
or you need to call your function with arrow function.
typeWrite() {
console.log(this.intro.current.textContent);
setTimeout(() => this.typeWrite(), 5000);
}

shouldComponentUpdate() is not being called

Problem
I've parent class which contains list of items and renders component for each item of the list. When some item has been changed (even only one), all items in the list are being rerendered.
So I've tried to implement shouldComponentUpdate(). I am using console.log() to see if it is called but I can't see the log. I've found question shouldComponentUpdate is not never called and tried to return return (JSON.stringify(this.props) !=JSON.stringify(nextProps)); but component still renders itself again. So I've tried just to return false (like do not ever update) but it still does. As the last try I've used PureComponent but it is still being rerendered.
Question
How can I stop children re-rendering if the parent list changes and why is ShouldComponentUpdate never called?
Edit
I've noticed something what I didn't mention in question, I'm sorry for that. I am using context. If I don't use context -> it's ok. Is there any chance to stop re-render while using context? (I'm not using context on updated item - values of context didn't change).
Example
I've parent class which iterates list and renders TaskPreview component for each item of list:
class Dashboard extends React.Component
{
constructor(props) {
this.state = {
tasks: {},
};
}
onTaskUpdate=(task)=>
this.setState(prevState => ({
tasks: {...prevState.tasks, [task._id]: task}
}));
// ... some code
render() {
return (
<div>
{(!Object.entries(this.props.tasks).length)
? null
: this.props.tasks.map((task,index) =>
<TaskPreview key={task._id} task={task} onChange={this.onTaskUpdate}/>
})}
</div>
)
}
}
and I've children TaskPreview class:
class TaskPreview extends React.Component
{
shouldComponentUpdate(nextProps) {
console.log('This log is never shown in console');
return false; // just never!
}
render() {
console.log('task rendered:',this.props.task._id); // indicates rerender
return(<div>Something from props</div>);
}
}
TaskPreview.contextType = TasksContext;
export default TaskPreview;
As #Nicolae Maties suggested I've tried to use Object.keys for iteration instead of direct map but it still doesn't call "shouldComponentUpdate" and still being re-rendered even if there is no changes.
Updated code:
render() {
return (
<div>
{(!Object.entries(this.props.tasks).length)
? null
: Object.keys(this.props.tasks).map((key,index) => {
let task = this.props.tasks[key];
<TaskPreview key={task._id} task={task}/>
}
})}
</div>
)
}
Component is being re-rendered because of .contextType.
TaskPreview.contextType = TasksContext;
Also as is mentioned in documentation:
The propagation from Provider to its descendant consumers (including .contextType and useContext) is not subject to the shouldComponentUpdate method, so the consumer is updated even when an ancestor component skips an update. Source: reactjs.org/docs/context
You have to use context somehow else or do not use it at all.
You can use Context.Consumer which won't force re-render of current component but it might force re-render of its children.
<TasksContext.Consumer>
{value => /* render something based on the context value */}
</TasksContext.Consumer>
Instead of return (JSON.stringify(this.props) != JSON.stringify(nextProps)); in your shouldComponentUpdate() life cycle, try specifying tasks object like this return (JSON.stringify(this.props.tasks) != JSON.stringify(nextProps.tasks));
Maybe react is creating new instances of your component and replaces the old instances with them. That's why you're probably not getting your lifecycle method invoked. That can happen if the key property you're assigning in the map always changes.
use from pureComponent and array as state:
class Dashboard extends React.PureComponent
{
constructor(props) {
this.state = {
tasks: this.props.tasks
}
}
onTaskUpdate=(task)=>
this.setState(prevState => ({
tasks: [...prevState.tasks, task] // render only new task
}));
render() {
const {tasks} = this.state
return (
<div>
{tasks.map(task => <TaskPreview key={task._id} task={task} />)}
</div>
)
}
}
class TaskPreview extends React.PureComponent
{
render() {
console.log('task rendered:',this.props.task._id); // indicates rerender
return(<div>Something from props</div>);
}
}
In the shouldComponentUpdate() method of your TaskPreview component, you should check if the next props have changes in comparison to the current props. Then if there are changes, return true to update the component, otherwise false.
The following example compares all the fields of props object with the new props object. But you can only check the props you are interested in.
shouldComponentUpdate(nextProps) {
return !!(Object.keys(nextProps).find(key => nextProps[key] !== this.props[key]));
}
I tried with below code snippet, shouldComponentUpdate worked as I expected. Could you share your Dashboard initial props ?
class Dashboard extends React.Component {
constructor(props) {
this.state = {
tasks: {}
};
}
onTaskUpdate = task =>
this.setState(prevState => ({
tasks: { ...prevState.tasks, [task._id]: task }
}));
// ... some code
render() {
return (
<div>
{!Object.entries(this.props.tasks).length
? null
: Object.keys(this.props.tasks).map((key, index) => {
let task = this.props.tasks[key];
return (
<TaskPreview
key={task._id}
task={task}
onChange={this.onTaskUpdate}
/>
);
})}
</div>
);
}
}
class TaskPreview extends React.Component {
shouldComponentUpdate(nextProps) {
console.log("This log is never shown in console");
return nextProps.task._id != this.props.task._id;
}
render() {
console.log("task rendered:", this.props.task); // indicates rerender
return (
<button onClick={() => this.props.onChange(this.props.task)}>
Something from props
</button>
);
}
}
my initial props for Dashboard component is :
<Dashboard tasks={{test:{_id:'myId', description:'some description'}}}/>

React: How can I call a method only once in the lifecycle of a component, as soon as data.loading is false

I have a React component with a prop 'total' that changes every time the component is updated:
function MyView(props) {
const total = props.data.loading ? 0 : props.data.total;
return (
<p> total </p>
);
}
The first time the component mounts the total is say 10. Every time the component is updated because of a prop change the total goes up.
Is there a way I can display the original total (in this example 10)?
I have tried setting it in this.total inside componentDidMount, but props.data.total is not yet available when componentDidMount is called. Same with the constructor. The total only becomes available when props.data.loading is false.
In order to get access to lifecycle features, you must move from function, stateless component, to a class component.
in the below example, InitialTotal is initialized in the construstor lifecycle method and it never changes.
currentTotal, is incremented each time the render function is called - when the component is re-rendered (because of props change or state changes)
it should look something like that:
class MyView extends React.Component {
constructor(props) {
super(props)
this.initialTotal = 10;
this.currentTotal = 10;
}
render() {
this.currentTotal+=1;
return (
<p>InitialToal: {this.initialTotal}</p>
<p>Current Total: {this.currentTotal}</p>
);
}
}
You could create a stateful component and store the initial total in the component state.
Example
class MyView extends React.Component {
state = {
initialTotal: this.props.total
};
render() {
const { total } = this.props;
const { initialTotal } = this.state;
return (
<div>
<p> Total: {total} </p>
<p> Initial total: {initialTotal} </p>
</div>
);
}
}
class App extends React.Component {
state = {
total: 10
};
componentDidMount() {
this.interval = setInterval(() => {
this.setState(({ total }) => {
return { total: total + 1 };
});
}, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return <MyView total={this.state.total} />;
}
}
If I understand your requirements correctly...
function MyView(props) {
// if you only need to set the value once on load just use useState.
// the const total will be the value you pass in to useState.
const [total, setTotal] = useState(props.data.loading ? 0 : props.data.total)
// if its possible that the value is not available on initial load and
// you want to set it only once, when it becomes available, you can use
// useEffect with useState
useEffect(() => {
// some condition to know if data is ready to set
if (!props.data.loading) {
setTotal(props.data.total)
}
}, [props.data.total, setTotal, props.data.loading]
// this array allows you to limit useEffect to only be called when
// one of these values change. ( when total changes in this case,
// as the const function setTotal will not change
// ( but react will fuss if its used and not in the list ).
return (
<p> {total} </p>
);
}
I have the same need. With a functional component, I need to store the inital snapshot of states, let user play with different state values and see their results immediately, eventually, they can just cancel and go back to the initial states. Apply the same structure to your problem, this is how it looks:
import React from 'react';
import { useEffect, useState } from "react";
const TestView = (props: { data: any }) => {
// setting default will help type issues if TS is used
const [initialTotal, setInitialTotal] = useState(props.data.total)
useEffect(() => {
// some condition to know if data is ready to set
setInitialTotal(props.data.total);
// Critical: use empty array to ensure this useEffect is called only once.
}, [])
return (
<div>
<p> { initialTotal } </p>
<p> { props.data.total } </p>
</div>
);
}
export default TestView
You can use getDerivedStateFromProps life cycle method.
static getDerivedStateFromProps(props, state){
if(props.data.total && (props.data.total==10)){
return {
total : props.total // show total only when its 10
}
}else{
return null; // does not update state
}
}

Child Components are not being re-rendered

I'm new to React. As a learning exercise I'm building an chess application
I want to change the DOM of child based on state in parent. Currently there is no change in child components on change of state in parent.
Parent Component
class Game extends Component{
constructor(){
super();
this.state = {
game :{
board_position = { 'One' : ''}
}
}
this.handleClick = this.handleClick.bind(this);
}
handleClick(){
let p = this.state.game.board_position;
// some computations on p
p = { board_position : { 'One' : Math.random() } }
this.setState({
game : p
})
//this.forceUpdate(); // Just trying different methods to make it work
}
render(){
return(
<div onClick={(e) => this.handleClick(e) }>
<Piece {...this.state.game.board_position} />
</div>
)
}
}
Child Component
Class Piece extends Component{
constructor(){
super(props);
this.state = {
clr : ''
}
}
componentWillMount(){
let c;
// some computations that change value of c based on props
if( typeof ( this.props.game.one) == number ){
c = 'Red'
} else {
c = 'Blue'
}
this.setState({
clr : c
})
}
render(){
return(
<span>{this.state.clr}</span>
)
}
}
On call of handle click method, there is a change in state of the game. However, subsequent changes in Child are not seen.
Can anyone please help me out? Where am I going wrong?
I'm not sure how to implement ref as suggested over here. However, I don't think that is a good solution. Probably I'm having some conceptual misunderstanding.
PS: Please ignore any syntax error. This code is a strip down of real code.
If you want to take a look at full code - go over here
componentWillMount in your Piece component will only fire once.
Are you updating your state each time the props change as well ? For example using componentWillReceiveProps
Also you could just display the props directly in your render function
render(){
return(
<span>{use the props directly here}</span>
)
}
Try to avoid using state whenever possible, and stick with just rendering your component based on props.
it's a pretty basic react process:
class Game extends Component{
constructor(){
super();
this.state = {
game :{
board_position = { 'One' : ''}
}
}
this.handleClick = this.handleClick.bind(this);
}
handleClick(e){
let p = this.state.game.board_position;
// some computations on p
p = { board_position : { 'One' : Math.random() } }
this.setState({
game : p
})
}
render(){
return(
<div onClick={this.handleClick}>
<Piece board_position={this.state.game.board_position} />
</div>
)
}
}
Child: //can be a function instead of a class
const Piece = ({board_position}) =>{
const {board_position} = this.props;
let clt = board_position;
//do some stuff
return(
<span>{board_position}</span>
);
};
if you want to do more calculations you can use componentWillReceiveProps(newProps)
A component re render when it's props or states get changed. There will be certain life cycle methods get invoked based on the values of props and own states.
The way you accessed your **props is wrong.**
It should be
this.props.One
Life cycle method you should have used is componentWillReciveProps(nextProps). In that case you should access the relevant prop like below
nextProps.One
I have created a working fiddle (your code had errors if you fixed those and looked at your log you could have easily figure out where the error is)

Rendering react component after api response

I have a react component that I wish to populate with images using the Dropbox api. The api part works fine, but the component is rendered before the data comes through & so the array is empty. How can I delay the rendering of the component until it has the data it needs?
var fileList = [];
var images = [];
var imageSource = [];
class Foo extends React.Component {
render(){
dbx.filesListFolder({path: ''})
.then(function(response) {
fileList=response.entries;
for(var i=0; i<fileList.length; i++){
imageSource.push(fileList[0].path_lower);
}
console.log(imageSource);
})
for(var a=0; a<imageSource.length; a++){
images.push(<img key={a} className='images'/>);
}
return (
<div className="folioWrapper">
{images}
</div>
);
}
}
Thanks for your help!
Changes:
1. Don't do the api call inside render method, use componentDidMount lifecycle method for that.
componentDidMount:
componentDidMount() is invoked immediately after a component is
mounted. Initialization that requires DOM nodes should go here. If you
need to load data from a remote endpoint, this is a good place to
instantiate the network request. Setting state in this method will
trigger a re-rendering.
2. Define the imageSource variable in state array with initial value [], once you get the response update that using setState, it will automatically re-render the component with updated data.
3. Use the state array to generate the ui components in render method.
4. To hold the rendering until you didn't get the data, put the condition inside render method check the length of imageSource array if length is zero then return null.
Write it like this:
class Foo extends React.Component {
constructor(){
super();
this.state = {
imageSource: []
}
}
componentDidMount(){
dbx.filesListFolder({path: ''})
.then((response) => {
let fileList = response.entries;
this.setState({
imageSource: fileList
});
})
}
render(){
if(!this.state.imageSource.length)
return null;
let images = this.state.imageSource.map((el, i) => (
<img key={i} className='images' src={el.path_lower} />
))
return (
<div className="folioWrapper">
{images}
</div>
);
}
}
You should be using your component's state or props so that it will re-render when data is updated. The call to Dropbox should be done outside of the render method or else you'll be hitting the API every time the component re-renders. Here's an example of what you could do.
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = {
imageSource: []
}
}
componentDidMount() {
dbx.filesListFolder({ path: '' }).then(function(response) {
const fileList = response.entries;
this.setState({
imageSource: fileList.map(file => file.path_lower);
})
});
}
render() {
return (
<div className="folioWrapper">
{this.state.imageSource.map((image, i) => <img key={i} className="images" src={image} />)}
</div>
);
}
}
If there are no images yet, it'll just render an empty div this way.
First off, you should be using the component's state and not using globally defined variables.
So to avoid showing the component with an empty array of images, you'll need to apply a conditional "loading" class on the component and remove it when the array is no longer empty.

Categories

Resources