From 04dd3b62ce77bb0ee6950b8ccd123fb00ea3859f Mon Sep 17 00:00:00 2001 From: Dominic Saadi Date: Fri, 5 May 2023 20:29:31 -0700 Subject: [PATCH] chore(docs): reversion docs for cell, type fixes (#8236) --- docs/versioned_docs/version-5.0/cells.md | 132 ++---- .../build-dashboards-fast-with-tremor.md | 413 ++++++++++++++++++ .../version-5.0/typescript/utility-types.md | 3 +- 3 files changed, 457 insertions(+), 91 deletions(-) create mode 100644 docs/versioned_docs/version-5.0/how-to/build-dashboards-fast-with-tremor.md diff --git a/docs/versioned_docs/version-5.0/cells.md b/docs/versioned_docs/version-5.0/cells.md index 5789179348f2..20c92ace2f37 100644 --- a/docs/versioned_docs/version-5.0/cells.md +++ b/docs/versioned_docs/version-5.0/cells.md @@ -21,69 +21,56 @@ yarn rw generate cell This creates a directory named `Cell` in `web/src/components` with four files: -```bash -~/redwood-app$ yarn rw generate cell user -yarn run v1.22.4 -$ /redwood-app/node_modules/.bin/rw g cell user - ✔ Generating cell files... - ✔ Writing `./web/src/components/UserCell/UserCell.mock.js`... - ✔ Writing `./web/src/components/UserCell/UserCell.stories.js`... - ✔ Writing `./web/src/components/UserCell/UserCell.test.js`... - ✔ Writing `./web/src/components/UserCell/UserCell.js`... -Done in 1.07s. -``` +| File | Description | +| :---------------------- | :------------------------------------------------------ | +| `Cell.js` | The actual Cell | +| `Cell.test.js` | Jest tests for each state of the Cell | +| `Cell.stories.js` | Storybook stories for each state of the Cell | +| `Cell.mock.js` | Mock data for both the Jest tests and Storybook stories | ### Single Item Cell vs List Cell -Sometimes you want a Cell that renders a single item, like the example above, and other times you want a Cell that renders a list. +Sometimes you want a Cell that renders a single item and other times you want a Cell that renders a list. Redwood's Cell generator can do both. First, it detects if `` is singular or plural. For example, to generate a Cell that renders a list of users, run `yarn rw generate cell users`. -Second, for **irregular words** whose singular and plural are identical, such as *equipment* or *pokemon*, you can specify the `--list` flag to tell Redwood to generate a list Cell explicitly: +Second, for irregular words whose singular and plural are the same, such as "equipment" or "pokemon", you can pass `--list` to tell Redwood to generate a list Cell explicitly: -``` +```bash yarn rw generate cell equipment --list ``` ## Cells in-depth -We'll go over each of these files in detail. But know that the file appended with just `.js` (in the example above, `UserCell.js`) contains all your Cell's logic. - -Off the bat, this file exports five constants: `QUERY`, `Loading` , `Empty` , `Failure` and `Success`. The root query in `QUERY` is the same as `` so that, if you're generating a cell based on a model in your `schema.prisma`, you can get something out of the database right away. But there's a good chance you won't generate your Cell this way, so if you need to, make sure to change the root query. See the [Cells](tutorial/chapter2/cells.md#our-first-cell) section of the Tutorial for a great example of this. +Cells exports five constants: `QUERY`, `Loading` , `Empty` , `Failure` and `Success`. The root query in `QUERY` is the same as `` so that, if you're generating a cell based on a model in your `schema.prisma`, you can get something out of the database right away. But there's a good chance you won't generate your Cell this way, so if you need to, make sure to change the root query. See the [Cells](tutorial/chapter2/cells.md#our-first-cell) section of the Tutorial for a great example of this. ## Usage With Cells, you have a total of seven exports to work with: -| Name | Type | Description | -| :------------ | :----------------- | :----------------------------------------------------------- | -| `QUERY` | `string\|function` | The query to execute | -| `beforeQuery` | `function` | Lifecycle hook; prepares variables and options for the query | -| `isEmpty` | `function` | Lifecycle hook; decides if Cell should render Empty | -| `afterQuery` | `function` | Lifecycle hook; sanitizes data returned from the query | -| `Loading` | `component` | If the request is in flight, render this component | -| `Empty` | `component` | If there's no data (`null` or `[]`), render this component | -| `Failure` | `component` | If something went wrong, render this component | -| `Success` | `component` | If the data has loaded, render this component | +| Name | Type | Description | +| :------------ | :---------------- | :----------------------------------------------------------- | +| `QUERY` | `string,function` | The query to execute | +| `beforeQuery` | `function` | Lifecycle hook; prepares variables and options for the query | +| `isEmpty` | `function` | Lifecycle hook; decides if the Cell should render Empty | +| `afterQuery` | `function` | Lifecycle hook; sanitizes data returned from the query | +| `Loading` | `component` | If the request is in flight, render this component | +| `Empty` | `component` | If there's no data (`null` or `[]`), render this component | +| `Failure` | `component` | If something went wrong, render this component | +| `Success` | `component` | If the data has loaded, render this component | Only `QUERY` and `Success` are required. If you don't export `Empty`, empty results are sent to `Success`, and if you don't export `Failure`, error is output to the console. -In addition to displaying the right component, Cells also make sure to funnel the right props to the right component. `Loading`, `Empty`, `Failure`, and `Success` all have access to the props passed down from the Cell in good ol' React fashion, and most of `useQuery`'s return (more on that below). In addition to all those props, `Empty` and `Success` also get the `data` returned from the query and an `updating` boolean prop saying whether the Cell is currently fetching new data or not. `Failure` also gets `updating` and exclusive access to `error` and `errorCode`. - -With this many props coming in, there's a risk of name clashing. A couple things to look out for are: - -- Your Cell has a prop with the same name as root-level data returned by your query. - - In this case, the root-level data overrides your prop. But since props double as query variables, you can destructure the `variables` prop that `useQuery` returns to retrieve it. Or you can just rename the prop on the Cell! +In addition to displaying the right component at the right time, Cells also funnel the right props to the right component. `Loading`, `Empty`, `Failure`, and `Success` all have access to the props passed down from the Cell in good ol' React fashion, and most of the `useQuery` hook's return as a prop called `queryResult`. In addition to all those props, `Empty` and `Success` also get the `data` returned from the query and an `updating` prop indicating whether the Cell is currently fetching new data. `Failure` also gets `updating` and exclusive access to `error` and `errorCode`. -- Your Cell has props or query results with the same name as any of `useQuery`'s returns. - - In this case, `useQuery`'s returns overwrite the props and results. - -We mentioned above that Cells receive "most" of what's returned from `useQuery`. You can see exactly what `useQuery` returns in Apollo Client's [API reference](https://www.apollographql.com/docs/react/api/react/hooks/#result). Note that, as we just mentioned, `error` and `data` get some special treatment. +We mentioned above that Cells receive "most" of what's returned from the `useQuery` hook. You can see exactly what `useQuery` returns in Apollo Client's [API reference](https://www.apollographql.com/docs/react/api/react/hooks/#result). Again note that `error` and `data` get some special treatment. ### QUERY -`QUERY` can be a string or a function. Note that it's totally more than ok to have more than one root query. Here's an example of that: +`QUERY` can be a string or a function. If `QUERY` is a function, it has to return a valid GraphQL document. + +It's more-than ok to have more than one root query. Here's an example: ```jsx {7-10} export const QUERY = gql`{ @@ -104,15 +91,11 @@ So in this case, both `posts` and `authors` would be available to `Success`: ```jsx export const Success = ({ posts, authors }) => { - // render logic with posts and authors + // ... } ``` -If `QUERY` is a function, it has to return a valid GraphQL document. -Use a function if your queries need to be more dynamic: - - -But what about variables? Well, Cells are setup to use any props they receive from their parent as variables (things are setup this way in `beforeQuery`). For example, here `BlogPostCell` takes a prop, `numberToShow`, so `numberToShow` is just available to your `QUERY`: +Normally queries have variables. Cells are setup to use any props they receive from their parent as variables (things are setup this way in `beforeQuery`). For example, here `BlogPostCell` takes a prop, `numberToShow`, so `numberToShow` is just available to your `QUERY`: ```jsx {7} import BlogPostsCell from 'src/components/BlogPostsCell' @@ -144,9 +127,9 @@ This means you can think backwards about your Cell's props from your SDL: whatev ### beforeQuery -`beforeQuery` is a lifecycle hook. The best way to think about it is as an API for configuring Apollo Client's `Query` component (so you might want to check out Apollo's [docs](https://www.apollographql.com/docs/react/api/react-components/#query) for it). +`beforeQuery` is a lifecycle hook. The best way to think about it is as a chance to configure [Apollo Client's `useQuery` hook](https://www.apollographql.com/docs/react/api/react/hooks#options). -By default, `beforeQuery` gives any props passed from the parent component to `Query` so that they're available as variables for `QUERY`. It'll also set the fetch policy to `'cache-and-network'` since we felt it matched the behavior users want most of the time. +By default, `beforeQuery` gives any props passed from the parent component to `QUERY` so that they're available as variables for it. It'll also set the fetch policy to `'cache-and-network'` since we felt it matched the behavior users want most of the time: ```jsx export const beforeQuery = (props) => { @@ -159,7 +142,6 @@ export const beforeQuery = (props) => { For example, if you wanted to turn on Apollo's polling option, and prevent caching, you could export something like this (see Apollo's docs on [polling](https://www.apollographql.com/docs/react/data/queries/#polling) and [caching](https://www.apollographql.com/docs/react/data/queries/#setting-a-fetch-policy)) - ```jsx export const beforeQuery = (props) => { return { variables: props, fetchPolicy: 'no-cache', pollInterval: 2500 } @@ -190,13 +172,14 @@ export const beforeQuery = ({ word }: { word: string }) => { ### isEmpty -`isEmpty` is an optional lifecycle hook. It returns a boolean to indicate if Cell is empty. Use it to override the [default check](#empty). +`isEmpty` is an optional lifecycle hook. It returns a boolean to indicate if the Cell should render empty. Use it to override the default check, which checks if the Cell's root fields are null or empty arrays. -It receives the `data`, and the default check reference `isDataEmpty`, so it's possible to extend the default check with custom logic. +It receives two parameters: 1) the `data`, and 2) an object that has the default `isEmpty` function, named `isDataEmpty`, so that you can extend the default: ```jsx -export const isEmpty = (data, { isDataEmpty }) => - isDataEmpty(data) || data?.blog?.status === 'hidden' +export const isEmpty = (data, { isDataEmpty }) => { + return isDataEmpty(data) || data?.blog?.status === 'hidden' +} ``` ### afterQuery @@ -206,16 +189,10 @@ Use it to sanitize data returned from `QUERY` before it gets there. By default, `afterQuery` just returns the data as it is: -```jsx -export const afterQuery = (data) => ({...data}) -``` - ### Loading If there's no cached data and the request is in flight, a Cell renders `Loading`. - - When you're developing locally, you can catch your Cell waiting to hear back for a moment if set your speed in the Inspector's **Network** tab to something like "Slow 3G". But why bother with Slow 3G when Redwood comes with Storybook? Storybook makes developing components like `Loading` (and `Failure`) a breeze. We don't have to put up with hacky workarounds like Slow 3G or intentionally breaking our app just to develop our components. @@ -223,31 +200,11 @@ But why bother with Slow 3G when Redwood comes with Storybook? Storybook makes d ### Empty A Cell renders this component if there's no data. - -What do we mean by no data? We mean if the response is 1) `null` or 2) an empty array (`[]`). There's actually four functions in [createCell.tsx](https://github.com/redwoodjs/redwood/blob/main/packages/web/src/components/createCell.tsx) dedicated just to figuring this out: - -```jsx title="createCell.tsx" -const isDataNull = (data: DataObject) => { - return dataField(data) === null -} - -const isDataEmptyArray = (data: DataObject) => { - const field = dataField(data) - return Array.isArray(field) && field.length === 0 -} - -const dataField = (data: DataObject) => { - return data[Object.keys(data)[0]] -} - -const isEmpty = (data: DataObject) => { - return isDataNull(data) || isDataEmptyArray(data) -} -``` +By no data, we mean if the response is 1) `null` or 2) an empty array (`[]`). ### Failure -A Cell renders this component if something went wrong. You can quickly see this in action (it's easy to break things) if you add a nonsense field to your `QUERY`: +A Cell renders this component if something went wrong. You can quickly see this in action if you add an untyped field to your `QUERY`: ```jsx {6} const QUERY = gql` @@ -255,7 +212,7 @@ const QUERY = gql` posts { id title - nonsense + unTypedField } } ` @@ -283,7 +240,7 @@ export const Failure = ({ error, errorCode }: CellFailureProps) => { If everything went well, a Cell renders `Success`. -As mentioned, Success gets exclusive access to the `data` prop. But if you try to destructure it from props, you'll notice that it doesn't exist. This is because Redwood adds another layer of convenience: in [createCell.tsx](https://github.com/redwoodjs/redwood/blob/main/packages/web/src/components/createCell.tsx#L149), Redwood spreads `data` (using the spread operator, `...`) into `Success` so that you can just destructure whatever data you were expecting from your `QUERY` directly. +As mentioned, Success gets exclusive access to the `data` prop. But if you try to destructure it from `props`, you'll notice that it doesn't exist. This is because Redwood adds a layer of convenience: Redwood spreads `data` into `Success` so that you can just destructure whatever data you were expecting from your `QUERY` directly. So, if you're querying for `posts` and `authors`, instead of doing: @@ -291,26 +248,23 @@ So, if you're querying for `posts` and `authors`, instead of doing: export const Success = ({ data }) => { const { posts, authors } = data - // render logic with posts and authors - ... + // ... } ``` -Redwood does: +Redwood lets you do: ```jsx export const Success = ({ posts, authors }) => { - // render logic with posts and authors - ... + // ... } ``` -Note that you can still pass any other props to `Success`. After all, it's still just a React component. - +Note that you can still pass any other props to `Success`. After all, it's just a React component. :::tip -Looking for info on how TypeScript works with Cells? Check out the [Utility Types](typescript/utility-types.md#cell) doc +Looking for info on how TypeScript works with Cells? Check out the [Utility Types](typescript/utility-types.md#cells) doc. ::: diff --git a/docs/versioned_docs/version-5.0/how-to/build-dashboards-fast-with-tremor.md b/docs/versioned_docs/version-5.0/how-to/build-dashboards-fast-with-tremor.md new file mode 100644 index 000000000000..c772a6a32723 --- /dev/null +++ b/docs/versioned_docs/version-5.0/how-to/build-dashboards-fast-with-tremor.md @@ -0,0 +1,413 @@ +--- + +description: "Learn how to build dashboards fast using the termor React library of data visualization components." +--- + +# Build Dashboards Fast with Tremor + +[Tremor](https://www.tremor.so) is a React library to build dashboards fast. Its modular components are fully open-source, made by data scientists and software engineers with a sweet spot for design. + +In this how to, you'll learn how to + +* setup tremor in a new or existing RedwoodJS app +* use tremor components to layout a new dashboard +* use a chart and card component to visualize static data +* access a GitHub repo to make your dashboard dynamic using an [example RedwoodJS app](https://github.com/redwoodjs/redwoodjs-tremor-dashboard-demo) + +## Live Demo + +See what's possible with a [dynamic dashboard live demo](https://tremor-redwood-dashboard-demo.netlify.app) build with RedwoodJS and Tremor. + +Cool, right? + +Let's get started! + +## Create a New RedwoodJS Project + + +In our terminal, we create a new RedwoodJS project: + +```bash +yarn create redwood-app my-project --ts +``` + +> **Note:** If you already have a RedwoodJS project, you can skip this step and continue with the next section. + +If you do not want a TypeScript project, omit the `--ts` flag. + +> **Important:** RedwoodJS prefers yarn over npm because a project is monorepo with api and web workspaces. You will install tremor and other web packages using yarn workspaces. + + +Use the Redwood setup command to install `TailwindCSS`, its peer dependencies, and create the `tailwind.config.js` file. + + +```bash +yarn rw setup ui tailwindcss +``` + +Install `tremor` in the web workspace from your command line via yarn. + +```bash +yarn workspace web add @tremor/react +``` + +Install `heroicons version 1.0.6` from your command line via yarn. + +```bash +yarn workspace web add @heroicons/react@1.0.6 +``` + +Update tailwind config `web/config/tailwind.config.js` **including the path to the tremor** module. + +```js +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + 'src/**/*.{js,jsx,ts,tsx}', + '../node_modules/@tremor/**/*.{js,ts,jsx,tsx}', + ], + theme: { + extend: {}, + }, + plugins: [], +} +``` + +> **Note:** the path for node_modules is `../` because the web workspace is in a subdirectory of the root directory. + +## Add a Dashboard Page + +Generate a page from your command line. + +```bash +yarn rw g page dashboard / +``` + +You will now have a new page at `web/src/pages/DashboardPage/DashboardPage.tsx` and `web/src/Routes.tsx` will have a new route added at: + +```tsx filename="web/src/Routes.tsx" +// web/src/Routes.tsx` + + +``` + + +Add simple area chart to the `DashboardPage`: + +```jsx +import { Grid, Col, Card, Title, AreaChart } from '@tremor/react' + +import { MetaTags } from '@redwoodjs/web' + +const DashboardPage = () => { + const chartdata = [ + { + date: 'Jan 22', + SemiAnalysis: 2890, + 'The Pragmatic Engineer': 2338, + }, + { + date: 'Feb 22', + SemiAnalysis: 2756, + 'The Pragmatic Engineer': 2103, + }, + { + date: 'Mar 22', + SemiAnalysis: 3322, + 'The Pragmatic Engineer': 2194, + }, + { + date: 'Apr 22', + SemiAnalysis: 3470, + 'The Pragmatic Engineer': 2108, + }, + { + date: 'May 22', + SemiAnalysis: 3475, + 'The Pragmatic Engineer': 1812, + }, + { + date: 'Jun 22', + SemiAnalysis: 3129, + 'The Pragmatic Engineer': 1726, + }, + ] + + const dataFormatter = (number: number) => { + return '$ ' + Intl.NumberFormat('us').format(number).toString() + } + + return ( +
+ + +

Dashboard

+ + + + + Newsletter revenue over time (USD) + + + + +
+ ) +} + +export default DashboardPage +``` + +Start your RedwoodJS development server + +```bash +yarn rw dev +``` + +Your app will start up and you should see the Dashboard page with an area with two `Newsletter revenue over time (USD)` data series. + +## Add a new component for a KPI Card + +Generate a component for a KPI (Key Performance Indicator) from your command line. + +```bash +yarn rw g component KpiCard +``` + +You will now have a new React component at `/web/src/components/KpiCard/KpiCard.tsx`. + +Update the `KpiCard` component to import the `Card` component and assemble a card using its default +styling. + +To create our first KPI, we import the `Metric` and `Text` component and place them within the card component. We use [Tailwind CSS'](https://tailwindcss.com/docs/utility-first) utilities in the **className** property to reduce the card's width and to center it horizontally. + +To make our KPI card more insightful, we add a `ProgressBar`, providing +contextual details about our metric. To align both text elements, we also import +the `Flex` component. + +```tsx filename="/web/src/components/KpiCard/KpiCard.tsx" +// /web/src/components/KpiCard/KpiCard.tsx + +import { + BadgeDelta, + DeltaType, + Card, + Flex, + Metric, + ProgressBar, + Text, +} from '@tremor/react' + +export type Kpi = { + title: string + metric: string + progress: number + metricTarget: string + delta: string + deltaType: DeltaType +} + +interface Props { + kpi: Kpi +} + +const KpiCard = ({ kpi }: Props) => { + return ( + + +
+ {kpi.title} + {kpi.metric} +
+ {kpi.delta} +
+ + {`${kpi.progress}% (${kpi.metric})`} + {kpi.metricTarget} + + +
+ ) +} + +export default KpiCard +``` + +## Add the KPI Card component to your Dashboard + +Import the `KpiCard` component and `Kpi` type. + +```tsx +import KpiCard from 'src/components/KpiCard/KpiCard' // 👈 Import the KpiCard component +import type { Kpi } from 'src/components/KpiCard/KpiCard' // 👈 Import the Kpi type +``` + +Next, create the `kpi` data collection with sample data + +```tsx + const kpis: Kpi[] = [ // 👈 Create some sample KPI data + { + title: 'Sales', + metric: '$ 12,699', + progress: 15.9, + metricTarget: '$ 80,000', + delta: '13.2%', + deltaType: 'moderateIncrease', + }, + { + title: 'Profit', + metric: '$ 45,564', + progress: 36.5, + metricTarget: '$ 125,000', + delta: '23.9%', + deltaType: 'increase', + }, + { + title: 'Customers', + metric: '1,072', + progress: 53.6, + metricTarget: '2,000', + delta: '10.1%', + deltaType: 'moderateDecrease', + }, + ] +``` + +Then iterate over the collection to add a `KpiCard` inside new `Col` for each KPI data item: + +```tsx + {kpis.map((kpi, i) => ( + + + + ))} +``` + +Your Dashboard page should now look like: + +```tsx +import { Grid, Col, Card, Title, AreaChart } from '@tremor/react' + +import { MetaTags } from '@redwoodjs/web' + +import KpiCard from 'src/components/KpiCard/KpiCard' // 👈 Import the KpiCard component +import type { Kpi } from 'src/components/KpiCard/KpiCard' // 👈 Import the Kpi type + +const DashboardPage = () => { + const chartdata = [ + { + date: 'Jan 22', + SemiAnalysis: 2890, + 'The Pragmatic Engineer': 2338, + }, + { + date: 'Feb 22', + SemiAnalysis: 2756, + 'The Pragmatic Engineer': 2103, + }, + { + date: 'Mar 22', + SemiAnalysis: 3322, + 'The Pragmatic Engineer': 2194, + }, + { + date: 'Apr 22', + SemiAnalysis: 3470, + 'The Pragmatic Engineer': 2108, + }, + { + date: 'May 22', + SemiAnalysis: 3475, + 'The Pragmatic Engineer': 1812, + }, + { + date: 'Jun 22', + SemiAnalysis: 3129, + 'The Pragmatic Engineer': 1726, + }, + ] + + const kpis: Kpi[] = [ // 👈 Create some sample KPI data + { + title: 'Sales', + metric: '$ 12,699', + progress: 15.9, + metricTarget: '$ 80,000', + delta: '13.2%', + deltaType: 'moderateIncrease', + }, + { + title: 'Profit', + metric: '$ 45,564', + progress: 36.5, + metricTarget: '$ 125,000', + delta: '23.9%', + deltaType: 'increase', + }, + { + title: 'Customers', + metric: '1,072', + progress: 53.6, + metricTarget: '2,000', + delta: '10.1%', + deltaType: 'moderateDecrease', + }, + ] + + const dataFormatter = (number: number) => { + return '$ ' + Intl.NumberFormat('us').format(number).toString() + } + + return ( +
+ + +

Dashboard

+ + + {kpis.map((kpi, i) => ( + + + + ))} + + + Newsletter revenue over time (USD) + + + + +
+ ) +} + +export default DashboardPage +``` + +Congratulations! You made your first dashboard. + +## Next Steps + +Now that you have a Dashboard + +1. Explore the other [components](https://www.tremor.so/components) and [blocks](https://www.tremor.so/blocks) that you can use to showcase your data + +2. Learn how to make a [dynamic dashboard using RedwoodJS cells](https://github.com/redwoodjs/redwoodjs-tremor-dashboard-demo) to fetch data from a Prisma-backed database using GraphQL. + +3. See a [dynamic dashboard live demo](https://tremor-redwood-dashboard-demo.netlify.app)! + + diff --git a/docs/versioned_docs/version-5.0/typescript/utility-types.md b/docs/versioned_docs/version-5.0/typescript/utility-types.md index 867ff9932569..d4fe3a5dba82 100644 --- a/docs/versioned_docs/version-5.0/typescript/utility-types.md +++ b/docs/versioned_docs/version-5.0/typescript/utility-types.md @@ -36,8 +36,7 @@ type SuccessProps = CellSuccessProps { // ...