How should we handle building reusable components in an application that uses a dependency injection library?
- Use the injection library as the only binding to instances of models, services and other dependencies in the application, only other properties will be given as additional
propTypes:<Profile injector={injector} picSize="small" /> - Use injection library to set individual props on the component that will transfer properties down the component tree, explicity stating the required data for the components:
class Profile extends Component {
static propTypes = {
userModel: PropTypes.instanceOf(UserModel).isRequired,
picSize: PropTypes.string
};
render() {...}
}
<Profile
userModel={this.userModel} // this refers to parent backbone view
picSize="small"
/>tl;dr: If a Component needs to alter one of its attributes at some point in time, that attribute should be part of its state, otherwise it should just be a prop for that Component.
props (short for properties) are a Component's configuration, its options if you may. They are received from above and immutable as far as the Component receiving them is concerned.
A Component cannot change its props, but it is responsible for putting together the props of its child Components.
The state starts with a default value when a Component mounts and then suffers from mutations in time (mostly generated from user events). It's a serializable* representation of one point in time—a snapshot.
A Component manages its own state internally, but—besides setting an initial state—has no business fiddling with the state of its children. You could say the state is private.
* We didn't say props are also serializable because it's pretty common to pass down callback functions through props.
"In React, data flows from owner to owned component through props as discussed above. This is effectively one-way data binding: owners bind their owned component's props to some value the owner has computed based on its props or state. Since this process happens recursively, data changes are automatically reflected everywhere they are used."
"By building modular components that reuse other components with well-defined interfaces, you get much of the same benefits that you get by using functions or classes. Specifically you can separate the different concerns of your app however you please simply by building new components."
Motivation: Seperation of Concerns - React Docs
"When designing interfaces, break down the common design elements (buttons, form fields, layout components, etc.) into reusable components with well-defined interfaces. That way, the next time you need to build some UI, you can write much less code. This means faster development time, fewer bugs, and fewer bytes down the wire."
Reusable Components - React Docs
"As your app grows it's helpful to ensure that your components are used correctly. We do this by allowing you to specify propTypes. React.PropTypes exports a range of validators that can be used to make sure the data you receive is valid."
The documentation for componentWillReceiveProps states that componentWillReceiveProps will be invoked when the props change as the result of a rerender. Some people assume this means "if componentWillReceiveProps is called, then the props must have changed", but that conclusion is logically incorrect.
class Component extends React.Component {
componentWillReceiveProps(nextProps) {
console.log('componentWillReceiveProps', nextProps.data.bar);
}
render() {
return <div>Bar {this.props.data.bar}!</div>;
}
}
var container = document.getElementById('container');
var mydata = {bar: 'drinks'};
ReactDOM.render(<Component data={mydata} />, container);
mydata.bar = 'food'
ReactDOM.render(<Component data={mydata} />, container);
mydata.bar = 'noise'
ReactDOM.render(<Component data={mydata} />, container);- The old
mydataand the newmydataare actually the same physical object (only the object’s internal value changed). Since the references are triple-equals-equal, doing an equality check doesn’t tell us if the value has changed. The only possible solution would be to have created a deep copy of the data, and then later do a deep comparison - but this can be prohibitively expensive for large data structures (especially ones with cycles). - The
mydataobject might contain references to functions which have captured variables within closures. There is no way for React to peek into these closures, and thus no way for React to copy them and/or verify equality. - The
mydataobject might contain references to objects which are re-instantiated during the parent's render (ie. not triple-equals-equal) but are conceptually equal (ie. same keys and same values). A deep-compare (expensive) could detect this, except that functions present a problem again because there is no reliable way to compare two functions to see if they are semantically equivalent.
- Parent Child relationships is irrelavent
- Same exact pattern as before
- Hides the current state of dependencies.
- Makes the compoenents always dependent on injector. Limiting their reuse.
- Removes testing data using
propTypevalidation. - More boilerplate code is required for testing.
- Less declarative API to understand the data required for that component.
- Will cause unnessary render cycles by react unable to see if it's data has changed.
