From 5d2601000b6cb61996a425da7343d4bb01397712 Mon Sep 17 00:00:00 2001 From: Kimmo Puputti Date: Tue, 29 Jan 2019 13:29:30 +0200 Subject: [PATCH] Move Redux and routing docs to Flex Docs --- docs/README.md | 9 +- docs/redux.md | 129 +---------------------- docs/routing.md | 269 +----------------------------------------------- 3 files changed, 8 insertions(+), 399 deletions(-) diff --git a/docs/README.md b/docs/README.md index baa34304..bf59c540 100644 --- a/docs/README.md +++ b/docs/README.md @@ -156,6 +156,8 @@ Flex Docs: - [How to change FTW icons](https://www.sharetribe.com/docs/guides/how-to-change-ftw-icons/) - [FTW customization checklist](https://www.sharetribe.com/docs/guides/ftw-customization-checklist/) - [How to deploy FTW to production](https://www.sharetribe.com/docs/guides/how-to-deploy-ftw-to-production/) +- [How routing works in FTW](https://www.sharetribe.com/docs/background/ftw-routing/) +- [How the Redux setup works in FTW](https://www.sharetribe.com/docs/background/ftw-redux/) See also the following articles: @@ -169,11 +171,8 @@ See also the following articles: - [How to use CI with FTW](https://www.sharetribe.com/docs/guides/how-to-use-ci-with-ftw/) - [How to improve performance](https://www.sharetribe.com/docs/guides/how-to-improve-performance/) -Extra documentation for specific topics can be found in the following files: - -- [Routing](routing.md) -- [Redux and duck files](redux.md) -- [Original create-react-app documentation](https://github.com/sharetribe/create-react-app/blob/master/packages/react-scripts/template/README.md) +See also the +[original create-react-app documentation](https://github.com/sharetribe/create-react-app/blob/master/packages/react-scripts/template/README.md). The application was bootstrapped with a forked version of [create-react-app](https://github.com/facebookincubator/create-react-app). While most of the diff --git a/docs/redux.md b/docs/redux.md index d1239300..0b180e85 100644 --- a/docs/redux.md +++ b/docs/redux.md @@ -1,130 +1,5 @@ # Redux configuration -## What is Redux +Documentation moved to the Flex Docs site: -Flex Template for Web (FTW) is JavaScript single-page application (SPA). This means that the app -needs to be able to render several different layouts (pages) depending on user interaction. State -management is essential for this process. FTW needs to know if a user has been authenticated, if it -has received relevant data for the current page, and so on. - -We use [Redux](https://redux.js.org/introduction) for state management on the application level. You -should read more about Redux before you start modifying queries to Flex API or creating new Page -level elements (unless you are modifying [a static page](static-pages.md)). - -In the following subtopics, we assume that you know the -[basics of Redux](https://redux.js.org/basics) already. - -- [Containers: Pages + TopbarContainer](#containers-pages--topbarcontainer) -- [Duck files](#duck-files) -- [Setting up Redux](#setting-up-redux) -- [Advanced Redux concepts: thunks](#advanced-redux-concepts-thunks) - -## Containers: Pages + TopbarContainer - -We have set up FTW so that pages are aware of Redux state store, but other components and forms are -purely -[presentational components](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0). -This makes it easier to customize UI components - you don't need to be aware of the complexity -related to Redux setup when you just want to touch the behavior of some visual component. In those -cases, you can just head to `scr/components/` or `src/forms/` directory and you can see from there -what props are available for each component when they are rendered. - -Naturally, there is a need for higher level components which fetch new data and define what props -are passed down to presentational components. In Redux terminology, those components are called -_Containers_. FTW has defined all the containers inside a directory called `src/containers/`. It -contains all the pages and a special container for top bar (TopbarContainer) since that is a very -complex component and it is shared with almost every page. You can read more about differences -between presentational and container components from an -[article written by Dan Abramov](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) - -The actual container setup of a page level component can be found from the bottom of the component -file. For example, `src/containers/TransactionPage/TransactionPage.js` connects itself to Redux -store with `mapStateToProps` and `mapDispatchToProps` functions: - -```js -const TransactionPage = compose( - connect( - mapStateToProps, - mapDispatchToProps - ), - injectIntl -)(TransactionPageComponent); -``` - -## Duck files - -Inside `src/containers/` directory, we have also a special `.duck.js` -file. These files wrap all the other page-specific Redux concepts into a single file. Instead of -writing action types, action creators and reducers in separate files (and spread them around our -directory structure), FTW tries to keep pages encapsulated. Page specific actions, store updates, -and data fetches happen inside their respective directory - just look for a file which name follows -a pattern: `.duck.js`. This pattern is just a simple concept of adding related things -into a single file. Read more from -[author's repository](https://github.com/erikras/ducks-modular-redux). - -### Global reducers - -Some reducers are needed in several pages. These global reducers we have defined inside `src/ducks/` -directory with their respective `*.duck.js` files. Most important global duck files are -`user.duck.js` and `marketplaceData.duck.js`. - -## Setting up Redux - -Container specific reducers are gathered and exported inside `src/containers/reducers.js` file and -global reducers are exported respectively in a file `src/ducks/index.js`. - -With those exports, we are able to create appReducer (`src/reducers.js`): - -```js -import { combineReducers } from 'redux'; -import * as globalReducers from './ducks'; -import * as pageReducers from './containers/reducers'; - -const appReducer = combineReducers({ ...globalReducers, ...pageReducers }); -``` - -`appReducer` is called by `createReducer` function, which is called inside of `configureStore` -function (in `src/store.js`). - -This setup creates a store structure that separates container specific state as well as global data -by their reducer names. Together with Ducks module naming schema, this means that: - -- the state of the `ListingPage` can be found from `state.ListingPage` and -- the state of the global `user` object can be found from `state.user`. - -## Advanced Redux concepts: thunks - -One essential part of state management in FTW, is filling the Redux store with data fetched from the -Flex API. This is done with -[Redux Thunks](https://redux.js.org/advanced/asyncactions#async-action-creators), which is a Redux -middleware to create asynchronous action creators. - -As with every other Redux store actions, we have defined Thunks inside `*.duck.js` file. For -example, fetching listing reviews can be done with a following thunk function: - -```js -export const fetchReviews = listingId => (dispatch, getState, sdk) => { - // Make store aware of a request to fetch reviews - dispatch(fetchReviewsRequest); - // Fetch reviews using Flex SDK - return sdk.reviews - .query({ - listingId, - state: 'public', - include: ['author', 'author.profileImage'], - 'fields.image': ['variants.square-small'], - }) - .then(response => { - const reviews = denormalisedResponseEntities(response); - // If fetch succeeds, make store aware of fetched data - dispatch(fetchReviewsSuccess(reviews)); - }) - .catch(e => { - // If fetch throws an error, save the error to the store (so that UI can react to it) - dispatch(fetchReviewsError(storableError(e))); - }); -}; -``` - -> Note: `sdk` parameter is also provided by Redux Thunk. We pass it to middleware in store.js: -> `thunk.withExtraArgument(sdk)` +https://www.sharetribe.com/docs/background/ftw-redux/ diff --git a/docs/routing.md b/docs/routing.md index a00fd32a..c1c74298 100644 --- a/docs/routing.md +++ b/docs/routing.md @@ -1,270 +1,5 @@ # Routing -Flex Template for Web (FTW) uses [React Router](https://reacttraining.com/react-router/web) for -creating routes to different pages. React Router is a collection of navigational components that -allow single page apps to create routing as a part of normal rendering flow of the React app. So, -instead of defining on server-side what gets rendered when user goes to URL -`somemarketplace.com/about`, we just catch all the path combinations and let the app to define what -page gets rendered. +Documentation moved to the Flex Docs site: -- [React Router setup](#react-router-setup) -- [Linking](#linking) -- [Loading data](#loading-data) -- [Analytics](#analytics) -- [A brief introduction to server-side rendering](#a-brief-introduction-to-ssr) - -## React Router setup - -### Route configuration - -FTW has a quite straightforward routing setup - there's just one file you need to check before you -link to existing routes or start creating new routes to static pages: `src/routeConfiguration.js`. - -There we have imported and configured all the page-level components that are currently used within -FTW: - -```js -import { - AboutPage, - AuthenticationPage, - //... -} from './containers'; - -// Our routes are exact by default. -// See behaviour from Routes.js where Route is created. -const routeConfiguration = () => { - return [ - { - path: '/about', - name: 'AboutPage', - component: AboutPage, - }, - { - path: '/login', - name: 'LoginPage', - component: props => , - }, - { - path: '/signup', - name: 'SignupPage', - component: props => , - }, - //... - ]; -}; - -export default routeConfiguration; -``` - -In the example, path `/login` renders `AuthenticationPage` component with prop 'tab' set to 'login'. -In addition, this route configuration has a name 'LoginPage'. - -> Routes use exact path matches in FTW. We felt that this makes it easier to understand the -> connection between a path and its routed view aka related page component. -> [Read more.](https://reacttraining.com/react-router/web/api/Route/exact-bool) - -There are a couple of extra configurations you can set. For example `/listings` path leads to a page -that lists all the listings provided by the current user: - -```js - { - path: '/listings', - name: 'ManageListingsPage', - auth: true, - authPage: 'LoginPage', // default is 'SingupPage' - component: props => , - loadData: ManageListingsPage.loadData, - }, -``` - -Here we have set this route to be available only for authenticated user (`auth: true`), because we -need to know whose listings we should fetch. If a user is unauthenticated, he/she is redirected to -LoginPage (`authPage: 'LoginPage'`) before he/she can see the content of `ManageListingsPage` page. - -There's also a `loadData` function defined. It is a special function that gets called if a page -needs to fetch more data (e.g. from Flex API) after redirecting to that route. We'll open up this -concept [later in this document](#loading-data). - -In addition to these configurations, there's also a rarely used `setInitialValues` function that -could be defined and passed to a route: - -```js - { - path: '/l/:slug/:id/checkout', - name: 'CheckoutPage', - auth: true, - component: props => , - setInitialValues: CheckoutPage.setInitialValues, - }, -``` - -This function gets called when some page wants to pass forward some extra data before redirecting -user to that page. For example we could ask booking dates on ListingPage and initialize CheckoutPage -state with that data before buyer is redirected to CheckoutPage. - -### How FTW renders a router with routeConfiguration.js - -Aforementioned route configuration is used in `src/app.js`. For example, `ClientApp` defines -`BrowserRouter` and gives it a child component (`Routes`) that gets the configuration as `routes` -property. - -Simplified `app.js` code that renders client-side FTW app: - -```js -import { BrowserRouter } from 'react-router-dom'; -import Routes from './Routes'; -import routeConfiguration from './routeConfiguration'; -//... -export const ClientApp = props => { - return ( - - - - ); -}; -``` - -`src/Routes.js` renders the `Route` navigational components (`Switch` renders the first `Route` that -matches the location): - -```js -import { Switch, Route } from 'react-router-dom'; -//... - -const Routes = (props, context) => { - //... - return ( - - {routes.map(toRouteComponent)} - - - ); -``` - -Inside `src/Routes.js`, we also have a component called `RouteComponentRenderer`, which has three -important jobs: - -- Calling loadData function, if those have been defined in `src/routeConfiguration.js`. This is an - asynchronous call, a page needs to define what gets rendered before data is complete. -- Reset scroll position after location change. -- Dispatch location changed actions to Redux store. This makes it possible for analytics Redux - middleware to listen location changes. For more information, see the - [How to set up Analytics for FTW](https://www.sharetribe.com/docs/guides/how-to-set-up-analytics-for-ftw/) - guide in Flex Docs. - -## Linking - -Linking is a special case in SPA. Using HTML `` tags will cause browser to redirect to given -`href` location. That will cause all the resources to be fetched again, which is a slow and -unnecessary step for SPA. Instead, we just need to tell our router to render a different page by -adding or modifying browser's history entries. - -### NamedLink and NamedRedirect - -React Router exports a couple of -[navigational components](https://reacttraining.com/react-router/web/api/Link) (e.g. -`About`) that could be used for linking to different internal paths. Since -FTW is a template app, we want all the paths to be customizable too. That means that we can't use -paths directly when redirecting user to another Route. For example marketplace for German customer -might want to customize the LoginPage path to be `/anmelden` instead of `/login` - and that would -mean that all the _Links_ to it would need to be updated. - -This is the reason why we have created names to different routes in `src/routeConfiguration.js`. We -have a component called `` and its _name_ property creates a link to -the correct Route even if the path is changed in routeConfiguration. Needless to say that those -names should only be used for internal route mapping. - -More complex example of `NamedLink` - -```js -// Link to LoginPage: -log in -// Link to ListingPage with path `l///`: -', slug: '' }}>some listing -// Link to SearchPage with query parameter: bounds -Turku city -``` - -`NamedLink` is widely used in FTW, but there are some cases when we have made redirection to another -page if some data is missing (e.g. CheckoutPage redirects to ListingPage, if some data is missing or -it is old). This can be done with rendering component called `NamedRedirect`, which is a similar -wrapper for [Redirect component](https://reacttraining.com/react-router/web/api/Redirect). - -### ExternalLink - -There's also a component for external links. The reason why it exists is that there's a -[security issue](https://mathiasbynens.github.io/rel-noopener/) that can be exploited when a site is -linking to external resources. `ExternalLink` component has some safety measures to prevent those. -We recommend that all the external links are created using `ExternalLink`component instead of -directly writing anchors like `External site`. (You can just change -the JSX element accordinly: `External site`.) - -## Loading data - -If a page component needs to fetch data, it can be done as a part of navigation. A page component -needs to define a static function called `loadData`, which needs to return a Promise, which is -resolved when all the asynchronous Redux Thunk calls are completed. - -For example here's a bit simplified version of `ListingPage.loadData` function: - -```js -export const loadData = (params, search) => dispatch => { - const listingId = new UUID(params.id); - - return Promise.all([ - dispatch(showListing(listingId)), // fetch listing data - dispatch(fetchTimeSlots(listingId)), // fetch timeslots for booking calendar - dispatch(fetchReviews(listingId)), // fetch reviews related to this listing - ]); -}; -``` - -> Unfortunately, `loadData` function needs to be separately mapped in routeConfiguration.js atm. -> There has been a problem with module initialization order and functional components have been used -> in routeConfiguration.js as wrappers to prevent a premature call to these static functions. - -## Analytics - -It is possible to track page views to gather information about navigation behaviour. Tracking is -tied to routing through `src/Routes.js` where `RouteRendererComponent` dispatches `LOCATION_CHANGED` -actions. These actions are handled by a global reducer (`src/ducks/Routing.duck.js`), but more -importantly, `src/analytics/analytics.js` (a Redux middleware) listens to these changes and sends -tracking events to configured services. For more information, see the -[How to set up Analytics for FTW](https://www.sharetribe.com/docs/guides/how-to-set-up-analytics-for-ftw/) -guide in Flex Docs. - -## A brief introduction to SSR - -Server-side rendering needs a better documentation at some point, but this routing setup is the key -to render any page on server-side without duplicating routing logic. We just need to fetch data if -`loadData` is defined on page component and then use `ReactDOMServer.renderToString` to render the -app to string (requested URL is a parameter for this render function). - -So, instead of having something like this on Express server: - -```js -app.get('/about', handleAbout); -``` - -We basically catch every path call using `*` on `server/index.js`: - -```js -app.get('*', (req, res) => { -``` - -and then we ask our React app to - -1. load data based on current URL (and return this preloaded state from Redux store) -2. render the correct page with this preloaded state (renderer also attaches preloadedState to - HTML-string to hydrate the app on the client-side) -3. send rendered HTML string as a response to the client browser - -```js - dataLoader - .loadData(req.url, sdk) - .then(preloadedState => { - const html = renderer.render(req.url, context, preloadedState); - //... - res.send(html); - } -``` +https://www.sharetribe.com/docs/background/ftw-routing/