diff --git a/FAQ.md b/FAQ.md index 7e23f4912b..832b4d414e 100644 --- a/FAQ.md +++ b/FAQ.md @@ -4,16 +4,11 @@ This is a list of support questions that frequently show up in GitHub issues. Th If there is a support question that you frequently see being asked, please open a PR to add it to this list. -- [Why aren't my components updating when the location changes?](#why-arent-my-components-updating-when-the-location-changes) - [Why doesn't my application render after refreshing?](#why-doesnt-my-application-render-after-refreshing) - [Why doesn't my application work when loading nested routes?](#why-doesnt-my-application-work-when-loading-nested-routes) - [How do I access the `history` object outside of components?](#how-do-i-access-the-history-object-outside-of-components) - [How do I pass props to the component rendered by a ``?](#how-do-i-pass-props-to-the-component-rendered-by-a-route) -### Why aren't my components updating when the location changes? - -React Router relies on updates propagating from your router component to every child component. If you (or a component you use) implements `shouldComponentUpdate` or is a `React.PureComponent`, you may run into issues where your components do not update when the location changes. For a detailed review of the problem, please see the [blocked updates guide](packages/react-router/docs/guides/blocked-updates.md). - ### Why doesn't my application render after refreshing? If your application is hosted on a static file server, you need to use a `` instead of a ``. diff --git a/packages/react-router/docs/api/withRouter.md b/packages/react-router/docs/api/withRouter.md index bfa2bdf35a..0c6ff14499 100644 --- a/packages/react-router/docs/api/withRouter.md +++ b/packages/react-router/docs/api/withRouter.md @@ -29,27 +29,7 @@ const ShowTheLocationWithRouter = withRouter(ShowTheLocation); #### Important Note -`withRouter` does not subscribe to location changes like React Redux's `connect` does for state changes. Instead, re-renders after location changes propagate out from the `` component. This means that `withRouter` does _not_ re-render on route transitions unless its parent component re-renders. If you are using `withRouter` to prevent updates from being blocked by `shouldComponentUpdate`, it is important that `withRouter` wraps the component that implements `shouldComponentUpdate`. For example, when using Redux: - -```js -// This gets around shouldComponentUpdate -withRouter(connect(...)(MyComponent)) -// or -compose( - withRouter, - connect(...) -)(MyComponent) - -// This does not -connect(...)(withRouter(MyComponent)) -// nor -compose( - connect(...), - withRouter -)(MyComponent) -``` - -See [this guide](https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/blocked-updates.md) for more information. +`withRouter` does not subscribe to location changes like React Redux's `connect` does for state changes. Instead, re-renders after location changes propagate out from the `` component. This means that `withRouter` does _not_ re-render on route transitions unless its parent component re-renders. #### Static Methods and Properties diff --git a/packages/react-router/docs/guides/blocked-updates.md b/packages/react-router/docs/guides/blocked-updates.md deleted file mode 100644 index 0123e4f827..0000000000 --- a/packages/react-router/docs/guides/blocked-updates.md +++ /dev/null @@ -1,200 +0,0 @@ -# Dealing with Update Blocking - -React Router has a number of location-aware components that use the current `location` object to determine what they render. By default, the current `location` is passed implicitly to components using React's context model. When the location changes, those components should re-render using the new `location` object from the context. - -React provides two approaches to optimize the rendering performance of applications: the `shouldComponentUpdate` lifecycle method and the `PureComponent`. Both block the re-rendering of components unless the right conditions are met. Unfortunately, this means that React Router's location-aware components can become out of sync with the current location if their re-rendering was prevented. - -### Example of the Problem - -We start out with a component that prevents updates. - -```js -class UpdateBlocker extends React.PureComponent { - render() { - return ( -
- About - F.A.Q. -
- ); - } -} -``` - -When the `` is mounting, any location-aware child components will use the current `location` and `match` objects to render. - -```jsx -// location = { pathname: '/about' } - -//
-// About -// F.A.Q. -//
-``` - -When the location changes, the `` does not detect any prop or state changes, so its child components will not be re-rendered. - -```jsx -// location = { pathname: '/faq' } - -// the links will not re-render, so they retain their previous attributes -//
-// About -// F.A.Q. -//
-``` - -### `shouldComponentUpdate` - -In order for a component that implements `shouldComponentUpdate` to know that it _should_ update when the location changes, its `shouldComponentUpdate` method needs to be able to detect location changes. - -If you are implementing `shouldComponentUpdate` yourself, you _could_ compare the location from the current and next `context.router` objects. However, as a user, you should not have to use context directly. Instead, it would be ideal if you could compare the current and next `location` without touching the context. - -#### Third-Party Code - -You may run into issues with components not updating after a location change despite not calling `shouldComponentUpdate` yourself. This is most likely because `shouldComponentUpdate` is being called by third-party code, such as `react-redux`'s `connect` and `mobx-react`'s `observer`. - -```js -// react-redux -const MyConnectedComponent = connect(mapStateToProps)(MyComponent); - -// mobx-react -const MyObservedComponent = observer(MyComponent); -``` - -With third-party code, you likely cannot even control the implementation of `shouldComponentUpdate`. Instead, you will have to structure your code to make location changes obvious to those methods. - -Both `connect` and `observer` create components whose `shouldComponentUpdate` methods do a shallow comparison of their current `props` and their next `props`. Those components will only re-render when at least one prop has changed. This means that in order to ensure they update when the location changes, they will need to be given a prop that changes when the location changes. - -### `PureComponent` - -React's `PureComponent` does not implement `shouldComponentUpdate`, but it takes a similar approach to preventing updates. When a "pure" component updates, it will do a shallow comparison of its current `props` and `state` to the next `props` and `state`. If the comparison does not detect any differences, the component will not update. Like with `shouldComponentUpdate`, that means that in order to force a "pure" component to update when the location changes, it needs to have a prop or state that has changed. - -### The Solution - -#### Quick Solution - -If you are running into this issue while using a higher-order component like `connect` (from react-redux) or `observer` (from Mobx), you can just wrap that component in a `withRouter` to remove the blocked updates. - -```javascript -// redux before -const MyConnectedComponent = connect(mapStateToProps)(MyComponent); -// redux after -const MyConnectedComponent = withRouter(connect(mapStateToProps)(MyComponent)); - -// mobx before -const MyConnectedComponent = observer(MyComponent); -// mobx after -const MyConnectedComponent = withRouter(observer(MyComponent)); -``` - -**This is not the most efficient solution**, but will prevent the blocked updates issue. For more info regarding this solution, read the [Redux guide](https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/redux.md#blocked-updates). To understand why this is not the most optimal solution, read [this thread](https://github.com/ReactTraining/react-router/pull/5552#issuecomment-331502281). - -#### Recommended Solution - -The key to avoiding blocked re-renders after location changes is to pass the blocking component the `location` object as a prop. This will be different whenever the location changes, so comparisons will detect that the current and next location are different. - -```jsx -// location = { pathname: '/about' } - -//
-// About -// F.A.Q. -//
- -// location = { pathname: '/faq' } - -//
-// About -// F.A.Q. -//
-``` - -#### Getting the location - -In order to pass the current `location` object as a prop to a component, you must have access to it. The primary way that a component can get access to the `location` is via a `` component. When a `` matches (or always if you are using the `children` prop), it passes the current `location` to the child element it renders. - -```jsx - -const Here = (props) => { - // props.location = { pathname: '/here', ... } - return
You are here
-} - - { - // props.location = { pathname: '/there', ... } - return
You are there
-}}/> - - { - // props.location = { pathname: '/everywhere', ... } - return
You are everywhere
-}}/> -``` - -This means that given a component that blocks updates, you can easily pass it the `location` as a prop in the following ways: - -```jsx -// the Blocker is a "pure" component, so it will only -// update when it receives new props -class Blocker extends React.PureComponent { - render() { - return ( -
- Oz - Kansas -
- ); - } -} -``` - -1. A component rendered directly by a `` does not have to worry about blocked updates because it has the `location` injected as a prop. - -```jsx -// The 's location prop will change whenever -// the location changes - -``` - -2. A component rendered directly by a `` can pass that location prop to any child elements it creates. - -```jsx -; - -const Parent = props => { - // receives the location as a prop. Any child - // element it creates can be passed the location. - return ( - - - - ); -}; -``` - -What happens when the component isn't being rendered by a `` and the component rendering it does not have the `location` in its variable scope? There are two approaches that you can take to automatically inject the `location` as a prop of your component. - -1. Render a pathless ``. While ``s are typically used for matching a specific path, a pathless `` will always match, so it will always render its component. - -```jsx -// pathless = will always be rendered -const MyComponent = () => ( - - - -); -``` - -2. You can wrap a component with the `withRouter` higher-order component and it will be given the current `location` as one of its props. - -```jsx -// internally, withRouter just renders a pathless -const BlockAvoider = withRouter(Blocker); - -const MyComponent = () => ( - - - -); -``` diff --git a/website/modules/base.css b/website/modules/base.css index 2c9e802e8f..a1adc98c83 100644 --- a/website/modules/base.css +++ b/website/modules/base.css @@ -258,3 +258,40 @@ h3 { opacity: 1; transition: opacity 250ms ease-in; } + +/* Just some temporary CSS for our hooks tour ad */ + +.hooks-tour { + padding: 1.5em; + background-image: url(https://reacttraining.com/images/blue-fade.svg), + url(https://reacttraining.com/images/blue-fade.svg); + background-position: 50% 0, 0 -20%; + background-size: 100%; + background-repeat: no-repeat; + background-color: #edf4f7; + border: 1px solid #d3dbde; + text-align: center; + border-radius: 0.5em; +} + +.hooks-tour .logo { + width: 60%; +} + +.hooks-tour a { + display: inline-block; + margin: 0.2em 0; + padding: 0.2em 0.5em; + background-color: #e94948; + color: #fff; + border-radius: 0.2em; +} + +.hooks-tour hr { + border: none; + border-bottom: 1px solid #d3dbde; +} + +.hooks-tour p:last-of-type { + font-size: 0.7em; +} diff --git a/website/modules/components/Bundle.js b/website/modules/components/Bundle.js index ecc28b9608..8ffc880659 100644 --- a/website/modules/components/Bundle.js +++ b/website/modules/components/Bundle.js @@ -1,32 +1,18 @@ -import React, { Component } from "react"; +import { useState, useEffect } from "react"; -class Bundle extends Component { - state = { - mod: null - }; +function Bundle({ children, load }) { + const [mod, setMod] = useState(); - componentWillMount() { - this.load(this.props); - } + useEffect( + () => { + load(mod => { + setMod(mod.default ? mod.default : mod); + }); + }, + [load] + ); - componentWillReceiveProps(nextProps) { - if (nextProps.load !== this.props.load) { - this.load(nextProps); - } - } - - load(props) { - this.setState({ - mod: null - }); - props.load(mod => { - this.setState({ mod: mod.default ? mod.default : mod }); - }); - } - - render() { - return this.props.children(this.state.mod); - } + return children(mod); } export default Bundle; diff --git a/website/modules/components/Environment.js b/website/modules/components/Environment.js index 3bdc01607f..a4d4b30a9c 100644 --- a/website/modules/components/Environment.js +++ b/website/modules/components/Environment.js @@ -1,6 +1,5 @@ -import React, { Component } from "react"; +import React, { useEffect } from "react"; import { Redirect } from "react-router-dom"; -import { Block } from "jsxstyle"; import PropTypes from "prop-types"; import EnvironmentLarge from "./EnvironmentLarge"; @@ -15,63 +14,56 @@ const envData = { core: require("bundle-loader?lazy!../docs/Core") }; -class Environment extends Component { - static propTypes = { - location: PropTypes.object, - history: PropTypes.object, - match: PropTypes.shape({ - params: PropTypes.shape({ - environment: PropTypes.string - }) - }) - }; - - componentDidMount() { - this.preload(); +function Environment({ + history, + location, + match, + match: { + params: { environment } } - - preload() { +}) { + useEffect(() => { Object.keys(envData).forEach(key => envData[key](() => {})); - } + }, []); - render() { - const { - history, - location, - match, - match: { - params: { environment } - } - } = this.props; - if (!envData[environment]) { - return ; - } else { - return ( - - {isSmallScreen => ( - - {data => - data ? ( - isSmallScreen ? ( - - ) : ( - - ) + if (!envData[environment]) { + return ; + } else { + return ( + + {isSmallScreen => ( + + {data => + data ? ( + isSmallScreen ? ( + ) : ( - + ) - } - - )} - - ); - } + ) : ( + + ) + } + + )} + + ); } } +Environment.propTypes = { + location: PropTypes.object, + history: PropTypes.object, + match: PropTypes.shape({ + params: PropTypes.shape({ + environment: PropTypes.string + }) + }) +}; + export default Environment; diff --git a/website/modules/components/EnvironmentHeader.js b/website/modules/components/EnvironmentHeader.js index e418d5ccf6..eb11568d0d 100644 --- a/website/modules/components/EnvironmentHeader.js +++ b/website/modules/components/EnvironmentHeader.js @@ -2,8 +2,9 @@ import React from "react"; import { Link, Route } from "react-router-dom"; import { Block, Row, Inline, Col } from "jsxstyle"; import PropTypes from "prop-types"; +import Media from "react-media"; -import { LIGHT_GRAY, RED } from "../Theme"; +import { LIGHT_GRAY, RED, SMALL_SCREEN } from "../Theme"; import Logo from "./Logo"; function Tab({ to, ...rest }) { @@ -82,6 +83,24 @@ function Branding() { function EnvironmentHeader() { return ( + + +

+ + Attend a React Workshop + {" "} + in a city near you this Spring! +

+
+
diff --git a/website/modules/components/EnvironmentLarge.js b/website/modules/components/EnvironmentLarge.js index eb194bf5ba..b60ada2cae 100644 --- a/website/modules/components/EnvironmentLarge.js +++ b/website/modules/components/EnvironmentLarge.js @@ -1,4 +1,4 @@ -import React, { Component } from "react"; +import React, { Fragment, useEffect } from "react"; import PropTypes from "prop-types"; import { Block, InlineBlock } from "jsxstyle"; import { Link, Route, Redirect, Switch } from "react-router-dom"; @@ -8,38 +8,34 @@ import EnvironmentHeader from "./EnvironmentHeader"; import Example from "./Example"; import Guide from "./Guide"; import API from "./API"; +import HooksTourAd from "./HooksTourAd"; + +function EnvironmentLarge({ data, match }) { + useEffect( + () => { + data.examples.forEach(example => { + // native doesn't have `load` + if (example.load) example.load(() => {}); + // all have `loadSource` + if (example.loadSource) example.loadSource(() => {}); + }); + }, + [data] + ); -class EnvironmentLarge extends Component { - static propTypes = { - data: PropTypes.object, - match: PropTypes.object - }; - - componentDidMount() { - this.preloadExamples(); - } - - preloadExamples() { - const { data } = this.props; - data.examples.forEach(example => { - // native doesn't have `load` - if (example.load) example.load(() => {}); - // all have `loadSource` - if (example.loadSource) example.loadSource(() => {}); - }); - } - - render() { - const { data, match } = this.props; - return ( - -