So I've got two react components, and for some reason one of them is running again (and causing a nasty bug) when I click another to go to another component.
My guess is that this is because I am running some asynchronous code for geolocation in my component constructor, but I don't know enough about React to be 100% certain of this.
The showPosition method makes an API call based on a user's location and other variables.
class Cards extends Component {
constructor() {
super();
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(this.showPosition);
}
this.showPosition = this.showPosition.bind(this);
}
...
render() {
if (this.state.isLoading) {
return <Loading />;
}
// The lines below this ALWAYS get run, even several seconds after I
// have been on my new component!
console.log("state value new");
console.log(this.state.data);
return (
<Page className="main-page">
<div className="cards">
{this.state.data.merchants.map( (merchant, index) =>
<CardRow
merchant={{merchant}}
count={index}
className={'card-color-' + index}
/>
)}
</div>
</Page>
);
}
}
This cards component creates a child component called CardRow, and then that component creates several Card and PromoCard component children.
I won't link the full card, but the way I am accessing the component that breaks is this way - a user clicks the link, and is directed to the chat component:
<Link to={{
pathname: "/chat/" + this.state.merchant.id,
state: {merchant: this.state.merchant}
}}>
I made a toy component for chat, and everything loads fine, but then the render function in <Cards /> runs again, which messes up my entire chat interface.
Why is this happening? Is it related to my geolocation code in my constructor? Something else potentially?
You have a memory leak, it is not a good practice to set listeners in your constructor.
You must use lifecycle methods (componentDidMount, componentWillUnmount, etc...)
the componentDidMount lifecycle method is the right place to set a listener
constructor() {
this.showPosition = this.showPosition.bind(this);
this.unmounted = false;
}
componentDidMount() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(this.showPosition);
}
}
componentWillUnmount() {
// indicate that the component has been unmounted
this.unmounted = true;
}
showPosition() {
// exiting early if the component was unmounted
// prevent any update using setState
if (this.unmounted) { return; }
// more code...
}
Related
I tried Ref and forwarding-refs in Reactjs to keep a reference to a DOM or React Component. I did not understand why the ref object in which created kept correct target reference before target component was rendered or mounted.
I have the codesandbox right here to present my question more details. Here is the screenshot
As what you see, Ref object keep a correct reference to the target (in this case is FancyButton Component) even the render and ComponentDidMount method of target have not yet fired.
Could someone possibly help me to understand about this more. Thanks.
Because console keep reference to current value to object (so you get value of object after rendering). If you will change your code to
console.log(JSON.stringif(ref))
Then you will get this:
]1
You are console logging in quite a few wrong places, namely the console.log("render of Fancybutton"); in the render method of FancyButton and in the function body of forwardRef of the FancyHOC. The render method should be a pure function without side-effect. Console logging is considered a side-effect.
Study this react component lifecycle diagram:
Notice where the render function resides. It resides in the "Render Phase" of the render cycle. Notice also that it "May be paused, aborted or restarted by React." This means they are called before anything in the "Commit Phase".
Notice now as well that the other component lifecycle methods, specifically componentDidMount and its functional component coutnerpart useEffect with empty dependency array are all called after the component has rendered (committed to DOM) at least once.
Fix the logging in the incorrect places:
FancyButtonHOC
Move the logs into componentDidUpdate and useEffect hook.
function createFancyButtonHOC(WrappedComponent) {
class FancyHOC extends React.Component {
render() {
const { forwardRef, ...rest } = this.props;
return <WrappedComponent ref={forwardRef} {...rest} />;
}
componentDidMount() {
console.log("FancyHOC mounted");
}
componentDidUpdate() {
console.log("render of HOC: ", this.props.forwardRef);
}
}
return React.forwardRef((props, ref) => {
React.useEffect(() => {
console.log("forwardRef callback: ", ref);
});
return <FancyHOC forwardRef={ref} {...props} />;
});
}
In FancyButton if you leave the console log in the render method it will simply log any time react is invoking the render method for DOM diffing purposes, not actually when it is rendered to the DOM during the commit phase. Move it to componentDidUpdate.
export default class FancyButton extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
componentDidMount() {
console.log("fancy button mounted");
}
componentDidUpdate() {
console.log("render of Fancybutton");
}
handleClick() {
console.log("button click");
}
render() {
return <button onClick={this.handleClick}>Click me</button>;
}
}
New console log output
render of HOC: {current: FancyButton}
fancy button mounted
FancyHOC mounted
forwardRef callback: {current: FancyButton}
I'm tracking when componentDidUpdate and render are firing with log statements.
The log statements in componentDidUpdate do not fire after render. I have used breakpoints to confirm this isn't a timing issue.
I'm using "render props" to wrap the component in question. My code (stripped down) is below. This is the output of the logging. Sometimes I'll get componentDidUpdate to fire, but inconsistently and it's never the final thing, a RENDER always shows up in my logs last, never UPDATE.
As I understand it componentDidUpdate should fire even if the update does not modify the DOM (though the renders here do update the DOM.) I've tried React#16.11.x and React#16.12.x with identical results.
class MyWrapper extends React.PureComponent {
render() {
const { buttonDefinitions } = this.props;
return (
<InfoProvider
render={infoProps => {
return (
<MyMenu
{...{ buttonDefinitions, infoProps }}
/>
);
}}
/>
);
}
}
class MyMenu extends React.Component {
componentDidUpdate() {
log.warn('UPDATE');
}
render() {
log.warn('RENDER');
const { buttonDefinitions } = this.props;
return (
<MenuWrapper>
{buttonDefinitions.map(buttonDef => (
<MyButton {...buttonDef} />
))}
</MenuWrapper>
);
}
}
As per react docs, if you are using render props with React pure component, then shallow prop comparison will always return false for new props. In this case, for each render it will generate a new value for the render prop. As new props getting created & not updating previous one it won't call componentDidUpdate.
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
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()).
I have a react app. The code does work perfectly but after using it. It starts getting slow. After waiting for a while you can use it again.
When you press a button, I use socketio to emit a message.
Function call takes longer after using it multiple times.
The function call is part of websocket.js. However when you dig deeper in the functions it seems like react rendering is taking longer.
Function that is taking a lot of time.
So react takes a lot of time to render the view. I can only think then that I don't delete something which will use a lot of memory and thus slow down the rendering process. On the picture you see that it is in the file react-dom.development.js, the issue also occurs when it is build for production.
import React, { Component } from 'react';
import Card from './Card.js'
class Game extends Component {
constructor(props) {
super(props);
this.state = {
socket: this.props.socket,
card: {
"name": "",
"cardValues": {}
}
}
this.props.socket.emit("startGame");
}
render() {
let {socket, card} = this.state;
socket.on("startGame", (data) => this.setState({
card: data["card"],
}))
socket.on("nextCard", (data) => this.setState({
card: data["nextCard"],
}))
return (
<div className="Game">
<p>GAME</p>
<Card socket={socket} card={this.state.card}/>
</div>
);
}
}
export default Game;
This part uses the class Card.
import React, { Component } from 'react';
class Card extends Component {
constructor(props) {
super(props);
this.state = {
socket: this.props.socket,
}
}
chooseCardValue = (value) => {
this.state.socket.emit("chooseCard", {"cardValue": value});
}
render() {
let card = this.props.card;
return (
<div className="Card">
<h3 className="Country">{card["name"]}</h3>
<ul>
{
Object.keys(card["cardValues"]).map((value, i) => {
return <li key={i}><button onClick = {
this.chooseCardValue.bind(null, value)
}>{value}: {card["cardValues"][value]}</button></li>
})
}
</ul>
</div>
);
}
}
export default Card;
It is here where the buttons are defined. When you click the button it triggers the function chooseCardValue which becomes slow after time.
Why does it become so slow, and what is the cause?
I tried to only include the parts that could be relevant. The whole classes are available here just in case: https://lpaste.net/3474101090415280128
You are attaching your socket listeners inside the render method. This means that every time your app re-renders you will add 2 extra listeners. Additionally, inside the listeners you call setState, which triggers a re-render, which adds another listener.
The first time you get a message your app will render once, and add a listener.
The second time you get a message your app will render twice (once for each listener) and you will add 2 listeners.
The third time there will be 4 renders.
Then 8, 16, 32 and so on.
Essentially what you need to do is not add those listeners in the render method. You could try moving them to the constructor or the componentDidMount method, but really it should be somewhere external to the component tree.
For clarity these are the lines that I'm talking about:
let {socket, card} = this.state;
socket.on("startGame", (data) => this.setState({
card: data["card"],
}))
socket.on("nextCard", (data) => this.setState({
card: data["nextCard"],
}))