Modifying state object directly re rendering the component in ReactJS - javascript

I was going through ReactJS documentation. I came across following concept in State and lifecycle section which says
Do Not Modify State Directly For example, this will not re-render a
component:
// Wrong
this.state.comment = 'Hello';
https://reactjs.org/docs/state-and-lifecycle.html
I tried to implement the same behavior and saw that the component got re rendered
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {
time : new Date(),
note: "Time is-"
}
}
componentDidMount() {
this.timerId = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearTimer(this.timerId);
}
render() {
return <h1>{this.state.note}{this.state.time.toLocaleTimeString()}</h1>
}
tick() {
this.state.note = "Dude!! Time is";
this.setState({
time : new Date()
})
}
}
ReactDOM.render(
<Clock/>,
document.getElementById('root')
);
The text got re rendered to "Dude Time is" from "Time is"
Can someone explain this? This behavior goes against what react documentation says

This is working because you are also doing the setState after this.state.note = "Dude!! Time is". If you remove the setState call after this line, the example won't work.
Here is the link to the codesandbox. I've removed the setState call.
https://codesandbox.io/s/50r500j62p

Related

I made a clock class in JavaScript, but it's not working properly when imported. I can only get the seconds and the interval doesn't seem to work too

import React, { Component } from 'react';
import { Text, View } from 'react-native';
export default class Clock extends Component {
componentDidMount(){
setInterval(() => (
this.setState(
{ curHours : new Date().getHours()}
),
this.setState(
{ curMins : new Date().getMinutes()}
),
this.setState(
{ curSeconds : new Date().getSeconds()}
)
), 1000);
}
state = {curHours:new Date().getHours()};
state = {curMins:new Date().getMinutes()};
state = {curSeconds:new Date().getSeconds()};
renderHours() {
return (
<Text>{'Hours:'}{this.state.curHours}</Text>
);
}
renderMinutes() {
return (
<Text>{'Minutes:'}{this.state.curMinutes}</Text>
);
}
renderSeconds() {
return (
<Text>{'Seconds:'}{this.state.curSeconds}</Text>
);
}
}
-I'm trying to make an app that can keep track of time kinda like a daily planner. So I need to get the current time in real time during app run time. The app is supposed to tell the user that they have failed to accomplish a certain task in a given time for example. I tried exporting the clock.js and using its functions but only the renderSeconds() is working, the others are only showing blanks.
I think functional components will be much simpler to solve this, but it's just my opinion.
Here's a link to an example
When you're defining state three times, only the last one is persisted because you're overwriting the previous variable. In addition, your initial state should be declared in a constructor. Add this to the top of your class
constructor() {
this.state = {
curHours:new Date().getHours(),
curMins:new Date().getMinutes(),
curSeconds:new Date().getSeconds(),
}
}

ReactJS - Destroy old Component-Instance and create new

I've got a may confusing question because it does not fit standard-behaviour how react and the virtual dom works but i would like to know the answer anyway.
Imagine i've got a simple react-component which is called "Container".
The Container-component has a "div" inside of the render-method which contains another component called "ChildContainer". The "div" which surrounds the "ChildContainer" has the id "wrappingDiv".
Example:
render() {
<Container>
<div id="wrappingDiv">
<ChildContainer/>
</div>
</Container
}
How can i destroy the "ChildContainer"-component-instance and create a completly new one. Which mean the "ComponentWillUnmount" of the old instance is called and the "ComponentDidMount" of the new component is called.
I don't want the old component to update by changing the state or props.
I need this behaviour, because an external library from our partner-company got a libary which change the dom-items and in React i'll get a "Node not found" exception when i Update the component.
If you give the component a key, and change that key when re-rendering, the old component instance will unmount and the new one will mount:
render() {
++this.childKey;
return <Container>
<div id="wrappingDiv">
<ChildContainer key={this.childKey}/>
</div>
</Container>;
}
The child will have a new key each time, so React will assume it's part of a list and throw away the old one, creating the new one. Any state change in your component that causes it to re-render will force that unmount-and-recreated behavior on the child.
Live Example:
class Container extends React.Component {
render() {
return <div>{this.props.children}</div>;
}
}
class ChildContainer extends React.Component {
render() {
return <div>The child container</div>;
}
componentDidMount() {
console.log("componentDidMount");
}
componentWillUnmount() {
console.log("componentWillUnmount");
}
}
class Example extends React.Component {
constructor(...args) {
super(...args);
this.childKey = 0;
this.state = {
something: true
};
}
componentDidMount() {
let timer = setInterval(() => {
this.setState(({something}) => ({something: !something}));
}, 1000);
setTimeout(() => {
clearInterval(timer);
timer = 0;
}, 10000);
}
render() {
++this.childKey;
return <Container>
{this.state.something}
<div id="wrappingDiv">
<ChildContainer key={this.childKey}/>
</div>
</Container>;
}
}
ReactDOM.render(
<Example />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.2/umd/react-dom.production.min.js"></script>
Having said that, there may well be a better answer to your underlying issue with the plugin. But the above addresses the question actually asked... :-)
Using hooks, first create a state variable to hold the key:
const [childKey, setChildKey] = useState(1);
Then use the useEffect hook to update the key on render:
useEffect(() => {
setChildKey(prev => prev + 1);
});
Note: you probably want something in the array parameter in useEffect to only update the key if a certain state changes

Using a global JS Object as the state for a React component

We're building a simulation tool and we are trying to replace our current implementation of how our popups are handled using React.
The issue is that the state of our popup component is set to
this.state = connections[this.props.id]
that object is a global object that exists, gets created and update in a separate js file and if I go into the console and change connections[this.props.id].name from "junction 15" to "junction 12", the changes are not rendered immediately. I have to close and reopen the popup so it renders with the correct information.
This is something our architect wants, and the way he explained it was that he needs any changes made to our connections object outside of react NEED to reflected within our popup if it's open, but if the state is set to the marker and I modify the name of the marker in the object through the console, i dont understand why it's not automatically being updated in React
I've looked at trying to use the lifecycle methods, redux, mobx, js proxies, react context but I'm still learning and I think I'm making this more complicated than it should be.
Here's our simple popup with components:
let globalValue = 'initial'
class ReactButton extends React.Component {
constructor(props) {
super(props);
this.state = connections[this.props.id];
this.changeName = this.changeName.bind(this);
}
updateOutsideReactMade() {
this.setState(state);
// this.forceUpdate();
}
changeName(newName) {
connections[this.props.id].name = newName;
this.setState(connections[this.props.id]);
}
// ignore this, this was my attempt at using a lifecycle method
//componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
// if (this.props.name !== prevProps.name) {
// this.setState(this.props.name);
// }
//}
render() {
return (
<div>
<Input onChange={this.changeName} />
<Header name={this.state.name}
id={this.state.id}
/>
</div>
);
}
}
function renderReactButton(iddd, type){
ReactDOM.render(
<ReactButton id={iddd} />,
document.getElementById(`react-component-${type}-${iddd}`)
);
}
class Header extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<h1>{this.props.name}
{this.props.id}</h1>
);
}
}
class Input extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const name = e.target.value;
this.props.onChange(name);
}
render() {
return (
<input onChange={this.handleChange}/>
);
}
}
So my question is how am i able to use an object (connections) that is global as my state for react AND if something modifies the data outside of React that it would be reflected on DOM. Right now, we have it working to where we can change the name through the react popups, but if we change the name through the console it will not update. Thank you guys!
****UPDATE**** 8/15/18
I wrapped each new object as a proxy as it was entered in my array.
connections[key] = new Proxy(polyLine, handleUpdatesMadeToMarkersOutsideOfReact);
I setup a handler:
let handleUpdatesMadeToMarkersOutsideOfReact = {
get: (connections, id) => {
return connections[id];
},
set: (connections, id, value) => {
//trigger react re-render
console.log('inside set');
//trigger react to update
return true;
}
};
Now I'm stuck trying to get the handler to trigger my react component to update. I created a class function for my component that forced the update but I was having a hard time accessing it with the way we have it setup.
Normally state is an object - giving existing object is ok. React requires setState usage to be able to process lifecycle, f.e. render with updated state. Modyfying state object from console doesn't let react to react ;)
You need some kind of observer, sth to tell react than data changed and to force render (call this.forceUpdate()).

React - method run right times via state but run double times when Parent Component changing state

I am trying to build a page with some data initialized at first time mounted, and update when websocket server give a response msg when certain button click event is triggered, also I need to ban the button aka. disabled, and tell the user in how many seconds the button is clickable again.
My first thought is, single component, update via states, give a state to the counter, then use setTimeout to count down 1 every 1000ms, turned out that the counter "banCount" worked well, until I add the websocket.send(), then it counted down 2 every time.
I thought that would be because when the websocket server responsed, the state is change, so the whole component is updated, the counter is messed up.
So, I had an idea, separating it into a child component, with its own state, but do nothing when in the life cycle of componentWillReceiveProps, and it will not receive props, so it will just work with it is own state. But the result is with or without separating the counter into a child component, they worked the same.
parent component:
import React from 'react';
import ReactDOM from 'react-dom';
import TestChild from './testChild/testChild';
class TestParent extends React.Component {
constructor(props) {
super(props);
this.state = {
wsData: null,
};
}
componentWillMount() {
this.wsClient = new WebSocket("ws://localhost:9000/server", 'echo-protocol');
this.wsClient.onmessage = msg => {
if (msg) {
this.setState({
wsData: msg.data
});
}
};
}
render() {
const data = () => {
if (this.state.wsData) {
return this.state.wsData;
} else {
return "waiting data";
}
};
return (
<div>
<div>{data()}</div>
<TestChild wsClient={this.wsClient}/>
</div>
);
}
}
ReactDOM.render(
<TestParent />,
document.getElementById('reactWrapper')
);
and the Child Component:
import React from 'react';
class TestChild extends React.Component {
constructor(props) {
super(props);
this.count = null;
this.state = {
banCount: this.count
};
this.wsClient = this.props.wsClient;
this.countupdate = 0;
}
banCount() {
this.setState({
banCount: this.count
});
}
callNext(n) {
this.wsClient.send('can you hear me');
this.count = n;
this.banCount();
}
componentDidUpdate() {
if (this.count > 0) {
setTimeout(() => {
this.count -= 1;
this.banCount();
}, 1000);
} else if (this.count === 0) {
this.count = null;
this.banCount();
}
}
render() {
return <button onClick={() => this.callNext(3)}>click me {this.state.banCount}</button>;
}
}
export default TestChild;
Please ignore 'whether the server and websocket connection' works part, they are fine.
I don't know why, I even had not updated Child component, I am really new to React, I really do not know how to debug this, I read this code for hours, but it is just too complicated for me.
Why it counted down 2 every time? and for sure I am wrong, what is the right way.
Please help me with only React and vanilla Javascript, I had not use Redux or Flux and even did not know what they are, thank you.
This is NOT tested code, but should help you to build what you want, I didn't tested your component but I suspect that your setTimeout() is called several times.
import React from 'react';
class TestChild extends React.Component {
constructor(props) {
super(props);
this.state = {
count: null,
};
}
startCountDown() {
var newCount = this.state.count -1;
if(newCount === 0){
clearTimeout(this.timer);
}
this.setState({
count: newCount,
});
}
callNext(n) {
this.wsClient.send('can you hear me');
this.setState({
count: n,
});
this.timer = setTimeout(() => {
startCountDown();
}, 1000);
}
componentWillUnmount() {
clearTimeout(this.timer);
}
render() {
return <button disabled={this.state.count>0} onClick={() =>
this.callNext(3)}>click me {this.state.count}</button>;
}
}
export default TestChild;
Finally I worked it out.
It is because React will re-render all the child component with or without setting children's new states. The only way to stop it from re-render is to use ShouldComponentUpdate, so:
shouldComponentUpdate() {
return this.state.banCount !== null;
}
will work, as when the child component receiving props after websocket.send(), this.count is still null, but right after the websocket.send(), this.count is set to 3, so the child component will update since.
Also another workround:
callNext(n) {
this.wsClient.send('can you hear me');
this.count = n;
}
componentWillReceiveProps(nextProps) {
this.data = nextProps.datas;
this.setState({
banCount: this.count
});
}
in this workround, without shouldComponentUpdate() the child component will always re-render when its parent receive websocket data, so in the click handler function, stop calling bancount(), so it would not update itself, but set the state when receive nextProps, that will trigger the re-render.
To sum all above:
child component will always re-render with or without setting state via new props unless shouldComponentUpdate return false, I alreay called bancount() in the click handler function, trigger child component to update the state itself, but after the parent component receiving websocket data, it triggered state updating again, that is why it run double times.

Where to apply my moment() function in a react component?

I am trying to do a clock component, simply to give the date and time in local format in a webpage. I imported MomentJS using the command line npm i moment --save in my webpack environment. Next I wrote this in my Clock.jsx component (mostly based on the React example on the website).
import React from 'react';
import Moment from 'moment';
export default class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {
dateTimestamp : Date.now()
};
}
tick = () => {
this.setState({dateTimestamp: this.state.dateTimestamp + 1});
console.log('tick');
}
componentDidMount() {
this.interval = setInterval(this.tick, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
const date = this.state.dateTimestamp;
return(
<div className="clock"> Heure locale : {date}</div>
);
}
}
Doing this the timestamp incremented correctly. However, when passing a new state element in the object, the first value (based on Date.now() ) is calculated in the constructor but for each tick, only the timestamp is incrementing the formatted date is stuck on its first value. Here is the code.
import React from 'react';
import Moment from 'moment';
export default class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {
dateTimestamp : Date.now(),
dateFormatted : Moment(Date.now()).toString()
};
}
tick = () => {
this.setState({dateTimestamp: this.state.dateTimestamp + 1});
console.log(this.state.dateTimestamp);
this.setState({dateFormatted: Moment(this.state.dateTimestamp).toString()});
console.log(this.state.dateFormatted);
}
...
render() {
const date = this.state.dateFormatted;
return(
<div className="clock"> Heure locale : {date}</div>
);
}
}
Does anyone could explain help me solving this issue but above all tell me what is going wrong with my piece of code?
Thanks
UPDATE: In the end my use of moment was not appropriate, even if I cannot figure out why it would not work this way. Find below my correct implementation to have the date and time refreshed every seconds.
import React from 'react';
import Moment from 'moment';
export default class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {
dateFormatted : Moment().locale('fr').format('dddd Do MMMM YYYY HH:mm:ss').toString()
};
}
tick = () => {
this.setState({
dateFormatted : Moment().locale('fr').format('dddd Do MMMM YYYY HH:mm:ss').toString()
});
}
componentDidMount() {
this.interval = setInterval(this.tick, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
const date = this.state.dateFormatted;
return(
<div className="clock"> Date (locale) : {date}</div>
);
}
}
This also "solves" the anti pattern issue exposed below (different cross-dependant setState() call). I would need the timestamp for any other reason but I will find a workaround.
#KrzysztofSztompka is correct, but I would add that maintaining two separate state variables to represent the current date as a number and as a formatted string is an antipattern. Derived state variables (i.e., state variables that can be calculated using another state variable) increases the responsibility on the developer to always keep the two state variables in sync. That may not seem too difficult in this simple example, but it can become more difficult in larger, more complicated components/apps. Instead, it is generally considered better practice to maintain one source of truth and calculate any derived values on the fly as you need them. Here's how I would apply this pattern to your example.
import React from 'react';
import Moment from 'moment';
export default class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {
dateTimestamp : Date.now()
};
this.tick = this.tick.bind(this);
}
tick() {
this.setState({
dateTimestamp: this.state.dateTimestamp + 1
});
}
componentDidMount() {
this.interval = setInterval(this.tick, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
// Calculate the formatted date on the fly
const date = Moment(this.state.dateTimestamp).toString();
return(
<div className="clock"> Heure locale : {date}</div>
);
}
}
change your tick function to this:
tick = () => {
var timestamp = this.state.dateTimestamp + 1;
this.setState({
dateTimestamp: timestamp,
dateFormatted: Moment(timestamp).toString()
});
}
This is because from docs :
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value.
Therefore in your next setState call it use old value. My proposition change this two values at once.

Categories