I have this code in render() that opens a websocket and connects to a Rest service.
return (
<div className="App">
<SockJsClient
url = 'http://localhost:8610/refresh/'
topics={['/topic/notification']}
onConnect={console.log("Connection established!")}
onDisconnect={console.log("Disconnected!")}
onMessage={(msg) => this.update()}
debug= {true}
/>
Everything works fine - the only issue with this approach is, that when the UI is rerendered, the application closes the websocket and reopens it again. Nothing breaks, the app just resumes and nothing happens.
Is this operation very resource consuming? Is there any way to tell react not to re-render that portion of code? Should I be worrying at all?
this.update() re-sets an array in my state.
I know shouldComponentUpdate() is what I need, but then I would have to create a new component that does the rerendering, right? I'm confused.
Thanks!
EDIT: This also works (#zavjs's solution is much more cleaner)
componentDidMount(){
var self = this; // use self to reference this
var socket = new SockJS("http://192.168.1.139:8610/refresh");
var stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame,) {
console.log('connected: ' + frame);
stompClient.subscribe('/topic/notification', function(notification) {
self.update(); // here
})
}, function(err) {
console.log('err', err);
});
Also here's more about this!
Yes, I'd create an intermediary component, to wrap around your SocketJSClient, and condition shouldComponentUpdate to whenever you purposefully want to make that re-render happen (perhaps when a Socket config like url has changed and you need to pass that down, for instance).
class PreventUpdate extends React.Component {
shouldComponentUpdate = (nextProps) => {
//set true if a Socket connection config has changed
//or something that needs to trigger a re-render in the
//child component
if(this.nextProps.shouldPreventUpdate) {
return false;
}
};
render() {
this.props.children;
}
}
And now you can do:
<PreventUpdate
shouldPreventUpdate={someDynamicCondition}>
<SocketJSClient />
<PreventUpdate/>
With this you can pass in an arbitrary flag to prevent update of a component and all its children. I hope it fits you well
Related
I am importing an external library (from a cdn; I know it's terrible) that needs to be initialised with some kind of config, and produces some kind of events.
Parent:
eventTrigerred = e => console.log(e)
...
<Component
onEvent= {this.eventTrigerred}
/>
Child:
render() {
const initiliseLibrary = () => {
let config = {
key: "XXX", // some key to initilise the library
onLibraryEvent: this.props.onEvent // the event I want to handle
}
window.StrangeLibrary.init(config)
}
return (
<div id="library-area">
{initialiseFrames()} // when initilised this library will render an elemnt here
</div>
);
}
The problem is that if in the parent the state changes, this reloads the child, and this result in the child being re-rendered, the event triggering again, and this infinitely loops.
I feel that I am doing somehting fundamentaly wrong, and I wish I did not have to use this library but I have to.
Any idea what the problem might be?
The render function shouldn't create any side effect. Instead, put your initialisation code in componentDidMount, like so :
componentDidMount() {
let config = {
key: "XXX", // some key to initilise the library
onLibraryEvent: this.props.onEvent // the event I want to handle
}
window.StrangeLibrary.init(config)
}
However, this code will be called again if the component will be remounted (but not if it's rendered again without remount, from a prop change, or a state change).
If it's still looping, you may want to lift the initialisation code in a parent component's componentDidMount that stays mounted all along.
So the problem I'm having right now is on first click, my GUI re-renders. But on second click, it does not re-render. I believe it's because I am not updating the state of "graphicLayers" which my render is binding through the "graphicLayers.map". That's my theory anyway (even though it works on first click? but not the 2nd click or anything after).
I tried forcing an setState update of graphicLayers, but it doesn't seem to be working. Like this:
let graphicLayersCopy = Object.assign([], this.state.graphicLayers);
this.setState({graphicLayers: graphicLayersCopy});
but that's not working. I know through the debugger that it's setting the data correctly, and if I refresh (it saves state and reloads the state), the GUI is then rendered correctly.
Is there anyway I can force a re-render of a variable some how even if it doesn't change value?
constructor
constructor(props, context) {
super(props, context);
this.state = {
graphicLayers: [id1, id2, id3],
graphicLayersById: {
id1: { ... },
id2: { ... },
id3: { ... }
}
this.addLayerClick = this.addLayerClick.bind(this);
};
render
render() {
return (
<div>
{this.state.graphicLayers.map((id) =>
<GraphicLayer addLayerClick={this.addLayerClick.bind(this)} />
)}
</div>
);
}
addLayerClick
addLayerClick() {
... change some property in graphicLayersById dictionary ...
self.setState({ graphicLayersById: newDataHere });
}
EDIT: I found the problem on my end, and it's not exactly shown here.
So my addLayerClick() actually calls another functions that is listening to a call and it sets the state inside. it's weird because the setState gets called in the callback function, but i got it to work by putting the setState in the addLayerClick() itself.. still dont know why this doens't work but i will upvote all of you at least
listenFunction() {
let self = this;
this.firebaseWrapper.storage.on('graphicLayersById', function (save) {
if (save) {
self.setState({ graphicLayersById: save }); // FOR SOME REASON THIS DOESN'T UPDATE THE GUI THE 2nd CLICK. The data is correct though and I see it going here on a breakpoint, but GUI won't update unless I setState in the actual button
}
else {
self.setState({ graphicLayersById: undefined });
}
});
}
In addLayerClick() you're only updating graphicLayersById, but rendering depends on graphicLayers. You should be updating the graphicLayers state in addLayerClick() as well.
addLayerClick() {
this.setState({
graphicLayers: ...
graphicLayersById: ....
});
}
On a side note, you shouldn't bind methods inside render() since that creates a brand new function on every render (and could impact performance). Instead of
<GraphicLayer addLayerClick={this.addLayerClick.bind(this)} />
do
<GraphicLayer addLayerClick={this.addLayerClick} />
and leave the binding in your constructor (the way you already have).
Actually, you have bound the addLayerClick() function to the component, so you can use this instead of self
You should revise your code like this: (there are about 2 changes)
constructor(props, context) {
super(props, context);
this.state = {
graphicLayers: [id1, id2, id3],
graphicLayersById: {
id1: { ... },
id2: { ... },
id3: { ... }
}
// don't need to bind here anymore, since you bind it in the click
//this.addLayerClick = this.addLayerClick.bind(this);
};
addLayerClick() {
//... change some property in graphicLayersById dictionary ...
// just use 'this' here
this.setState({ graphicLayersById: newDataHere });
// the below line is NOT recommended, which is force the component to update
// this.forceUpdate(); // this line will update the component even though there's no change
}
If this doesn't work yet, please post here how you handle onCLick function in the child component, and also post some errors if any, thanks
Hope these two possible way will reder your view
this.setState({graphicLayersById: newDataHere} , ()=>{
console.log(this.state.graphicLayersById);
});
OR
var update = require('react-addons-update');
var graphicLayers = update(this.state, {
graphicLayersById: {$set: newDataHere}
});
this.setState(graphicLayers);
On my template js I have the following
updateBarTitle(title){
this.setState({barTItle:title});
}
render() {
const childrenWithProps = React.Children.map(this.props.children, function (child) {
var childProps = {
updateBarTitle: this.updateBarTitle.bind(this)
};
var childWithProps = React.cloneElement(child, childProps);
return childWithProps;
}, this);
And on the child I have.
componentDidUpdate(){
this.props.updateBarTitle('Users');
}
When testing out the app and once state changes my browser freezes for a long time then returns a Maximum call stack size exceeded Need some advice on what I'm doing wrong.
You're creating an infinite loop with the child componentDidUpdate() method. It calls updateBarTitle(), which calls setState(), which re-renders, which calls updateBarTitle() again...and so on.
If you want that logic in the componentDidUpdate() method where you have it, you need to add some condition so that it will only call once.
You could pass the current state of barTitle to the child as a prop and then do something like:
if(this.props.barTitle !== 'Users') {
this.props.updateBarTitle('Users');
}
To stop that loop from happening for example.
EDIT: To give a clearer example here is a DEMO of this to illustrate.
I have this class:
export default class Search extends Component {
throttle(fn, threshhold, scope) {
var last,
deferTimer;
return function () {
var context = scope || this;
var now = +new Date,
args = arguments;
if (last && now < last + threshhold) {
// hold on to it
clearTimeout(deferTimer);
deferTimer = setTimeout(function () {
last = now;
fn.apply(context, args);
}, threshhold);
} else {
last = now;
fn.apply(context, args);
}
}
}
render() {
return (
<div>
<input type='text' ref='input' onChange={this.throttle(this.handleSearch,3000,this)} />
</div>
)
}
handleSearch(e) {
let text = this.refs.input.value;
this.someFunc();
//this.props.onSearch(text)
}
someFunc() {
console.log('hi')
}
}
All this code does it log out hi every 3 seconds - the throttle call wrapping the handleSearch method takes care of this
As soon as I uncomment this line:
this.props.onSearch(text)
the throttle methods stops having an effect and the console just logs out hi every time the key is hit without a pause and also the oSearch function is invoked.
This onSearch method is a prop method passed down from the main app:
<Search onSearch={ text => dispatch(search(text)) } />
the redux dispatch fires off a redux search action which looks like so:
export function searchPerformed(search) {
return {
type: SEARCH_PERFORMED
}
}
I have no idea why this is happening - I'm guessing it's something to do with redux because the issue occurs when handleSearch is calling onSearch, which in turn fires a redux dispatch in the parent component.
The problem is that the first time it executes, it goes to the else, which calls the dispatch function. The reducer probably immediately update some state, and causes a rerender; the re-render causes the input to be created again, with a new 'throttle closure' which again has null 'last' and 'deferTimer' -> going to the else every single time, hence updating immediately.
As Mike noted, just not updating the component can you get the right behavior, if the component doesn't need updating.
In my case, I had a component that needed to poll a server for updates every couple of seconds, until some state-derived prop changed value (e.g. 'pending' vs 'complete').
Every time the new data came in, the component re-rendered, and called the action creator again, and throttling the action creator didn't work.
I was able to solve simply by handing the relevant action creator to setInterval on component mount. Yes, it's a side effect happening on render, but it's easy to reason about, and the actual state changes still go through the dispatcher.
If you want to keep it pure, or your use case is more complicated, check out https://github.com/pirosikick/redux-throttle-actions.
Thanks to luanped who helped me realise the issue here. With that understood I was able to find a simple solution. The search component does not need to update as the input is an uncontrolled component. To stop the cyclical issue I was having I've used shouldComponentUpdate to prevent it from ever re-rendering:
constructor() {
super();
this.handleSearch = _.throttle(this.handleSearch,1000);
}
shouldComponentUpdate() {
return false;
}
I also moved the throttle in to the constructor so there can only ever be once instance of the throttle.
I think this is a good solution, however I am only just starting to learn react so if anyone can point out a problem with this approach it would be welcomed.
I have come across a problem about states based on properties.
The scenario
I have a Component parent which creates passes a property to a child component.
The Child component reacts according to the property received.
In React the "only" proper way to change the state of a component is using the functions componentWillMount or componentDidMount and componentWillReceiveProps as far as I've seen (among others, but let's focus on these ones, because getInitialState is just executed once).
My problem/Question
If I receive a new property from the parent and I want to change the state, only the function componentWillReceiveProps will be executed and will allowed me to execute setState. Render does not allow to setStatus.
What if I want to set the state on the beginning and the time it receives a new property?
So I have to set it on getInitialState or componentWillMount/componentDidMount. Then you have to change the state depending on the properties using componentWillReceiveProps.
This is a problem when your state highly depends from your properties, which is almost always. Which can become silly because you have to repeat the states you want to update according to the new property.
My solution
I have created a new method that it's called on componentWillMount and on componentWillReceiveProps. I have not found any method been called after a property has been updated before render and also the first time the Component is mounted. Then there would not be a need to do this silly workaround.
Anyway, here the question: is not there any better option to update the state when a new property is received or changed?
/*...*/
/**
* To be called before mounted and before updating props
* #param props
*/
prepareComponentState: function (props) {
var usedProps = props || this.props;
//set data on state/template
var currentResponses = this.state.candidatesResponses.filter(function (elem) {
return elem.questionId === usedProps.currentQuestion.id;
});
this.setState({
currentResponses: currentResponses,
activeAnswer: null
});
},
componentWillMount: function () {
this.prepareComponentState();
},
componentWillReceiveProps: function (nextProps) {
this.prepareComponentState(nextProps);
},
/*...*/
I feel a bit stupid, I guess I'm loosing something...
I guess there is another solution to solve this.
And yeah, I already know about this:
https://facebook.github.io/react/tips/props-in-getInitialState-as-anti-pattern.html
I've found that this pattern is usually not very necessary. In the general case (not always), I've found that setting state based on changed properties is a bit of an anti-pattern; instead, simply derive the necessary local state at render time.
render: function() {
var currentResponses = this.state.candidatesResponses.filter(function (elem) {
return elem.questionId === this.props.currentQuestion.id;
});
return ...; // use currentResponses instead of this.state.currentResponses
}
However, in some cases, it can make sense to cache this data (e.g. maybe calculating it is prohibitively expensive), or you just need to know when the props are set/changed for some other reason. In that case, I would use basically the pattern you've written in your question.
If you really don't like typing it out, you could formalize this new method as a mixin. For example:
var PropsSetOrChangeMixin = {
componentWillMount: function() {
this.onPropsSetOrChange(this.props);
},
componentWillReceiveProps: function(nextProps) {
this.onPropsSetOrChange(nextProps);
}
};
React.createClass({
mixins: [PropsSetOrChangeMixin],
onPropsSetOrChange: function(props) {
var currentResponses = this.state.candidatesResponses.filter(function (elem) {
return elem.questionId === props.currentQuestion.id;
});
this.setState({
currentResponses: currentResponses,
activeAnswer: null
});
},
// ...
});
Of course, if you're using class-based React components, you'd need to find some alternative solution (e.g. inheritance, or custom JS mixins) since they don't get React-style mixins right now.
(For what it's worth, I think the code is much clearer using the explicit methods; I'd probably write it like this:)
componentWillMount: function () {
this.prepareComponentState(this.props);
},
componentWillReceiveProps: function (nextProps) {
this.prepareComponentState(nextProps);
},
prepareComponentState: function (props) {
//set data on state/template
var currentResponses = this.state.candidatesResponses.filter(function (elem) {
return elem.questionId === props.currentQuestion.id;
});
this.setState({
currentResponses: currentResponses,
activeAnswer: null
});
},