Summary
This post contains notes from chapter 5 in Fullstack React - "Advanced Component Configuration with props, state, and children". The purpose of this chapter is to gain a better understanding of configuring React components.
Overview
In chapter 4, we gained a deeper understanding of what is happening with React elements. In this chapter, we dove deeper into React components. A lot of the things we saw in this chapter were things we already covered, but we cover it in more detail in this chapter.
Creating a component with ES6 syntax
Creating a class with React.createClass
render()
React expects the render method to return a single child element.
props
props are immutable pieces of data that are passed into child components from parents.
We can pass any JavaScript object through props. We can pass primitives, simple JavaScript objects, atoms, functions etc. We can even pass other React elements and Virtual DOM nodes.
context is not a replacement for using props and should be used sparingly and only for things that are truly a global context, so something like getting the logged in user.
Just like when state or props change, a component will be re-rendered if context changes.
To setup a variable to be inserted into context and used by child components, we have to do the following:
1) Add the childContextTypes and getChildContext properties to the parent class to define the context and then set the values for the variables in the context
2) Subscribe to the context and use the variables in the child classes
The following is the same example using React.createClass instead of ES6 classes:
State is necessary, but it is also something we should try to minimize. We should have as few stateful components as possible because it introduces complexity and makes composing components more difficult. Also, updating state can be very slow because that triggers an Actual DOM refresh. In general, the only information that we should put in state are values that are not computed and do not need to be sync'd across the app. Instead, we should try to pass values in via props as much as possible.
To make a component stateful, we just have to set the initial state for the component using the getInitialState() method. You subsequently change values stored in the state using the setState() method. Note, I don't think the getInitialState() is necessary if we are using the plugin transform-class-properties (but I'm not 100% certain of that - see Chapter 1 Notes).
This is the same thing but using React.createClass. Notice how we use getInitialState instead of simply declaring state as a property:
Notes about event handlers
If you want to pass a value to your click event handler, there is a little trick that you have to do. This is a common pattern that you will see in most React apps.
Notice that I have a different event handler for each div. I did this to illustrate the different ways that a method can be defined in the class and called (albeit erroneously for myHandler1 and myHandler3)
Note that you would define and use the stateless component the same way if you were using React.createClass as you would in the above ES6 class example.
When you have a component that has nested child components, the child components will not render unless we specifically tell the render method for the parent component to render them. In order to do tell them to render, we use the React provided {this.props.children} property. I think this.props.children is typically when used when you have an unknown / dynamic set of child components.
React.Children.map
When working with child components, we often want to do something to those components, like wrapping them in a containing div, adding a css class, adding a common property that should be on all children, etc. In order to do this, we can use React.Children.map to loop through the children and add a whatever we want to each child component.
map() vs. forEach()
We will typically use map() when working with an array of child components. map() will loop through the array, perform some action against each item, and then return an array of the results of that action. forEach() will loop through the array, perform some action against each item and that is the end, it doesn't return an array of results.
The following will write 1, 2, and 3 to the console.log and render 3 divs with the numbers 1, 2, and 3 within the Movie element.
The following will write 1, 2, and 3 to the console.log, but it will not add any divs to the Movie element.
React.Children.toArray()
Sometimes when working with this.props.children, it can be difficult because of the data structure that it returns. To alleviate this, React provides the React.Children.ToArray() method to convert the child elements into a simple array, which you can then use to perform your typical functions that you see when working with an array (such as sorting).
Quick note about the book...I feel like this section in the book could have been a little better written. I wasn't exactly clear on why we would use this.props.children or how it really worked. I think a full example of this would have been more useful. I ended up reading more here https://learn.co/lessons/react-this-props-children to figure out what this feature is and why it is used.
Creating React Components
There are 2 ways to create React components. We can use the ES6 class syntax or using traditional JavaScript code with React.createClassCreating a component with ES6 syntax
1 2 3 4 5 6 7 8 9 | class JSXExample extends React.Component { render () { return ( <div> Simple React Element with ES6 syntax </div> ); } } |
Creating a class with React.createClass
1 2 3 4 5 6 7 8 9 | const JSXExample = React.createClass({ render: function() { return ( <div> Simple React Component with React.createClass </div> ) } }); |
render()
React expects the render method to return a single child element.
props
props are immutable pieces of data that are passed into child components from parents.
We can pass any JavaScript object through props. We can pass primitives, simple JavaScript objects, atoms, functions etc. We can even pass other React elements and Virtual DOM nodes.
- PropTypes - PropTypes are a way to validate the values that are passed in through props.
- There are a number of different prop types that we can validators:
- React.proptypes.string
- React.proptypes.number
- React.proptypes.boolean
- React.proptypes.function
- React.proptypes.object
- React.proptypes.shape
- React.proptypes.oneOf
- React.proptypes.instanceOf
- React.proptypes.array
- React.proptypes.arrayOf
- React.proptypes.node
- React.proptypes.element
- React.proptypes.any
- React.proptypes.required
- See Appendix A: PropTypes for more information
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
class JSXExample extends React.Component { render () { return ( <div> <Movie title="Something about Mary" price={14.99} /> </div> ); } } class Movie extends React.Component { render () { return ( <div> {this.props.title} {this.props.price} </div> ); } } Movie.propTypes = { title: React.PropTypes.string, price: React.PropTypes.number }
- The following is the same thing, but using React.createClass instead of ES6 classes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
const JSXExample = React.createClass({ render: function() { return ( <div> <Movie title="Something about Mary" price={14.99} /> </div> ) } }); const Movie = React.createClass({ propTypes: { title: React.PropTypes.string, price: React.PropTypes.number }, render: function() { return ( <div> {this.props.title} {this.props.price} </div> ) } });
- getDefaultProps()
- getDefaultProps lets you define default values for your props. In the following example, the Movie component will be rendered with the Title set to 'Default Movie Title' and the price to 0.00
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
class JSXExample extends React.Component { render () { return ( <div> <Movie /> </div> ); } } class Movie extends React.Component { render () { return ( <div> {this.props.title} {this.props.price} </div> ); } } Movie.defaultProps = { title: 'Default Movie Title', price: 0.00 }
context
context is an experimental feature that let's us create "global" variables so we don't have to continually pass common props down across child components. React will automatically make any variables that are stored in a component's context available to child components that subscribe to that context.context is not a replacement for using props and should be used sparingly and only for things that are truly a global context, so something like getting the logged in user.
Just like when state or props change, a component will be re-rendered if context changes.
To setup a variable to be inserted into context and used by child components, we have to do the following:
1) Add the childContextTypes and getChildContext properties to the parent class to define the context and then set the values for the variables in the context
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class JSXExample extends React.Component { getChildContext = () => { return { userId: 'homer' }; }; render () { return ( <div> <Movie title="The Jerk" price="9.99"/> </div> ); } } // Define the context types on the parent component JSXExample.childContextTypes = { userId: React.PropTypes.string } |
2) Subscribe to the context and use the variables in the child classes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Movie extends React.Component { render () { return ( <div> UserId = {this.context.userId} {/* Use the userId in context */} <br /> {this.props.title} {this.props.price} </div> ); } } // Subscribe to the context types in the child component(s) Movie.contextTypes = { userId: React.PropTypes.string } |
The following is the same example using React.createClass instead of ES6 classes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | const JSXExample = React.createClass({ // Define the context types on the parent component childContextTypes: { userId: React.PropTypes.string }, // Set the value of the context variables getChildContext: function() { return { userId: 'homer2' } }, render: function() { return ( <div> <Movie title="The Jerk2" price={9.99} /> </div> ) } }); const Movie = React.createClass({ // Subscribe to the context types in the child component(s) contextTypes: { userId: React.PropTypes.string }, render: function() { return ( <div> UserId = {this.context.userId} {/* Use the userId in context */} <br /> {this.props.title} {this.props.price} </div> ) } }); |
state
state is where we hold data local to a component. State is private to a component and mutable. When state is changed, the component needs to be re-rendered.State is necessary, but it is also something we should try to minimize. We should have as few stateful components as possible because it introduces complexity and makes composing components more difficult. Also, updating state can be very slow because that triggers an Actual DOM refresh. In general, the only information that we should put in state are values that are not computed and do not need to be sync'd across the app. Instead, we should try to pass values in via props as much as possible.
To make a component stateful, we just have to set the initial state for the component using the getInitialState() method. You subsequently change values stored in the state using the setState() method. Note, I don't think the getInitialState() is necessary if we are using the plugin transform-class-properties (but I'm not 100% certain of that - see Chapter 1 Notes).
- getInitalState() is only called once
- If you are going to set a state value to a prop that was passed in, getInitialState is the only place you should ever do this. If you do this, you will typically want to name the prop something like initialValue.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | class JSXExample extends React.Component { render () { return ( <div> <Movie title="The Jerk changed" price="9.99"/> </div> ); } } class Movie extends React.Component { // Notice how we define intial state as a property on an ES6 class state = { title: this.props.title || '' } handleTitleChange () { return (evt) => { this.setState({ title: evt.target.value}); console.log(this.state.title); // Notice that this still has the previous value of state.title } } render () { return ( <div> Which movie do you like best? <input type="text" value={this.state.title} onChange={this.handleTitleChange()}/> </div> ); } } |
This is the same thing but using React.createClass. Notice how we use getInitialState instead of simply declaring state as a property:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | const JSXExample = React.createClass({ render: function() { return ( <div> <Movie title="The Jerk2" price={9.99} /> </div> ) } }); const Movie = React.createClass({ getInitialState: function() { return { title: this.props.title || '' } }, handleTitleChange () { return (evt) => { this.setState({ title: evt.target.value}); console.log(this.state.title); // Notice that this still has the previous value of state.title } }, render: function() { return ( <div> Which movie do you like best? <input type="text" value={this.state.title} onChange={this.handleTitleChange()}/> </div> ) } }); |
Notes about event handlers
If you want to pass a value to your click event handler, there is a little trick that you have to do. This is a common pattern that you will see in most React apps.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | class JSXExample extends React.Component { render () { return ( <div aria-hidden={true}> <Movie title='The Jerk' director = 'Carl Reiner' /> </div> ); } } class Movie extends React.Component { myHandler1 = (title) => { // This alert will be fired when the component is rendered alert('myHandler1: ' + title); }; myHandler2 (title) { return (evt) => { // This alert will be fired when the div is clicked alert('myHandler2: ' + title); } } myHandler3 = (title) => { // This alert will be fired when the component is rendered alert('myHandler3 ' + title); } myHandler4 = () => { // This alert will be fired when the div is clicked alert('myHander4 ' + this.props.title); } render () { return ( <div> <div onClick={this.myHandler1(this.props.title)}>This div will not do anything when I click on it</div> <div onClick={this.myHandler2(this.props.title)}>This div will make an alert(title) as expected</div> <div onClick={this.myHandler3(this.props.title)}>This div will not do anything when I click on it</div> <div onClick={this.myHandler4}>This div will make an alert(title) as expected</div> </div> ); } } ReactDOM.render(<JSXExample />, document.getElementById('content')); |
Notice that I have a different event handler for each div. I did this to illustrate the different ways that a method can be defined in the class and called (albeit erroneously for myHandler1 and myHandler3)
- myHandler1 will be called when the first div is rendered. When the div is clicked, nothing happens because onClick is actually set to null when the div is rendered since myHandler1 didn't return anything.
- myHandler2 will be called when the second div is rendered. However, since myHandler2 returns a function, that function is called when the div is clicked and we see an alert statement as expected.
- myHandler3 will be called when the third div is rendered. Just like the first div, nothing happens because onClick is actually set to null when the div is rendered since myHandler3 didn't return anything.
- myHandler4 will not be called when the fourth div is rendered. When the div is rendered, onClick is set to the function myHandler4. In the first 3 examples, onClick is set to the result of the corresponding handler.
Updating state has performance implications because it triggers a refresh, which means we will be updating the Actual DOM. Updating the Actual DOM is slow, so we want to do this as little as possible.
Importing CSS with webpack loader
With webpack loader, we are able to access styles similar to the way that we access objects. For example, if we have a style called .active, we are able to reference that style in our React code by styles.active.
INSERT SAMPLE CODE HERE
Importing CSS with webpack loader
With webpack loader, we are able to access styles similar to the way that we access objects. For example, if we have a style called .active, we are able to reference that style in our React code by styles.active.
INSERT SAMPLE CODE HERE
Stateless Components
Stateless components are the preferred method for creating components because they reduce state and increase performance since React doesn't have to keep track of component instance in memory and do any dirty checking. We should try to use stateless components as much as possible.- Stateless components aren't really components in the traditional sense. They are simply functions with no backing instance and just return a block of JSX.
- Props are just passed in as a parameter to the function.
- There is no concept of the this keyword or state
- You can still use PropTypes and defaultProps
- Stateless components also help by making components reusable throughout the application.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | class JSXExample extends React.Component { render () { return ( <div> <Header userId='homer' /> <Movie title='The Jerk changed' price='9.99'/> </div> ); } } class Movie extends React.Component { render () { return ( <div> Which movie do you like best? {this.props.title} </div> ); } } // Stateless header component const Header = function (props) { return ( <div> <h1>This is the header</h1> <h2>Welcome {props.userId}</h2> </div> ) } |
Note that you would define and use the stateless component the same way if you were using React.createClass as you would in the above ES6 class example.
children
this.props.childrenWhen you have a component that has nested child components, the child components will not render unless we specifically tell the render method for the parent component to render them. In order to do tell them to render, we use the React provided {this.props.children} property. I think this.props.children is typically when used when you have an unknown / dynamic set of child components.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | class JSXExample extends React.Component { render () { return ( <div> <Movie title='The Jerk changed' price='9.99'> <Actor name="Steve Martin" /> <Actor name="Bernadette Peters" /> </Movie> </div> ); } } class Movie extends React.Component { render () { return ( <div> {this.props.title} <br /> {this.props.children} {/* The Actor elements will not render without this line */} </div> ); } } class Actor extends React.Component { render () { return ( <div> Actor: {this.props.name} </div> ) } } |
React.Children.map
When working with child components, we often want to do something to those components, like wrapping them in a containing div, adding a css class, adding a common property that should be on all children, etc. In order to do this, we can use React.Children.map to loop through the children and add a whatever we want to each child component.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | class JSXExample extends React.Component { render () { return ( <div> <Movie title='The Jerk changed' price='9.99'> <Actor name="Steve Martin" /> <Actor name="Bernadette Peters" /> </Movie> </div> ); } } class Movie extends React.Component { renderChildren = React.Children.map(this.props.children, child => { return React.cloneElement(child, { sampleProp: 'This was added to all children' }); }); render () { return ( <div> {this.props.title} <br /> {this.renderChildren} {/* Render Actor elements with extra sampleProp */} </div> ); } } class Actor extends React.Component { render () { return ( <div> Actor: {this.props.name} {this.props.sampleProp} </div> ) } } |
map() vs. forEach()
We will typically use map() when working with an array of child components. map() will loop through the array, perform some action against each item, and then return an array of the results of that action. forEach() will loop through the array, perform some action against each item and that is the end, it doesn't return an array of results.
The following will write 1, 2, and 3 to the console.log and render 3 divs with the numbers 1, 2, and 3 within the Movie element.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | class JSXExample extends React.Component { showArray = () => { return [1, 2, 3].map((n) => { return <div>{n}</div>; }); } render () { return ( <div> <Movie title='The Jerk changed' price='9.99'> {this.showArray()} </Movie> </div> ); } } class Movie extends React.Component { renderChildren = React.Children.map(this.props.children, child => { return React.cloneElement(child, { sampleProp: 'This was added to all children' }); }); render () { return ( <div> {this.props.title} <br /> {this.renderChildren} {/* Render Actor elements with extra sampleProp */} </div> ); } } |
The following will write 1, 2, and 3 to the console.log, but it will not add any divs to the Movie element.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | class JSXExample extends React.Component { showArray = () => { return [1, 2, 3].forEach((n) => { console.log(n); return <div>{n}</div>; }); } render () { return ( <div> <Movie title='The Jerk changed' price='9.99'> {this.showArray()} </Movie> </div> ); } } class Movie extends React.Component { renderChildren = React.Children.map(this.props.children, child => { return React.cloneElement(child, { sampleProp: 'This was added to all children' }); }); render () { return ( <div> {this.props.title} <br /> {this.renderChildren} {/* Render Actor elements with extra sampleProp */} </div> ); } } |
React.Children.toArray()
Sometimes when working with this.props.children, it can be difficult because of the data structure that it returns. To alleviate this, React provides the React.Children.ToArray() method to convert the child elements into a simple array, which you can then use to perform your typical functions that you see when working with an array (such as sorting).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | class JSXExample extends React.Component { render () { return ( <div> <Movie title='The Jerk changed' price='9.99'> <Actor name="Steve Martin" /> <Actor name="Bernadette Peters" /> </Movie> </div> ); } } class Movie extends React.Component { render () { const arr = React.Children.toArray(this.props.children); return ( <div> {this.props.title} <br /> {arr.sort((a, b) => a.props.name > b.props.name )} {/* Render Actor elements sorted alphabetically by name */} </div> ); } } class Actor extends React.Component { render () { return ( <div> Actor: {this.props.name} </div> ) } } |
Quick note about the book...I feel like this section in the book could have been a little better written. I wasn't exactly clear on why we would use this.props.children or how it really worked. I think a full example of this would have been more useful. I ended up reading more here https://learn.co/lessons/react-this-props-children to figure out what this feature is and why it is used.
statics
Statics in React are pretty much the same thing that they are in any other programming language, meaning you are able to call the function directly without creating an instance of that component.
In the following example, we call the getSomeValue method directly in the movie class, which will cause the text "The result of a static function" to be rendered when JSXExample is rendered.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | class JSXExample extends React.Component { render () { return ( <div> <Movie title='The Jerk changed' price='9.99' /> {Movie.getSomeValue()} </div> ); } } class Movie extends React.Component { static getSomeValue () { return 'The result of a static function'; } render () { return ( <div> {this.props.title} </div> ); } } |
No comments:
Post a Comment