# Advanced React _by React Training_ > Personal notes while working through Advanced React: https://courses.reacttraining.com/p/advanced-react Granted this is a contrived example, but it's still something I took notice to: in those "Advanced React" videos I've been watching, Ryan Florence codes very slowly, and does not make one quick change and jump back to the browser to see what changed. He stops and thinks. He asks himself (or the viewer) questions. He wonders what different approaches might look like. He double checks his work. He is in no rush. And it is _so much_ more intelligent of a way to program. The way Ryan Florence handles this course is unlike most tutorials I find. He is lighthearted and funny throughout the whole series, and that makes watching and listening to him all the more pleasurable. Code is often taken far too seriously (I am certainly guilty of this), but Ryan is able to take complex topics and make them entertaining. His pairing of humor and insight is invaluable, and I have learned more than just how to code from watching him. He is also highly experimental in his way of programming, and uses code as a medium of thought process. When he has a crazy idea, he tries it out; when something doesn't work, he removes it and tries a different path; when his spidey-sense is tingling, he double checks his work and is not unduly quick to mash the save button and see what renders. I really love the way these exercises are set up. Rather than having you implement something from a blank canvas, many of the exercises come with some code that is already "using" the code we are meant to implement. What I mean by this is that the consumption of the API has already been created for us—what we need to do is implement the internals of the API. This provides a great starting point and also helps us in another way. When writing code on my own now (i.e. starting from a "blank canvas"), I have often found myself writing my API before I have even decided how I'd like to interact with it. After watching this series, I have started to write what I would _expect_ my API to provide before actually writing it. ## Section 01 - Imperative vs. Declarative The topic of this section is to discuss how to create React components that isolate imperative operations. We are shown how to create "behavioral components": components that don't render any UI but just provide some sort of behavior or state. ### Take Aways > "One of the things I love about React is that it lets me eliminate time in my > code; I don't have to think about time, I just have to think about snapshots in > time. At any time in a React app, we can look at the state of the app and then > look at the render methods of our components, and we should be able to predict > what the screen is going render." — Ryan Florence > "You can tell when you've got a good abstraction not just when you can add > code easily, but when you can remove code easily." — Michael Jackson The "behavioral" component `` is a great example of a component that has this kind of nice abstraction. In order to remove the behavior it provides, one need only to remove the component and render its children as they are. _That's it_. The code is very easily removable without breaking any functionality, and that's wonderful abstraction to work with when building. This idea of "behavioral" React Components—or components that don't render anything but just provide some sort of behavior or state—is still quite novel to me. The examples in this section are intriguing (especially the `` component from the lecture, which I initially saw a while back at Ryan Florence's React Rally 2016 talk), and I love how wonderful they package imperative code, but I still find myself struggling to fit the concepts into my own development. I suppose this will just take time, and having the knowledge that they exist will hopefully allow me to recognize when and where they can be used. Perhaps a good heuristic to slowly train myself to think about "behavioral" components would be to isolate my imperative code in a (temporarily named) `doImperativeStuff` method, and then figure out if I could instead just be "reacting" to state changes in `componentDidMount` and `componentDidUpdate`. `componentDidUpdate` can be thought of as React's way of saying _"okay, I've updated this component's state (and props) and I've updated the DOM in response to the changed state (and props), is there anything else you would like to react to? Is there anything else you'd like to do given the state of this app?"_. It is our chance to "do imperative stuff". It's React giving us a chance to participate in updating the app in response to a change in state. #### TL;DR **Behavioral React Components**: components that don't render anything but just provide some sort of behavior or state. `componentDidMount` and `componentDidUpdate` are wonderful React life cycle hooks that can be used together in order to isolate and perform imperative operations. They are React's way of giving us a chance to participate in updating the app in response to a change in state. ### Exercise Solution Code ```javascript // My solution class PinScrollToBottom extends Component { state = { scrollHeight: undefined } componentDidMount () { const { scrollHeight } = document.documentElement.scrollHeight window.scrollTo(0, scrollHeight) this.setState({ scrollHeight }) } componentDidUpdate (prevProps, prevState) { const { scrollTop, scrollHeight, clientHeight } = document.documentElement if (scrollHeight !== prevState.scrollHeight) { this.setState({ scrollHeight }) } if (scrollTop === scrollHeight - clientHeight) { window.scrollTo(0, scrollHeight) } } render () { return this.props.children } } ``` ```javascript // React Training's solution class PinScrollToBottom extends Component { componentDidMount () { this.scroll() } componentDidUpdate () { this.scroll() } componentWillUpdate () { const { clientHeight, scrollTop, scrollHeight } = document.documentElement this.scrolledUp = clientHeight + scrollTop < scrollHeight } scroll () { if (!this.scrolledUp) window.scrollTo(0, document.documentElement.scrollHeight) } render () { return this.props.children } } ``` ### Thoughts on Implementations I had to spend some time getting familiar with `scrollHeight`, `clientHeight`, `scrollTop`, and `scrollTo`, as these were methods and properties I had not previously worked with. Even after reading the MDN pages for these methods and properties, I had to experiment myself with them to fully grasp what they did. In my experimentation, I found that `scrollTop === scrollHeight - clientHeight` was true whenever I was scrolled to the bottom of the page. This obviously felt like a useful predicate to determine if I should be calling `scrollTo` and updating the scroll to the bottom of the page (i.e. `scrollHeight`). I also found that `clientHeight`, by its definition, never changed on a scroll event (it only changes when I change the height of my browser window). The biggest mental roadblock I ran into was around _when_ and _how_ I should be checking and updating the values of the `scrollHeight` and/or `scrollTop`. (Even just that sentence has so many ands and ors when I write it out now, ha!) It became clear that I needed to maintain some sort of state. In hindsight, after seeing the React Training solution, it appears that state may not have been the best way to handle this, as setting state will run a component through a render life cycle, and before I had my conditionals straight this resulted in many a stack overflow and unresponsive browser tabs. The solution provided by React Training is definitely more elegant than what I came up with. I love that both `componentDidMount` and `componentDidUpdate` simply make a call to `scroll`, and there is no component state. The more I look at it, the more my solution and the solution React Training provides seem similar. If I moved the `if` statement that makes the call to `scrollTo` to a `scroll` method, then the only difference between the two solutions is that mine uses `scrollHeight` state and React Training's uses `componentWillUpdate` (which can't update state) and an instance method `scrolledUp`. I'm endlessly impressed with the ever-growing number of problems React is capable of handling. Reactively rendering UI is one thing, but creating components that allow us to just provide behavior or state, and implementing them in a way that eliminates the need to think about time, is outstanding. ## Section 02 - Compound Components The topic of this section is on creating compound components and passing around implicit props in order to make components extendable and changeable in ways other than an ever-growing list of props. ### Take Aways > "Our [component] children are really just data. Just because someone told us > to render these children doesn't mean we have to render them in their current > form. We can map over them, inspect them, and clone them to render something > different based on the data the give us." — Ryan Florence In the beginning of the solution video, Ryan Florence talks about his preferred building process in React. He likes to start with building out state and then rendering that state. This way, he can just swap that state around to make sure that he's got things working right before adding in the event handlers. This helps him keep things declarative. --- The third or fourth time watching the lecture video I had an epiphany moment when I really understood why compound components are useful. When a component owns all of the rendering, when you have to do something new or different (e.g. disabling, changing render order, etc.) you end up having to create and expose a new prop. This is more or less the same as how we used to create elements with something like jQueryUI, which handled all rendering on an initial setup, and then exposed some kind of options object to give some instruction on what to render. But what if in React, instead of having one big component responsible for rendering, we create a bunch of components that "compound" together in order to get the unique interaction we're looking for? --- ```javascript
this.handleClick} /> ``` React doesn't add and remove event listeners like the above `onClick` prop, but instead it delegates one click from the top of the document to all of its virtual DOM elements. So it will just capture the click and check if there is actually a handler on the virtual element and only call the handler if it's present. But as the app switches back and forth between disabled and enabled, React isn't adding and removing event listeners, so code like the above snippet is actually really cheap in terms of performance. #### TL;DR **Implicit state**: non app-level state that the product developer doesn't care about or see in order to use the component API successfully; in React, this is generally accomplished by using `React.Children.map` and `React.cloneElement` in order to implicitly pass state-derived or event-callback props to children **Component components**: design architecture that helps avoid having one component in charge of too much rendering or state; the pattern encourages components to have limit responsibility, which helps identify where certain state and rendering should be owned in a component composition, rather than exposing all options at a single, top-level component with an ever-growing list of props Compound components generally have some level of implicit prop passing in order to accomplish the task of limited responsibility. Rather than treating children components as something to be immediately rendered, compound components clone and extend children components in order pass along useful data. Ryan Florence's state-first build process: - state - render - swap state around manually for testing - create event handlers ### Exercise Solution Code ```javascript // My solution class RadioGroup extends Component { state = { activeIndex: 0 } selectButtonIndex = activeIndex => { this.setState({ activeIndex }) } render () { return (
{this.props.legend} {React.Children.map(this.props.children, (child, index) => { return React.cloneElement(child, { isActive: index === this.state.activeIndex, onSelect: () => this.selectButtonIndex(index) }) })}
) } } class RadioButton extends Component { render () { const { isActive, onSelect } = this.props const className = 'radio-button ' + (isActive ? 'active' : '') return ( ) } } ``` ```javascript // React Training's solution class RadioGroup extends Component { state = { value: this.props.defaultValue } render() { const children = React.Children.map(this.props.children, (child) => { return React.cloneElement(child, { isActive: child.props.value === this.state.value, onSelect: () => this.setState({ value: child.props.value }) }) }) return (
{this.props.legend} {children}
); } } class RadioButton extends Component { render() { const { isActive, onSelect } = this.props const className = "radio-button " + (isActive ? "active" : ""); return ( ); } } ``` ### Thoughts on Implementations This section's lecture video was actually part of the preview for this course, so I had watched it when the course was initially announced and implemented my own version of actual radio elements (i.e. ``) for practice. I like that the exercise here uses radio buttons as it's example, as actual radio elements are a perfect real-world use-case for this kind of thing. There is not much of a difference between my solution and the one provided by React Training, but I did notice that the `defaultValue` prop was not actually included on `` in the solution source code. I could have just added it myself, but instead decided to follow the directions to a tee (i.e. not touching `` at all) and wondered if I could implement a solution without `defautlValue`. I ended up just using the child index as the active value, very similar to how the lecture code was written for tabs. Doing this is more or less the only difference between my solution and the provided solution. The only other differences are cosmetic, but perhaps one worth mentioning is that I inline rendered the `React.Component.map` call and the provided solution associates this with a local `children` variable. It's nothing more than a matter of taste, but I do think I prefer the provided solution over my own inline rendering. ## Section 03 - Context ### Take Aways With the added overhead React context brings, and with the core team discouraging its use, I'm unconvinced that using it makes for a better app API. The lecture on refactoring `` to use context was more code, a new API, and merely provided the short-sighted benefit of avoiding passing `activeIndex` and `onSelectTab` as props. The proposition of using context to alleviate a strict parent-child relationship between compound components seems a bit contrived, and ultimately adds more abstraction and opaqueness to the code. For example, in the lecture, why not just add a `className` prop to the `` and `` components? Perhaps the lecture refactor was a contrived example because of how small the benefit was to using context. I am thankful for the context API in popular libraries like Redux and React Router, as they save me from having to thread props to deeply nested children. However, I am not sold on it being a pattern used liberally or without a full understanding of how to isolate its ostensibly unstable API with higher order components. It should be seen as a second-class citizen to props and state, and including it in production-level code should not be taken lightly. That said, experimentation with context (especially when used with higher order components) can be a very eye-opening endeavour. It will help expose how libraries that use context do so intelligently and without the app developer needing to touch the context themselves. #### TL;DR React context is implemented in two parts: a provider and a consumer. The provider component uses the static `childContextTypes` property in order to specify the name, type, and requirement of the context it plans to provide, and then makes use of the `getChildContext` life cycle hook to actually provide a context object. The consumer component uses the static `contextTypes` property to specific the name, type, and requirement of the context it plans to consume. If the context properties on the provider match the context properties on the consumer, the consumer can make use of the properties available within the component on `this.context`. Context can then be used to share and manage state between components regardless of any intermediary UI. It acts as a bit of a wormhole between provider and consumer, breaking the normal boundaries of state management between components via props. While context makes it rather easy to pass state around to arbitrary levels of your component hierarchy, it also obfuscates where a component may be getting its state and props from. Context is also warned against by the React team due to its instable and change-prone api. ### Exercise Solution Code ```javascript class AudioPlayer extends React.Component { static childContextTypes = { play: PropTypes.func.isRequired, pause: PropTypes.func.isRequired, jumpForward: PropTypes.func.isRequired, jumpBack: PropTypes.func.isRequired, jump: PropTypes.func.isRequired, isPlaying: PropTypes.bool.isRequired, duration: PropTypes.number.isRequired, currentTime: PropTypes.number.isRequired } state = { isPlaying: false, duration: 0, currentTime: 0 } getChildContext () { const { isPlaying, duration, currentTime } = this.state return { play: this.play, pause: this.pause, jumpForward: this.jumpForward, jumpBack: this.jumpBack, jump: this.jump, isPlaying, duration, currentTime } } play = () => { this.audio.play() this.setState({ isPlaying: true }) } pause = () => { this.audio.pause() this.setState({ isPlaying: false }) } jumpForward = seconds => { this.audio.currentTime += seconds } jumpBack = seconds => { this.audio.currentTime -= seconds } jump = seconds => { this.audio.currentTime = seconds } render () { return (
) } } class Play extends React.Component { static contextTypes = { play: PropTypes.func.isRequired, isPlaying: PropTypes.bool.isRequired } render () { const { play, isPlaying } = this.context return ( ) } } class Pause extends React.Component { static contextTypes = { pause: PropTypes.func.isRequired, isPlaying: PropTypes.bool.isRequired } render () { const { pause, isPlaying } = this.context return ( ) } } class PlayPause extends React.Component { static contextTypes = { isPlaying: PropTypes.bool.isRequired } render () { return this.context.isPlaying ? : } } class JumpForward extends React.Component { static contextTypes = { jumpForward: PropTypes.func.isRequired, isPlaying: PropTypes.bool.isRequired } render () { const { jumpForward, isPlaying } = this.context return ( ) } } class JumpBack extends React.Component { static contextTypes = { jumpBack: PropTypes.func.isRequired, isPlaying: PropTypes.bool.isRequired } render () { const { jumpBack, isPlaying } = this.context return ( ) } } class Progress extends React.Component { static contextTypes = { duration: PropTypes.number.isRequired, currentTime: PropTypes.number.isRequired, jump: PropTypes.func.isRequired } jumpProgress = e => { const { duration, jump } = this.context const rect = e.currentTarget.getBoundingClientRect() const relativeClickPosition = e.clientX - rect.left const percentage = relativeClickPosition / rect.width const updatedTime = percentage * duration jump(updatedTime) } render () { const { currentTime, duration } = this.context const progressPercentage = ((currentTime / duration) * 100) || 0 return (
) } } ``` ```javascript class AudioPlayer extends React.Component { static childContextTypes = { audio: object } state = { isPlaying: false, duration: null, currentTime: 0, loaded: false } getChildContext() { return { audio: { ...this.state, setTime: (time) => { this.audio.currentTime = time }, jump: (by) => { this.audio.currentTime = this.audio.currentTime + by }, play: () => { this.setState({ isPlaying: true }) this.audio.play() }, pause: () => { this.setState({ isPlaying: false }) this.audio.pause() } } } } handleTimeUpdate = (e) => { this.setState({ currentTime: this.audio.currentTime, duration: this.audio.duration }) } handleAudioLoaded = (e) => { this.setState({ duration: this.audio.duration, loaded: true }) } handleEnded = () => { this.setState({ isPlaying: false }) } render() { return (
) } } class Play extends React.Component { static contextTypes = { audio: object } render() { return ( ) } } class Pause extends React.Component { static contextTypes = { audio: object } render() { return ( ) } } class PlayPause extends React.Component { static contextTypes = { audio: object } render() { const { isPlaying } = this.context.audio return isPlaying ? : } } class JumpForward extends React.Component { static contextTypes = { audio: object } render() { return ( ) } } class JumpBack extends React.Component { static contextTypes = { audio: object } render() { return ( ) } } class Progress extends React.Component { static contextTypes = { audio: object } handleClick = (e) => { const { audio } = this.context const rect = this.node.getBoundingClientRect() const clientLeft = e.clientX const relativeLeft = clientLeft - rect.left audio.setTime((relativeLeft / rect.width) * audio.duration) } render() { const { loaded, duration, currentTime } = this.context.audio return (
this.node = n} onClick={this.handleClick} onKeyDown={this.handleKeyDown} >
) } } ``` ### Thoughts on Implementations For a while, I was having trouble passing the audio ref through context. Since the ref prop is not invoked until [after the component is mounted][refs], and `getChildContext` is only called when state or props are changed, passing the instance property `this.audio` through context will be undefined in any consumer component. One solution I attempted was to set the audio element ref to an `` state property in `componentDidMount`, something like `this.setState({ audio: this.audio })`. I figured that since the ref would available after mount, this would work out fine. But I then ran into issues where the `` and `` components would not invoke a re-render when clicked, i.e., I could get the song to play and pause just fine thanks to the closures created around them, but disabling the buttons using `audio.paused` (which is what I had initially been using instead of an `isPlaying` state property) was not working because actions taken on the audio ref element passed down in context would not cause a re-render. Another workaround I came up with was to create closures like `play = () => this .audio.play()` as instance methods in `` and then pass `this.play` in context. This approach rubbed me the wrong way and is a duplication of what already exists on the audio element itself, but I ended up choosing this and moving forward nevertheless. I don't know why I didn't think of just inlining the functions on the `getChildContext` return object, but that is certainly the cleanest solution. I knew there had to be a better way than what I was doing, and continually found myself down a number of rabbit holes that made the solution appear far more difficult than it actually was. --- Updating the `` on click was also challenging. The audio element's `currentTime` property in seconds, but the best I can get with a user click is a percentage of the duration. I had to write out a way of converting the progress percentage from the user click into a (rough) value in seconds, and ended up using the same approach as the solution video. --- It's nice to see that as I progressed through this challenge I was hitting the same checkpoints that Ryan Florence seems to hit (albeit not quite as elegantly; my learning style was much more debugging and experimentation—the solution video made it all look so easy, ha!). #### Video solution bits that I liked - I liked the spreading of state in `getChildContext` - I thought it interesting that Ryan Florence uses `null` values for state that is "unknown" on initial render in a component - I liked how all of the context was put on an `audio` object rather than as top-level properties - Liked the use of a generic `jump` function and the passing of negative values to jump backwards #### My solution bits that I liked - I liked setting `currentTime` back to zero in `onEnded` callback, rather than leaving it with the progress bar filled in - I liked using `event.currentTarget` instead of using a ref for the progress bar click handler [refs]: https://facebook.github.io/react/docs/refs-and-the-dom.html#adding-a-ref-to-a-dom-element ## Section 04 - Higher Order Components This section shows off another way to handle the passing around of state and state management across an application. Unlike other methods we have seen in previous sections like the use of compound components, `cloneElement`, and context, the pattern described in section 04 introduces the concept of "higher order components" that can be used to make state sharing a bit more explicit. ### Take Aways The term "higher order component" is borrowed from the functional programming term "higher order function". A "higher order function" is a function that returns another function, generally used to compose function behavior together thanks to closure scopes. In a similar vein, a "higher order component" is a function that takes a component as an argument and return a new component with some extra behavior baked into it. A higher order component _is not_ a component that returns a component, because that is precisely what every React component does by default. Interesting to think that using `cloneElement` can more or less do the same thing that higher order components can do, they are just less explicit about it. It is harder to see that `` is passing state between `` and ``—there is nothing inherently telling that the `` is using some state derived from `` because all this looks like is a component hierarchy. Something like `withMedia(App)` is a bit more telling; we can infer from the "decorative" nature of `withMedia` that it is doing something with `App` and returning something new. > "If you're used to using classical inheritance to clean up code and share > behavior across an app, HOCs are a great substitute for that. You're not going > to see people doing inheritance in React." — Ryan Florence HOCs are a nice drop-in replacement for inheritance (actually closer to mixins or multiple-inheritance patterns). The HOC pattern is something I've worked closely with in the last few months (using ReactRedux's `conntect` and ReactRouter's `withRouter`), but hadn't taken the time to study or build out myself. It also appears to currently be the de facto way of passing state around a React application, although the "render prop/children as a function" pattern may usurp HOCs soon, as there are [many pitfalls to HOCs][hoc-pitfalls]. #### TL;DR Higher order components are functions that take a component as an argument and return a new component with some extra behavior baked into it. HOCs A great way to share behavior across an app. Watch out of common pitfalls of HOCs, including naming collisions when using multiple HOCs, confusion or indirection on which HOC is providing which props, and the number or precautions that must be taken when creating an HOC so as to not strip the original component of any its own implementation details. ### Exercise Solution Code ```javascript // My solution const withStorage = (key, default_) => Component => { return class WithStorage extends React.Component { state = { sidebarIsOpen: get(key, default_) } componentDidMount () { this.unsubscribe = subscribe(() => { this.setState({ sidebarIsOpen: get('sidebarIsOpen') }) }) } componentWillUnmount () { this.unsubscribe() } render () { return } } } ``` ```javascript // React Training's solution const withStorage = (key, default_) => (Comp) => ( class WithStorage extends Component { state = { [key]: get(key, default_) } componentDidMount() { this.unsubscribe = subscribe(() => { this.setState({ [key]: get(key) }) }) } componentWillUnmount() { this.unsubscribe() } render() { return ( ) } } ) ``` ### Thoughts on Implementations > "You'll see this across React generally [where] you'll get some sort of state > as a prop and then you'll get a function to change that state. This is as > fundamental as `` components having a `value` prop as well as an > `onChange` prop." — Ryan Florence Get some state, get a way to change that state. Many of the video solution details were lost on me when I attempted a solution of my own at first. - I forgot to use a computed property key for my `localStorage` component state variable (which wouldn't harm the usefulness of the HOC, but would certainly make the source code difficult to understand for anyone looking at it) - I didn't think about the naming collisions of calling my HOC component argument `Component` and people using `Component` as a named export of `React` - I didn't consider replacing `set` from `local-storage` with a setter like `setStorage` passed in from our HOC, which removes forcing users to import and acknowledge the API of `set` - I didn't think about how to handle props that may be coming in on the component we are wrapping (need to spread in `this.props` on the returned component) - I didn't think about how a consumer who has imported the HOC-wrapped component will not have access to `static` properties on the wrapped component (although, to be fair, this one does seem a bit more nuanced, and it would seem as though many people have deferred to handling it with an external lib) - I didn't consider adding our wrapped component as a static property of our HOC (although, to be fair, in my own experience with HOCs I have opted to also export the base "unwrapped" class as a named export for testing purposes instead of relying on a static method) [hoc-pitfalls]: https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce ## Section 05 - Render Prop Callbacks Section 05 was perhaps the most epiphanic section thus far, and really drives home what the point of this entire series is: abstracting behavior and state but still allowing the app to decide what to render based on that state. The lecture does a better job explaining different types of state and what patterns are useful for managing both. It introduces the latest pattern for handling application-level state: render prop callbacks. This pattern addresses some of the issues we've witnessed using HOCs, and makes it much clearer what it means to compose state dynamically. ### Take Aways > "Render callbacks and HOCs solve a lot of the same use-cases—in fact, every HOC > could be implemented as a render callback. The difference between them is where > the composition happens." — Ryan Florence Render callbacks compose dynamically. They occur while we're rendering the element in the `render` method of a component. HOCs compose statically. They occur while we are defining our component, not while we're rendering them. _App-level state_: state that the developer actually cares about; state the developer would like to have the ability to interact with (e.g. HOCs) _Implicit state_: state that isn't relevant to the developer or doesn't need to be seen or used or cared about by the developer (e.g. `React.cloneElement`, context) > "I'd like to be able to compose my state as easily as I compose my components > together." — Ryan Florence Render props are another great way to move behavior out of one component and into another so that it can be more sharable and reusable, but the real benefit is that it allows you to compose your state and share it across the app. #### TL;DR > "Everything we've been doing [throughout this series] has been trying to give > rendering back to the developer, and move [state management and imperative > operations] somewhere else." — Ryan Florence We named a prop "render", gave it a function, and in the component's `render` method that we put the render prop on, we call the function and pass it the component's state. So now we are not only able to compose the behavior and look of our components, but we can also compose the state. Render props are a great way to share _application-level_ state and behavior. If you wish to share _implicit_ state, use context or `React.cloneElement`. ### Exercise Solution Code ```javascript // My solution class GeoAddress extends React.Component { state = { address: null } componentDidMount () { this.updateAddress(this.props.coords) } componentWillReceiveProps (nextProps) { if (nextProps.coords !== this.props.coords) { this.updateAddress(nextProps.coords) } } updateAddress = async (coords) => { const address = await getAddressFromCoords(coords.lat, coords.lng) this.setState({ address }) } render () { return this.props.render(this.state.address) } } class App extends React.Component { render () { return (
{ return error ? (
Error: {error.message}
) : coords ? ( ( )} /> ) : ( ) }} />
) } } ``` ```javascript // React Training's solution class GeoAddress extends React.Component { static propTypes = { coords: React.PropTypes.object } state = { address: null, error: null } componentDidMount() { if (this.props.coords) this.fetchAddress() } componentDidUpate(nextProps) { if (nextProps.coords !== this.props.coords) { this.fetchAddress() } } fetchAddress() { const { lat, lng } = this.props.coords getAddressFromCoords(lat, lng).then(address => { this.setState({ address }) }) } render() { return this.props.render(this.state) } } class App extends React.Component { render() { return (
( state.error ? (
Error: {state.error.message}
) : state.coords ? ( ( )}/> ) : ( ) )}/>
) } } ``` ### Thoughts on Implementations As Ryan states in the solution video, refactoring components to use a render prop is incredibly easy—almost literally just cut and paste and add a render method. This pattern makes it very easy to take some behavior and some state out of a component and make it reusable. It's also very apparent that state is being composed together with this pattern. Since we are using callbacks, we get the power of closures, and this allows nested components using render prop callbacks to reference parent render prop callback arguments, thus allowing state to be composed dynamically in the render method, just like components. The video for the solution differs slightly than the actual solution code here in that in the solution video `getAddressFromCoords` errors are actually caught and the `` error state is set. I also like the use of `||` in the render prop callback for `` to determine what text to render. The solution video uses conditional checks inside of `` to only fetch the address if the coords props is available in order to handle the case where the fetch may be made before the map is fully loaded. While I understand that the check is saving us from when we pull `lat` and `lng` off of an undefined `this.props.coords`, I'm confused as to when this would ever happen. Since we only render `` if `coords` are available from the `` render prop callback argument, won't we always have the `coords` as a prop? Perhaps there is a case where somehow `coords` is not an object? Even if `lat` and `lng` are `undefined`, the Google Maps API is nice enough to throw a 400 error for invalid coordinates, which we would catch in the error handler callback. In any case, I suppose it is an easy way to avoid having our app blow up in case `coords` is not an object. I like the solution videos use of `componentDidUpdate` life cycle hook to determine if the address needs to be updated, as opposed to my use of `componentWillReceiveProps` to perform the same check. The only reason being that `componentDidUpdate` will be able to use `this.props.coords` (just like `componentDidMount`) rather than `nextProps.coords`, which is what must be used with `componentWillReceiveProps`. I also like it because `componentDidMount` and `componentDidUpdate` seem like a better life cycle pair, and because I always forget how to spell `componentWillReceiveProps` ;) > "That's the whole point of this […]. Abstract behavior and state into a > different component but still allow the app to decide what to render based on > that state." — Ryan Florence The above quote really struck a chord with me. Writing software all day, it's easy to get sucked into syntax and stick to comfortable and known patterns. But quotes like this one beg the question of what is reality? What is the code for? Using the render prop callback pattern is immensely beneficial at giving power back to the developer for how things should render. > "Good abstractions make it easy to add code and behavior; really good > abstractions make it really easy to remove code." — Ryan Florence ## Section 06 - Implementing React Router This was the first section without a lecture, and was an exercise to use the patterns talked about throughout this course to reimplement React Router. Not only does it show off how React Router works on a basic level, it provides a practical application for the patterns discussed throughout this series. ### Take Aways We used context and we used compound components to pass around some _implicit_ state (i.e. `history`) in order to make an imperative APIs more declarative (e.g. instead of everyone having to do `history.push` in their anchor tag click handlers, we can just declare a `` and wrap the behavior in the component's implementation that the rest of the app doesn't have to know about.) We also used the render prop callback pattern to provide inline rendering for `` components. We also saw the use of a `component` prop that allows the developer to just pass the `` a component they would like to render. This pattern is very similar to the HOC pattern and the render prop callback pattern, and in the real React Router package is a way to pass on some [props][route-props] to the component provided. ### Exercise Solution Code ```javascript // My solution class Router extends React.Component { static childContextTypes = { history: PropTypes.object.isRequired } state = { location: null } getChildContext () { return { history: { push: (to) => this.history.push(to), location: this.state.location } } } componentDidMount () { this.history = createBrowserHistory() this.setState({ location: this.history.location }) this.unsubscribe = this.history.listen(() => { this.setState({ location: this.history.location }) }) } componentWillUnmount () { this.unsubscribe() } render () { return this.props.children } } class Route extends React.Component { static contextTypes = { history: PropTypes.object.isRequired } render () { const { history } = this.context const { path, exact, render, component: Component } = this.props if (history.location) { if (exact && history.location.pathname !== path) { return null } if (!history.location.pathname.match(path)) { return null } } if (Component) { return } if (render) { return render() } return null } } ``` ```javascript // React Training's solution class Router extends React.Component { history = createBrowserHistory() static childContextTypes = { history: this.history } getChildContext() { return { history: this.history } } componentDidMount() { this.unsubscribe = this.history.listen(() => { this.forceUpdate() }) } componentWillUnmount() { this.unsubscribe() } render() { return this.props.children } } class Route extends React.Component { static contextTypes = { history: PropTypes.object.isRequired } render() { const { path, exact, render, component:Component } = this.props const { location } = this.context.history const match = exact ? location.pathname === path : location.pathname.startsWith(path) if (match) { if (render) { return render() } else if (Component) { return } else { return null } } else { return null } } } ``` ### Thoughts on Implementations Figuring out how to check if the current location pathname matched the path prop was quite difficult The snippet above ("My solution") is a trivial solution I came up with that includes code to meet the *bare minimum* necessary to make the UI behave as expected. However, having used React Router since version 4 was released, I knew there were other things that would be worth implementing to better mimic the real library. Most notably, I wanted to see if I could pass the correct [props][route-props] to components rendered within ``. There is an extra commit for my solution code that shows how I went about this extra step. I had followed Tyler McGinnis's [Build your own React Router v4][build-react-router-v4] tutorial article some months ago, but it was obvious I had not retained everything I had learned from that article when I attempted to reimplement it again here. I inevitably succumbed to looking at my solution from the tutorial article for pointers on how to move forward, but still ended up creating a new solution on my own for this self-propagated exercise extension. I continually forget that `this.forceUpdate` is a React-provided method, and therefore continually forget to think about it as a possible solution. Thinking of this would have come in handy when I was trying to figure out what to do with changes in the URL. I had settled on creating local component state to update the `location` context, which was unnecessary since the `history` instance property was already keeping track of URL changes. I also forget about creating custom [class instance properties][class-properties] when working with components, despite never hesitating to create `state` and custom class instance methods all the time. It's funny to me how complacent we can become with repetitive syntax and forget what is actually happening under the hood. The solution video sets `history` as a class instance property on ``, which is great because it will be set before it would if it were in `componentDidMount`, which is necessary when using it on context since child consumer components will likely be expecting it to be available on their initial render. (This would have saved me from making the check to see if `history.location` was defined or not in the render method of ``.) This will be helpful to keep in mind when I develop with imperative APIs passed to children components. I'm still learning new things that came out in ES6, like the [`startsWith`][startsWith] string method that Ryan uses in the solution video to match the url location with the path prop in the `` component. Granted, the actual library needs to keep a reference of the match to pass down to the rendered component, and `startsWith` simply returns a boolean, but it's still good to know that this method exists on strings (as if there weren't already enough substring methods in JavaScript, heh). Interesting that the React Training solution uses `this.history` for the value in `childContextTypes`—for one reason or another I had always assumed only `PropTypes` properties could be used here, but it makes total sense to use any object as a type. However, the solution video is clever enough to never open the browser developer tools during the recording, which would have shown an invalid type warning for the `this.history` being `undefined` on the `` component's initial render. [route-props]: https://reacttraining.com/react-router/web/api/Route/Route-props [startsWith]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith [build-react-router-v4]: https://tylermcginnis.com/build-your-own-react-router-v4/ [class-properties]: https://babeljs.io/docs/plugins/transform-class-properties/ ## Section 07 - Implementing React Redux React Redux is the binding to the Redux state management library for React. In this exercise-only section we roll our own `` component and `connect` HOC, the two pillars of React Redux. ### Take Aways I have actually implemented a good deal of Redux on my own, writing functions like `createStore` and `combineReducers` by hand in order to understand how they work. However, I had never attempted to write parts of the React Redux library on my own before, so accomplishing that in this section's exercise was a lot of fun. Now all that's left to implement by hand is React itself ;) It hit me that `mapStateToProps` and `mapDispatchToProps` are not required to connect React components to Redux, they are niceties that put the app developer in charge of what to call the state in the component. You could totally just pass the entire state to the connected component, but having `mapStateToProps` gives the power back to the developer to decide what slices of state a component should know about and also what to call them as props. #### TL;DR This exercise is more or less the heart of what React Redux is, using context and HOCs in order to facilitate a pleasant pattern to create apps in React. ### Exercise Solution Code ```javascript // My solution const connect = (mapStateToProps, mapDispatchToProps) => { return (Component) => { return class extends React.Component { static contextTypes = { store: PropTypes.object.isRequired } componentDidMount () { this.unsubscribe = this.context.store.subscribe(() => this.forceUpdate()) } componentWillUnmount () { this.unsubscribe() } render () { const { getState, dispatch } = this.context.store const state = getState() const propsFromState = mapStateToProps(state) const propsFromDispatch = mapDispatchToProps(dispatch) return ( ) } } } } ``` ```javascript // React Training's solution const connect = (mapStateToProps, mapDispatchToProps) => { return (Component) => { return class extends React.Component { static contextTypes = { store: PropTypes.object.isRequired } componentDidMount() { this.unsubscribe = this.context.store.subscribe(() => { this.forceUpdate() }) } componentWillUnmount() { this.unsubscribe() } render() { return ( ) } } } } ``` ### Thoughts on Implementations This is the first time that the React Training solution and mine were more or less exactly the same. The only notable difference between the two implementations are that spread `this.props` on the component returned by `connect` in order to preserve and prioritize any props that were added to `` by the user. However, after playing around with manually passing a `todos` props to ``, I am seeing now that **React Redux does not prioritize nor preserve own props on a connected component if they happen to duplicate prop names passed in via mapStateToProps or mapDispatchToProps.** So really my solution deviates more from the real library spec, and I should have passed `this.props` before passing the props from state and dispatch. Today I learned that passing an uninvoked callback to a function will result in my app blowing up with errors if the function that invokes the callback is dependent on a specific receiver (i.e. the right `this`). ```javascript // `this` will be undefined when the callback is invoked, which will break the app this.context.store.subscribe(this.forceUpdate) // thanks to the callback closure, `this` is preserved as the component and the method works as expected this.context.store.subscribe(() => this.forceUpdate()) ``` Many a time I have done something like `stuff.map(toSomethingElse)` (as opposed to `stuff.map((data) => toSomethingElse(data))`) when I'm piping data from one function to the next, but I hadn't considered the implications this would have around this callback using `this`.