JavaScript basically doesn't provide watchers for variables.
So I wonder how React.js does it.
I'm trying to do the same thing. For objects and arrays, I can use Proxy, like this:
let proxy = new Proxy(arr, {
deleteProperty: function(target, property) {
console.log("Deleted %s", property);
return true;
},
set: function(target, property, value, receiver) {
target[property] = value;
console.log("Set %s to %o", property, value);
return true;
}
});
Maybe it's not a good idea. And I'm still don't know what to do with literals such as Numbers.
The TL/DR was already given in the comments: React does not watch the data, changes are always triggered from "the outside", which means setState. The state does not even have to change, calling setState with the same data again already triggers the lifecycle of the component.
However, if you use redux or some other state container, it can look as if components magically detect changes themself whenever the data used by mapStateToProps function changes. But react-redux's connect function merely wraps your component with a proxy that listens for events:
register a listener for every redux action
compare the current and previous state on every event using strict equal (===)
if the state is different, call mapStateToProps
compare the result of mapStateToProps with previous calls using strict equal
if different, trigger a rerendering of the component
In order to imitate the behaviour, you would need some kind "thingsDidChange" listener which is triggered by events or continuous (setInterval) check.
Related
The React documentation says the following about setState:
If you need to set the state based on the previous state, read about the updater argument below,
Beside the following sentence, which I don't understand:
If mutable objects are being used and conditional rendering logic cannot be implemented in shouldComponentUpdate(), calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders.
They say:
The first argument is an updater function with the signature (state, props) => stateChange ... state is a reference to the component state at the time the change is being applied.
And make an example:
this.setState((state, props) => {
return {counter: state.counter + props.step};
});
Saying:
Both state and props received by the updater function are guaranteed to be up-to-date. The output of the updater is shallowly merged with state.
What do they mean by guaranteed to be up-to-date and what should we be aware of when deciding if we should use setState with an updater function (state, props) => stateChange or directly with an object as the first parameter?
Let's assume a real world scenario. Suppose we have a fancy chat application where:
The state of the chat is represented by this.state = { messages: [] };
Previous messages are loaded making an AJAX request and are prepended to the messages currently in the state;
If other users (not the current user) send a message to the current user, new messages arrive to current user from a realtime WebSocket connection and are appended to the messages currently in the state;
If it is the current user the one who sends the message, the message is appended to the messages of the state as in point 3 as soon as the AJAX request fired when the message is sent completes;
Let's pretend this is our FancyChat component:
import React from 'react'
export default class FancyChat extends React.Component {
constructor(props) {
super(props)
this.state = {
messages: []
}
this.API_URL = 'http://...'
this.handleLoadPreviousChatMessages = this.handleLoadPreviousChatMessages.bind(this)
this.handleNewMessageFromOtherUser = this.handleNewMessageFromOtherUser.bind(this)
this.handleNewMessageFromCurrentUser = this.handleNewMessageFromCurrentUser.bind(this)
}
componentDidMount() {
// Assume this is a valid WebSocket connection which lets you add hooks:
this.webSocket = new FancyChatWebSocketConnection()
this.webSocket.addHook('newMessageFromOtherUsers', this.handleNewMessageFromOtherUser)
}
handleLoadPreviousChatMessages() {
// Assume `AJAX` lets you do AJAX requests to a server.
AJAX(this.API_URL, {
action: 'loadPreviousChatMessages',
// Load a previous chunk of messages below the oldest message
// which the client currently has or (`null`, initially) load the last chunk of messages.
below_id: (this.state.messages && this.state.messages[0].id) || null
}).then(json => {
// Need to prepend messages to messages here.
const messages = json.messages
// Should we directly use an updater object:
this.setState({
messages: messages.concat(this.state.messages)
.sort(this.sortByTimestampComparator)
})
// Or an updater callback like below cause (though I do not understand it fully)
// "Both state and props received by the updater function are guaranteed to be up-to-date."?
this.setState((state, props) => {
return {
messages: messages.concat(state.messages)
.sort(this.sortByTimestampComparator)
}
})
// What if while the user is loading the previous messages, it also receives a new message
// from the WebSocket channel?
})
}
handleNewMessageFromOtherUser(data) {
// `message` comes from other user thanks to the WebSocket connection.
const { message } = data
// Need to append message to messages here.
// Should we directly use an updater object:
this.setState({
messages: this.state.messages.concat([message])
// Assume `sentTimestamp` is a centralized Unix timestamp computed on the server.
.sort(this.sortByTimestampComparator)
})
// Or an updater callback like below cause (though I do not understand it fully)
// "Both state and props received by the updater function are guaranteed to be up-to-date."?
this.setState((state, props) => {
return {
messages: state.messages.concat([message])
.sort(this.sortByTimestampComparator)
}
})
}
handleNewMessageFromCurrentUser(messageToSend) {
AJAX(this.API_URL, {
action: 'newMessageFromCurrentUser',
message: messageToSend
}).then(json => {
// Need to append message to messages here (message has the server timestamp).
const message = json.message
// Should we directly use an updater object:
this.setState({
messages: this.state.messages.concat([message])
.sort(this.sortByTimestampComparator)
})
// Or an updater callback like below cause (though I do not understand it fully)
// "Both state and props received by the updater function are guaranteed to be up-to-date."?
this.setState((state, props) => {
return {
messages: state.messages.concat([message])
.sort(this.sortByTimestampComparator)
}
})
// What if while the current user is sending a message it also receives a new one from other users?
})
}
sortByTimestampComparator(messageA, messageB) {
return messageA.sentTimestamp - messageB.sentTimestamp
}
render() {
const {
messages
} = this.state
// Here, `messages` are somehow rendered together with an input field for the current user,
// as well as the above event handlers are passed further down to the respective components.
return (
<div>
{/* ... */}
</div>
)
}
}
With so many asynchronous operations, how can I be really sure that this.state.messages will always be consistent with the data on the server and how would I use setState for each case? What considerations I should make? Should I always use the updater function of setState (why?) or is safe to directly pass an object as the updater parameter (why?)?
Thank you for the attention!
setState is only concerned with component state consistency, not server/client consistency. So setState makes no guarantees that the component state is consistent with anything else.
The reason an updater function is provided, is because state updates are sometimes delayed, and don't occur immediately when setState is called. Therefore, without the updater function, you have essentially a race condition. For example:
your component begins with state = {counter: 0}
you have a button that updates the counter when clicked in the following way: this.setState({counter: this.state.counter +1})
the user clicks the button really fast, so that the state does not have time to be updated between clicks.
that means that the counter will only increase by one, instead of the expected 2 - assuming that counter was originally 0, both times the button is clicked, the call ends up being this.setState({counter: 0+1}), setting the state to 1 both times.
An updater function fixes this, because the updates are applied in order:
your component begins with state = {counter: 0}
you have a button that updates the counter when clicked in the following way: this.setState((currentState, props) => ({counter: currentState.counter + 1}))
the user clicks the button really fast, so that the state does not have time to be updated between clicks.
unlike the other way, currentState.counter + 1 does not get evaluated immediately
the first updater function is called with the initial state {counter: 0}, and sets the state to {counter: 0+1}
the second updater function is called with the state {counter: 1}, and sets the state to {counter: 1+1}
Generally speaking, the updater function is the less error-prone way to change the state, and there is rarely a reason to not use it (although if you are setting a static value, you don't strictly need it).
What you care about, however, is that updates to the state don't cause improper data (duplicates and the like). In that case, I would take care that the updates are designed so that they are idempotent and work no matter the current state of the data. For instance, instead of using an array to keep the collection of messages, use a map instead, and store each message by key or hash that is unique to that message, no matter where it came from (a millisecond timestamp may be unique enough). Then, when you get the same data from two locations, it will not cause duplicates.
I'm not an expert in React by any means and have only been doing it for two months only, but here's what I learned from my very first project in React, which was as simple as showing a random quote.
If you need to use the updated state right after you use setState, always use the updater function. Let me give you an example.
//
handleClick = () => {
//get a random color
const newColor = this.selectRandomColor();
//Set the state to this new color
this.setState({color:newColor});
//Change the background or some elements color to this new Color
this.changeBackgroundColor();
}
I did this and what happened was that the color that was set to the body was always the previous color and not the current color in the state, because as you know, the setState is batched. It happens when React thinks it's best to execute it. It's not executed immediately. So to solve this problem, all I have to do was pass this.changeColor as a second argument to setState. Because that ensured that the color I applied was kept up to date with the current state.
So to answer your question, in your case, since you're job is to display the message to the user as soon as a new message arrives, i.e use the UPDATED STATE, always use the updater function and not the object.
I have a Vue instance with two watchers:
watch: {
zone:function(zone) {
console.log('Zone watcher');
this.route = {};
},
route:function(route) {
console.log('Route watcher');
if(Object.getOwnPropertyNames(route).length === 0) {
var _this = this;
axios.get(route.url).then(function(response) {
_this.tracks = response.data;
});
} else this.tracks = {};
}
},
When a user selects a zone, route (and tracks) are reset. When user selects a route, tracks are loaded;
I have a component receiving zone and tracks as props, also with two internal watchers that perform some independent actions when any of this props change.
I also have a method that changes both variables:
jump:function(path) {
var parts = path.split(',');
this.zone = this.ZONES[parts[0]];
this.route = this.zone.routes[parts[1]];
},
The problem is watcher for route is fired in first place, then the watcher for zone changes route value triggering its watcher again, reseting tracks value to an empty object.
Is there a way to define the order that watchers must be triggered?
Andrey's comment shows the way. This question comes down to which tools you use for what job. Inevitably there's a bit of opinion... watch is for edge cases. You don't need it often, and if you can do without it, you probably should. watch belongs with computed and v-bind: they're reactive, use them (only) for representing state on screen, you have no (or little) control over when they run, and you shouldn't care.
A server request belongs in a method, or in a function outside of Vue (in your store perhaps) where it can be called explicitly. So create yourself a changeZone() function that clears routes and tracks, then calls the server, then updates your data (or store) with the server response. Your code will be much clearer if these little functional sequences are specified explicitly, in one place. The trigger for your sequence should likely be from an event (user-action) or lifecyle hook, not a watch.
I am learning how redux works but its a lot of code to do simple things. For example, I want to load some data from the server before displaying. For editing reasons, I can't simply just use incoming props but I have to copy props data into the local state.
As far as I've learned, I have to send a Fetch_request action. If successful, a fetch_success action will update the store with new item. Then updated item will cause my component's render function to update.
In component
componentWillMount() {
this.props.FETCH_REQUEST(this.props.match.params.id);
}
...
In actions
export function FETCH_REQUEST(id) {
api.get(...)
.then(d => FETCH_SUCCESS(d))
.catch(e => FETCH_FAILURE(e));
}
...
In reducer
export function FETCH_REDUCER(state = {}, action ={}) {
switch (action.type) {
case 'FETCH_SUCCESS':
return { ...state, [action.payload.id]: ...action.payload }
...
}
Back in component
this.props.FETCH_REDUCER
// extra code for state, getting desired item from...
Instead, can I call a react-thunk function and pass some callback functions? The react-thunk can update the store and callbacks can change the component's local state.
In component
componentWillMount() {
this.props.FETCH_REQUEST(this.props.match.params.id, this.cbSuccess, this.cbFailure);
}
cbSuccess(data) {
// do something
}
cbFailure(error) {
// do something
}
...
In action
export function FETCH_REQUEST(id, cbSuccess, cbFailure) {
api.get(...)
.then(d => {
cbSuccess(d);
FETCH_SUCCESS(d);
}).catch(e => {
cbFailure(d);
FETCH_FAILURE(e);
});
}
...
Is this improper? Can I do the same thing with redux-observable?
UPDATE 1
I moved nearly everything to the redux store, even for edits (ie replaced this.setState with this.props.setState). It eases state management. However, every time any input's onChange fires, a new state is popping up. Can someone confirm whether this is okay? I'm worried about the app's memory management due to redux saving a ref to each state.
First of all, you should call your API in componentDidMount instead of componentWillMount. More on this at : what is right way to do API call in react js?
When you use a redux store, your components subscribe to state changes using the mapStateToProps function and they change state using the actions added a props through the mapDispatchToProps function (assuming you are using these functions in your connect call).
So you already are subscribing to state changes using your props. Using a callback would be similar to having the callback tell you of a change which your component already knows about because of a change in its props. And the change in props would trigger a re-render of the component to show the new state.
UPDATE:
The case you refer to, of an input field firing an onChange event at the change of every character, can cause a lot of updates to the store. As mentioned in my comments, you can use an api like _.debounce to throttle the updates to the store to reduce the number of state changes in such cases. More on handling this at Perform debounce in React.js.
The issue of memory management is a real issue in real world applications when using Redux. The way to reduce the effect of repeated updates to the state is to
Normalize the shape of state : http://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html
Create memoized selectors using Reselect (https://github.com/reactjs/reselect)
Follow the advice provided in the articles regarding performance in Redux github pages (https://github.com/reactjs/redux/blob/master/docs/faq/Performance.md)
Also remember that although the whole state should be copied to prevent mutating, only the slice of state that changes needs to be updated. For example, if your state holds 10 objects and only one of them changes, you need to update the reference of the new object in the state, but the remaining 9 unchanged objects still point to the old references and the total number of objects in your memory is 11 and not 20 (excluding the encompassing state object.)
I have a component connected to redux store and I have a code block like in this:
if (this.props.person !== nextProps.person) {
...
} else {
...
}
...
MyComponent.propTypes {
person: PropTypes.object.isRequired
}
Is it safe to check object reference in this way? Can I assume that in a reducer object reference will always change?
It is safe as long as your reducer is a pure function. To guarantee purity, those are the 3 most important things you should never do inside a reducer:
Mutate its arguments (use Object.assign or the object spread operator to avoid mutations on objects)
Perform side effects like API calls and routing transitions
Call non-pure functions, e.g. Date.now() or Math.random().
If your reducer satisfy all 3 conditions, any particular action dispatched that turned out to modify person property inside the state tree, will result in a new person object.
In that case, this.props.person and nextProps.person would be two different objects and that object reference check would be correct. However if a particular action dispatched didn't modify person property inside the state tree, this.props.person and nextProps.person would still be the same object
Consider a simple reducer:
function reducer(state, action) {
switch(action.type) {
...
default:
return state
}
}
This is a normal part of redux - if your reducer is called with an unknown action, it returns the same state. In this case, this.props.person === nextProps.person, because the object reference has not changed.
If your code (your reducer) changes the reference to person, the reference will have changed in your component as well.
I'm using ReactJS.
I have a stateful component (stopwatch container) and multiple child components which are stateless (stopwatches).
In the outer component, I'm doing something like this:
// the outer component is a "container" for multiple stopwatches
tick: function() {
for (var i = 0; i < this.state.stopwatches.length; i++)
{
// if a particular stopwatch is "started", I'll increase it's time
if (this.state.stopwatches[i].status == "started")
{
this.state.stopwatches[i].time.seconds++;
// do some processing
}
}
// this is the suspicious code!
this.setState(this.state);
}
Notice that I'm changing the this.state property and then calling setState() on the state object itself. This seems so wrong to me. But, in the other hand, in order to not manipulate the state object itself, i'd have to clone it and then do setState(stateClone), but I'm not sure if it's possible to clone objects effectively in JS, neither if I really should do this.
Can I continue to do setState(this.state) ?
Instead of calling this.setState(this.state) you can just call this.forceUpdate().
Just remember that this is not recommended way of updating components state. If you are unsure about your modifications take a look on Immutability Helpers.
Just my two cents here: React will re-render every time you call setState:
boolean shouldComponentUpdate(object nextProps, object nextState)
returns true by default. If you don't wish to re-render make shouldComponentUpdate(object nextProps, object nextState) to return false.
Quoting from Facebook docs:
By default, shouldComponentUpdate always returns true to prevent
subtle bugs when state is mutated in place, but if you are careful
to always treat state as immutable and to read only from props and
state in render() then you can override shouldComponentUpdate
with an implementation that compares the old props and state to
their replacements.
Aside from immutability (which is always good), the second best would be this.setState({stopwatches: this.state.stopwatches}).
With immutability helpers, it'd look like this:
var stopwatches = this.state.stopwatches.map(function(watch){
return update(watch, {
time: {$set: watch.time + 1}
});
});
this.setState({stopwatches: stopwatches})
Or with es6 you can save a few characters.
var stopwatches = this.state.stopwatches.map(watch => update(watch, {
time: {$set: watch.time + 1}
});
this.setState({stopwatches})