diff --git a/docs/examples/InvalidateQuery.tsx b/docs/examples/InvalidateQuery.tsx new file mode 100644 index 0000000..11859b1 --- /dev/null +++ b/docs/examples/InvalidateQuery.tsx @@ -0,0 +1,74 @@ +import { bind, Subscribe } from "@react-rxjs/core" +import { createSignal } from "@react-rxjs/utils" +import React, { useRef } from "react" +import { concat, defer } from "rxjs" +import { concatMap, switchMap } from "rxjs/operators" + +const { getTodos, postTodo } = (() => { + let todos = [ + { + id: 0, + title: "Grocery shopping", + }, + ] + + return { + getTodos: async () => todos, + postTodo: async (todo) => { + todos = [ + ...todos, + { + id: todos[todos.length - 1].id + 1, + title: todo, + }, + ] + }, + } +})() + +const [todoPost$, addTodo] = createSignal() + +const todoResult$ = todoPost$.pipe(concatMap(postTodo)) + +const [useTodos] = bind( + // When do we need to request todos? + concat( + // 1. One single time when starting + defer(getTodos), + // 2. Every time we have created a new todo + todoResult$.pipe(switchMap(getTodos)), + ), + [], +) + +function Todos() { + const todos = useTodos() + + const ref = useRef() + const handleAddClick = () => { + addTodo(ref.current!.value) + ref.current!.value = "" + ref.current!.focus() + } + + return ( +
+ + + +
    + {todos.map((todo) => ( +
  • {todo.title}
  • + ))} +
+
+ ) +} + +export default function InvalidateQuery() { + return ( + Loading...}> + + + ) +} diff --git a/docs/features.md b/docs/features.md new file mode 100644 index 0000000..ea76f4c --- /dev/null +++ b/docs/features.md @@ -0,0 +1,15 @@ +--- +title: Features +--- + +- Build truly reactive state. +- Makes code-splitting simple. +- Simplifies code navigability. +- First-class support for React Suspense. +- First-class support for Error boundaries. +- Completely decentralized store. +- Built in Typescript. +- Works with any React renderer (React DOM, React Native, etc.). +- No external dependencies. +- Extremely light: 3.4kB parsed, 1.5kB gziped. +- Thin API. diff --git a/docs/getting-started.md b/docs/getting-started.md deleted file mode 100644 index d3732a7..0000000 --- a/docs/getting-started.md +++ /dev/null @@ -1,99 +0,0 @@ ---- -title: Getting Started ---- - -import CharacterCounter from "./examples/CharacterCounter" -import BrowserOnly from '@docusaurus/BrowserOnly'; - -## Installation - -React-RxJS is published in npm as `@react-rxjs/core` - -```sh -npm i rxjs @react-rxjs/core -``` - -or using yarn - -```sh -yarn add rxjs @react-rxjs/core -``` - -## Create a hook from an observable - -`@react-rxjs/core` exports a function called `bind` which is used to connect a stream to a hook. - -```tsx -import { bind } from "@react-rxjs/core" -import { createSignal } from "@react-rxjs/utils" - -// A signal is an entry point to react-rxjs. It's equivalent to using a subject -const [textChange$, setText] = createSignal(); - -const [useText, text$] = bind(textChange$, "") - -function TextInput() { - const text = useText() - - return ( -
- setText(e.target.value)} - /> -
- Echo: {text} -
- ) -} -``` - -`bind` returns a tuple that contains the hook, plus the underlying shared observable so it can be used by other streams: - -```tsx -import { map } from "rxjs/operators" -import { bind, Subscribe } from "@react-rxjs/core" - -// Previously... -// const [useText, text$] = bind(...); - -const [useCharCount, charCount$] = bind( - text$.pipe( - map((text) => text.length) - ) -) - -function CharacterCount() { - const count = useCharCount() - - return <>Character Count: {count} -} -``` - -Something to note is that a subscription on the underlying observable must be present before the hook is executed. We can use `Subscribe` to help us with it: - -```tsx -function CharacterCounter() { - return ( -
- - - - -
- ) -} -``` - -The interactive result: - - - {() => } - - -## Next steps - -We strongly recommend reading through [core concepts](core-concepts.md) to -understand the mindset of this library. diff --git a/docs/quick-start.md b/docs/quick-start.md new file mode 100644 index 0000000..93f9f43 --- /dev/null +++ b/docs/quick-start.md @@ -0,0 +1,92 @@ +--- +title: Quick Start +--- + +import CharacterCounter from "./examples/CharacterCounter" +import BrowserOnly from '@docusaurus/BrowserOnly'; + +## Installation + +React-RxJS is published in npm as `@react-rxjs/core`. +We also publish a recommended `@react-rxjs/utils` package with some useful functions to build reactive state streams. + +```sh +npm i rxjs @react-rxjs/core @react-rxjs/utils +``` + +or using yarn + +```sh +yarn add rxjs @react-rxjs/core @react-rxjs/utils +``` + +## Usage + +```tsx +import { map } from "rxjs/operators" +import { bind, Subscribe } from "@react-rxjs/core" +import { createSignal } from "@react-rxjs/utils" + +// A signal is an entry point to react-rxjs. It's equivalent to using a subject +const [textChange$, setText] = createSignal() + +// bind returns a hook to get the value of the observable. +const [useText, text$] = bind(textChange$, "") + +// And it also returns an observable so we can compose this state with other observables +// in here we're making a derived state by calculating the text$'s length. +const [useCharCount, charCount$] = bind( + text$.pipe( + map((text) => text.length) + ) +) + +function TextInput() { + // Just use the hook! + const text = useText() + + return ( +
+ setText(e.target.value) + } + /> +
+ Echo: {text} +
+ ) +} + +function CharacterCount() { + const count = useCharCount() + + return <>Character Count: {count} +} + +function CharacterCounter() { + // `Subscribe` manages the subscription lifetime of its children + return ( + + + + + ) +} +``` + +### Interactive result + + + {() => } + + +## There's more! + +This is just a simple example of two components sharing a synchronous state. + +React-RxJS gets even more fun when you start using asynchronous state, leveraging Suspense and enhancing code-splitting! diff --git a/docs/recipes/invalidate-query.md b/docs/recipes/invalidate-query.md new file mode 100644 index 0000000..dac5ee9 --- /dev/null +++ b/docs/recipes/invalidate-query.md @@ -0,0 +1,71 @@ +--- +title: Invalidate Query +--- + +import InvalidateQuery from "../examples/InvalidateQuery" +import BrowserOnly from '@docusaurus/BrowserOnly'; + +```tsx +import { bind, Subscribe } from "@react-rxjs/core" +import { createSignal } from "@react-rxjs/utils" +import { concat, defer } from "rxjs" +import { concatMap, switchMap } from "rxjs/operators" +import { getTodos, postTodo } from "../my-api" + +const [todoPost$, addTodo] = createSignal() + +const todoResult$ = todoPost$.pipe( + concatMap(postTodo) +) + +const [useTodos] = bind( + // When do we need to request todos? + concat( + // 1. One single time when starting + defer(getTodos), + // 2. Every time we have created a new todo + todoResult$.pipe( + switchMap(getTodos) + ) + ), + [] +) + +function Todos() { + const todos = useTodos() + + const ref = useRef() + const handleAddClick = () => { + addTodo(ref.current!.value) + ref.current!.value = "" + ref.current!.focus() + } + + return ( +
+ + + +
    + {todos.map((todo) => ( +
  • {todo.title}
  • + ))} +
+
+ ) +} + +function App() { + return ( + + + + ) +} +``` + +### Interactive result + + + {() => } + diff --git a/sidebars.js b/sidebars.js index 1c2bbd6..8ff77d4 100644 --- a/sidebars.js +++ b/sidebars.js @@ -1,7 +1,7 @@ module.exports = { someSidebar: { - Introduction: ["motivation", "core-concepts", "getting-started"], - Tutorial: ["tutorial/github-issues", "tutorial/todos"], + Introduction: ["motivation", "quick-start", "features"], + Recipes: ["recipes/invalidate-query"], "API Reference": [ { type: "category", diff --git a/src/pages/index.js b/src/pages/index.js index 0613a4e..b05e7a8 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -112,7 +112,7 @@ function Home() { "button button--outline button--secondary button--lg", styles.getStarted, )} - to={useBaseUrl("docs/getting-started")} + to={useBaseUrl("docs/motivation")} > Get Started