are there limitations on what sort of objects that can be kept in a component's state? would be very nice to use Set(), but don't know if that's a good idea (although it appears to be legal).
var Com = new React.createClass({
getInitialState: function() {
{ set: new Set() }
},
componentDidMount: function() {
let set = this.state.set;
set.add( this.props.someAttribute );
this.setState( { set: set } );
},
...
});
the reason this feels wrong is:
it appears React attempts to protects its component state, and
if so, probably uses Object.freeze()).
if these assumptions are correct, and knowing that Object.freeze() does not freeze Set() or Map(), it might be that using them undermines React's ability to protect its component state.
which is why the question: is this a good idea or not?
Related
Can't find any recent official info if any of the three options below is allowed?
constructor(props) {
this.state = {
item: <SomeItem />,
item1: () => <SomeItem />,
item2: SomeItem,
};
}
I found this answer but it references an old link from web archive which says:
What Shouldn’t Go in State?
...
React components: Build them in render()
based on underlying props and state.
But that link doesn't say why that is a bad idea, if it will introduce bugs, etc.
This is a really good question.
The reason that putting components in state is advised against is just that it goes fundamentally against the React model, which is that a component provides a render method (which is a pure function) that the React engine uses to automatically update the DOM to reflect the values of the component's props and state.
The output of that render, i.e. the React Element, is supposed to be used directly by the React engine. The contract is that your app, and all its components, generate a bunch of Elements in a pure way for the React engine to manage.
By doing things like introducing side effects in render, or putting the Elements in state, you're essentially breaking the 'pure' contract and it may give unpredictable results, which may or may not be considered bugs in your application. The specifics of the bugs may even change with different versions of React, with different engine implementations. The point is that you're breaking the React contract, so whilst it may work in some cases, it also may not in others or even the same cases as React itself changes. The behaviour is not guaranteed.
React has built-in ways to cache renders based on prop values, like React.memo, that the engine provides and understands, and are part of the contract. If you want to cache render output for performance reasons, this is the way to do it.
Indeed, this is exactly why such functions are provided by the React API rather than just letting you do it yourself.
At the end of the day, React component instances are just objects, and you can store objects in state, so it shouldn't cause any trouble if you avoid pitfalls. One such pitfall is that if you're creating handlers to put on their props, those handlers will close over the context in which they're created, which may lead to some unexpected outcomes. Here's an example of that:
const {useState, Fragment} = React;
function Thingy({onClick}) {
return <div onClick={onClick}>A</div>;
}
// STALE CLOSURE
function Example() {
const [value, setValue] = useState(0);
const [comp, setComp] = useState(
<Thingy onClick={() => { console.log("A: value = " + value); }} />
);
const handler = () => {
setValue(v => {
++v;
console.log("B: value = " + v);
return v;
});
};
return <Fragment>
{comp}
<div onClick={handler}>B</div>
</Fragment>;
}
ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
This is the classic stale closure thing. It's probably a bit easier to do accidentally using functional components and hooks (as I did there) rather than class components, but it's definitely possible to do with class components as well.
But if you're not doing that (either not creating functions for the component you're storing, or creating ones that don't use anything they close over that may change), it should be fine.
But look at React.memo, which may be a better answer depending on what your reason for wanting to put component instances in state is.
You can do something like this, if I understand you right
const Title = () => {
return <h1>Hello CodeSandbox</h1>;
};
class App extends React.Component {
state = {}
constructor(props) {
super(props)
this.state = {
item: function() {
return <Title />;
}
};
}
render() {
return (
<div className="App">
{this.state.item()}
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
}
export default App;
You can do it, but it's a bit strange. A React element is an object like any other. In this case, it will be the result of a method call:
// `<SomeItem/>` compiles to
React.createElement(SomeItem, null);
// A similar `React.createElement("div", null)` becomes
const el = {
$$typeof: Symbol(react.element),
key: null,
props: {},
ref: null,
type: "div",
_owner: null,
}
It's strange because it's unnecessary (and a little confusing). You can just generate the element (including any state or props updates) whenever you need it.
There's also a risk that you break one of the core guarantees of React: elements are immutable. Storing the element like this gives you a chance to mutate it and thus confuse React.
If you need many copies of the same element then it may be slightly more performant to keep it like this, especially if it is expensive to generate.
If I have a Vue component like:
<script>
export default {
updated() {
// do something here...
}
};
</script>
is there anyway to get the changes that resulted in the update? Like how watch hooks accept arguments for previous and next data?
watch: {
someProp(next, prev) {
// you can compare states here
}
}
React seems to do this in componentDidUpdate hooks, so I'm assuming Vue has something similar but I could be wrong.
The updated lifecycle hook doesn't provide any information on what caused the Vue component instance to be updated. The best way to react to data changes is by using watchers.
However, if you're trying to investigate what caused an update for debugging purposes, you can store a reference to the state of the Vue instance's data and compare it to the state when updated.
Here's an example script using lodash to log the name of the property that changed, triggering the update:
updated() {
if (!this._priorState) {
this._priorState = this.$options.data();
}
let self = this;
let changedProp = _.findKey(this._data, (val, key) => {
return !_.isEqual(val, self._priorState[key]);
});
this._priorState = {...this._data};
console.log(changedProp);
},
This works because properties prepended with the underscore character are reserved for internal use and are not available for binding. This could be saved in a mixin to use whenever you needed to debug a Vue component this way.
Here's a working fiddle for that example.
I'm trying to understand something about react and would like to get thoughts on the better way to do it.
Basically, I want to do some transformations/calculations on incoming props. I have limited event based state changes currently, but that may change in the future. Basically, is it better to do these calculations in render, or in componentWillMount and componentWillReceiveProps and set state?
in render example:
render() {
var url = this.getUrl() // get url transforms some props into a valid url
var something = this.getSomething() // etc etc
return <a href={url}>{something}</a>
}
outside of render:
componentWillMount() {
this._checkAndSetUrl(this.getUrl(this.props.data));
},
componentWillReceiveProps(nextProps) {
const currentGivenUrl = this._getUrl(this.props.data)
const nextGivenUrl = this._getUrl(nextProps.data)
if (currentGivenUrl !== nextGivenUrl) {
this._checkAndSetUrl(nextGivenUrl);
}
},
_checkAndSetUrl(url) {
// check validity and do some stuff to url
url = "new url"
something = this.getSomething()
this.setState({url: url, something: something})
}
My thinking is the second way is better because you don't do the calculations on every render, only when things are changed. What's the accepted way to do this?
Just for simplicity and readability you should keep them in render, and implement shouldComponentUpdate:
shouldComponentUpdate: function(nextProps, nextState) {
// TODO: return whether or not current chat thread is
// different to former one. this.props refers to the 'old' props.
}
If you return false from shouldComponentUpdate, render will not be called again. If this.props.data === nextProps.data you can probably return false.
It avoids you keeping around unnecessary state, and is readable. If you want to make the checks more granular you can split up your url and something into different components, with their own shouldComponentUpdate.
For more information, see https://facebook.github.io/react/docs/advanced-performance.html
I've recently started using ReactJS in my UI projects which has greatly simplified my UI workflow. Really enjoyable API to work with.
I've noticed recently that I've had to use a pattern in a couple of my projects that needed to aggregate data on a page. This data would live in the DOM and not be dependent on using the React state for data transitions.
This is an example implementation:
var Component = module.exports = React.createClass({
componentDidMount: function() {
this.component = new Component();
this.component.start();
},
componentWillUnmount: function(prevProps, prevState) {
if (this.component !== undefined) {
this.component.destroy();
}
},
render: function() {
return (
<div id="componentContainer"></div>
);
}
});
var Component = function(){
// Some object that dynamically loads content in a
// pre-packaged NON-react component into componentContainer
// such as a library that renders a graph, or a data loader
// that aggregates DOM elements using an infinite scroll
}
My question is whether or not this is the proper way to aggregate data into the DOM using React. I looked around for the idiomatic way of doing this, but my google-foo was unable to come up with anything.
Thanks!
EDIT - as a side note, does anyone think there will be a problem with the way I destroy the container, using the componentWillUnmount?
The main problem is that you're using an id, which is inflexible and makes assumptions about the rest of the components (because ids must be globally unique).
module.exports = React.createClass({
componentDidMount: function() {
// pass a DOM node to the constructor instead of it using an id
this.component = new Component(this.getDOMNode());
this.component.start();
},
componentWillUnmount: function() {
this.component.destroy();
},
render: function() {
return <div />;
}
});
Your componentWillUnmount was fine, but the one place you set this.component will always run before componentWillUnmount, and there's no other reason it'd be assigned/deleted, so the if statement isn't needed.
Also the arguments both weren't used, and aren't provided to componentWillUnmount. That signature belongs to componentDidUpdate.
This example is a bit contrived, but bear with me. Say I have two functions as members in a React component:
var OrderApp = React.createClass({
getInitialState: function() {
return {
selectedOrders: {},
selectedLineItems: {}
};
},
setOrderSelected: function(order, newSelectedState) {
var mutation = {};
mutation[order.id] = {$set: newSelectedState};
var newSelectedOrders = React.addons(this.state.selectedOrders, mutation);
_.each(order.lineItems, function(lineItem) {
setLineItemSelected(lineItem, newSelectedState);
});
this.setState({selectedOrders: newSelectedOrders});
},
setLineItemSelected: function(lineItem, newSelectedState) {
var mutation = {};
mutation[lineItem.id] = {$set: newSelectedState};
var newSelectedLineItems = React.addons(this.state.selectedLineItems, mutation);
this.setState({seelctedLineItems: newSelectedLineItems});
}
});
At the completion of calling OrderApp.setOrderSelected(order, true), only the last lineItem selected state appears to have changed in my component's state. I'm guessing this is because React is batching the calls to setState, and so when I'm calling:
var newSelectedLineItems = React.addons(this.state.selectedLineItems, mutation);
in the second function, I'm picking up the original state each time, and so only the last mutation takes effect.
Should I be collecting all of the state mutations in the parent function and only calling setState once? This feels a bit clunky to me, and I'm wondering if I'm missing a simpler solution.
You should never be calling this.setState more than once in a given thread of execution within a component.
So you have a couple of options:
Use forceUpdate instead of setState
You don't HAVE to use this.setState - you can modify the state directly and use this.forceUpdate after you're done.
The main thing to remember here is that you should treat this.state as tainted as soon as you start modifying it directly. IE: don't use the state, or properties you know you've modified until after you've called this.forceUpdate. This isn't usually a problem.
I absolutely prefer this method when, you're dealing with nested objects within this.state. Personally I'm not a huge fan of React.addons.update because I find the syntax extremely clunky. I prefer to modify directly and forceUpdate.
Create an object that you pass around until finally using it in setState
Collect an object of all your changes, to pass to this.setState at the end of your thread of execution.
Unfortunately, this is a problem with shallow merge. You do have to collect the updates, and apply them at one time.
feels a bit clunky to me
This is a really good sign that you should be using a mixin! This code looks pretty terrible, but you can just pretend it looks pretty when you're using it.
var React = require('react/addons');
var UpdatesMixin = {
componentDidMount: function(){
this.__updates_list = [];
},
addUpdate: function(update){
this.__updates_list.push(update);
if (this.__updates_list.length === 1) {
process.nextTick(function(){
if (!this.isMounted()) {
this.__updates_list = null;
return;
}
var state = this.__updates_list.reduce(function(state, update){
return React.addons.update(state, update);
}, this.state);
this.setState(state);
this.__updates_list = [];
}.bind(this));
}
}
};
requirebin
It buffers all updates that occur synchronously, applies them to this.state, and then does a setState.
The usage looks like this (see the requirebin for a full demo).
this.addUpdate({a: {b: {$set: 'foo'}};
this.addUpdate({a: {c: {$set: 'bar'}};
There are some places it could be optimized (by merging the updates objects, for example), but you can do that later without changing your components.