Skip to content
This repository has been archived by the owner on Sep 14, 2023. It is now read-only.

Commit

Permalink
docs(readme): make general improvements
Browse files Browse the repository at this point in the history
closes #35
  • Loading branch information
derrickbeining committed Dec 5, 2018
1 parent a17be3e commit 98c20c6
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 67 deletions.
41 changes: 18 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@

## Description

`react-atom` provides a simple way to manage state in React, for both global app state and for local component state:

`Atom`s ✨
`react-atom` provides a simple way to manage state in React, for both global app state and for local component state: ✨`Atom`s✨

### Put your state in an `Atom`:

Expand All @@ -54,25 +52,25 @@ const appState = Atom.of({
});
```

### Read Atom state with `deref`
### Read state with `deref`

You can't inspect `Atom` state directly, you have to `deref` it, like this:
You can't inspect `Atom` state directly, you have to `deref`erence it, like this:

```js
import { deref } from "@dbeining/react-atom";

const { color } = deref(appState);
```

### Update the state with `swap`
### Update state with `swap`

You can't modify an `Atom` directly. The only way to alter the state of an `Atom` is with `swap`. Here's its call signature:
You can't modify an `Atom` directly. The main way to update state is with `swap`. Here's its call signature:

```ts
function swap<S>(atom: Atom<S>, updateFn: (state: S) => S): void;
```

`updateFn` is applied to `atom`'s state and its return value is set as `atom`'s new state. There are just two simple rules for `updateFn`:
`updateFn` is applied to `atom`'s state and the return value is set as `atom`'s new state. There are just two simple rules for `updateFn`:

1. it must return a value of the same type/interface as the previous state
2. it must not mutate the previous state
Expand All @@ -91,7 +89,7 @@ const setColor = color =>

Take notice that our `updateFn` is [spread](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax)ing the old state onto a new object before overriding `color`. This is an easy way to obey the rules of `updateFn`.

### Side-Effects? Just use `swap`.
### Side-Effects? Just use `swap`

You don't need to do anything special for managing side-effects. Just write your IO-related logic as per usual, and call `swap` when you've got what you need. For example:

Expand All @@ -103,7 +101,7 @@ const saveColor = async color => {
};
```

### Use Atoms in components with ✨ `useAtom`
### `useAtom`✨ to subscribe to Atom state in components

`useAtom` is a [custom React Hook][customhooksurl]. It does two things:

Expand Down Expand Up @@ -158,8 +156,9 @@ export function ColorReporter(props) {
<summary>
😬 <strong><code>React.useState</code> doesn't play nice with <code>React.memo</code></strong>
</summary>

`useState` is cool until you realize that in most cases it forces you to pass new function instances through props on every render because you usually need to wrap the `setState` function in another function. That makes it hard to take advantage of `React.memo`. For example:
<blockquote>
<code>useState</code> is cool until you realize that in most cases it forces you to pass new function instances through props on every render because you usually need to wrap the <code>setState</code> function in another function. That makes it hard to take advantage of <code>React.memo</code>. For example:
<div>---</div>

```jsx
function Awkwardddd(props) {
Expand All @@ -186,18 +185,13 @@ With `react-atom`, this problem doesn't even exist. You can define your update f

```jsx
const state = Atom.of({ name, bigState: { ...useYourImagination } });
const updateName = evt =>
swap(state, s => ({
...s,
name: evt.target.value
}));

const updateName = ({ target }) => swap(state, prev => ({ ...prev, name: target.value }));

const handleDidComplete = val =>
swap(state, s => ({
...s,
bigState: {
...s.bigState,
inner: val
}
swap(state, prev => ({
...prev,
bigState: { ...prev.bigState, inner: val }
}));

function SoSmoooooth(props) {
Expand All @@ -212,6 +206,7 @@ function SoSmoooooth(props) {
}
```

</blockquote>
</details>
<details>
<summary>
Expand Down
23 changes: 11 additions & 12 deletions docs/globals.html
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ <h3>atom</h3>
<li class="tsd-description">
<aside class="tsd-sources">
<ul>
<li>Defined in react-atom-internal.ts:149</li>
<li>Defined in react-atom-internal.ts:148</li>
</ul>
</aside>
<div class="tsd-comment tsd-typography">
Expand Down Expand Up @@ -174,7 +174,7 @@ <h3>deref</h3>
<li class="tsd-description">
<aside class="tsd-sources">
<ul>
<li>Defined in react-atom-internal.ts:174</li>
<li>Defined in react-atom-internal.ts:173</li>
</ul>
</aside>
<div class="tsd-comment tsd-typography">
Expand Down Expand Up @@ -213,7 +213,7 @@ <h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">S</span><
<li class="tsd-description">
<aside class="tsd-sources">
<ul>
<li>Defined in react-atom-internal.ts:205</li>
<li>Defined in react-atom-internal.ts:204</li>
</ul>
</aside>
<div class="tsd-comment tsd-typography">
Expand Down Expand Up @@ -284,17 +284,16 @@ <h3>set</h3>
<li class="tsd-description">
<aside class="tsd-sources">
<ul>
<li>Defined in react-atom-internal.ts:411</li>
<li>Defined in react-atom-internal.ts:410</li>
</ul>
</aside>
<div class="tsd-comment tsd-typography">
<div class="lead">
<p>Takes an <a href="classes/atom.html">Atom</a> with state of some type, <code>S</code>, and sets its
state to a value of the same type, <code>S</code>.</p>
<p>Sets <code>atom</code>s state to <code>nextState</code>.</p>
</div>
<p>Once the <a href="classes/atom.html">Atom</a> is set, all function components that have called
<a href="globals.html#useatom">useAtom</a> on the <a href="classes/atom.html">Atom</a> will automatically re-render so they
read its new state.</p>
<p>It is equivalent to <code>swap(atom, () =&gt; newState)</code> and actually calls <code>swap</code> internally.</p>
<p>Once the <code>atom</code>&#39;s state is set, all components that have called <a href="globals.html#useatom">useAtom</a> on the
<code>atom</code> will automatically re-render so they read its new state.</p>
<dl class="tsd-comment-tags">
<dt>example</dt>
<dd><pre><code class="language-js">
Expand Down Expand Up @@ -343,7 +342,7 @@ <h3>swap</h3>
<li class="tsd-description">
<aside class="tsd-sources">
<ul>
<li>Defined in react-atom-internal.ts:367</li>
<li>Defined in react-atom-internal.ts:366</li>
</ul>
</aside>
<div class="tsd-comment tsd-typography">
Expand Down Expand Up @@ -430,7 +429,7 @@ <h3>use<wbr>Atom</h3>
<li class="tsd-description">
<aside class="tsd-sources">
<ul>
<li>Defined in react-atom-internal.ts:247</li>
<li>Defined in react-atom-internal.ts:246</li>
</ul>
</aside>
<div class="tsd-comment tsd-typography">
Expand Down Expand Up @@ -476,7 +475,7 @@ <h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">S</span><
<li class="tsd-description">
<aside class="tsd-sources">
<ul>
<li>Defined in react-atom-internal.ts:282</li>
<li>Defined in react-atom-internal.ts:281</li>
</ul>
</aside>
<div class="tsd-comment tsd-typography">
Expand Down
49 changes: 23 additions & 26 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -95,24 +95,23 @@ <h4 id="disclaimer-the-react-hooks-api-is-currently-only-a-proposal-therefore-th
<li><a href="#contributing--feedback">Contributing / Feedback</a></li>
</ul>
<h2 id="description">Description</h2>
<p><code>react-atom</code> provides a simple way to manage state in React, for both global app state and for local component state:</p>
<p><code>Atom</code>s ✨</p>
<p><code>react-atom</code> provides a simple way to manage state in React, for both global app state and for local component state: ✨<code>Atom</code>s✨</p>
<h3 id="put-your-state-in-an-atom-">Put your state in an <code>Atom</code>:</h3>
<pre><code class="language-ts"><span class="hljs-keyword">import</span> { Atom } <span class="hljs-keyword">from</span> <span class="hljs-string">"@dbeining/react-atom"</span>;

<span class="hljs-keyword">const</span> appState = Atom.of({
color: <span class="hljs-string">"blue"</span>,
userId: <span class="hljs-number">1</span>
});</code></pre>
<h3 id="read-atom-state-with-deref">Read Atom state with <code>deref</code></h3>
<p>You can&#39;t inspect <code>Atom</code> state directly, you have to <code>deref</code> it, like this:</p>
<h3 id="read-state-with-deref">Read state with <code>deref</code></h3>
<p>You can&#39;t inspect <code>Atom</code> state directly, you have to <code>deref</code>erence it, like this:</p>
<pre><code class="language-js"><span class="hljs-keyword">import</span> { deref } <span class="hljs-keyword">from</span> <span class="hljs-string">"@dbeining/react-atom"</span>;

<span class="hljs-keyword">const</span> { color } = deref(appState);</code></pre>
<h3 id="update-the-state-with-swap">Update the state with <code>swap</code></h3>
<p>You can&#39;t modify an <code>Atom</code> directly. The only way to alter the state of an <code>Atom</code> is with <code>swap</code>. Here&#39;s its call signature:</p>
<h3 id="update-state-with-swap">Update state with <code>swap</code></h3>
<p>You can&#39;t modify an <code>Atom</code> directly. The main way to update state is with <code>swap</code>. Here&#39;s its call signature:</p>
<pre><code class="language-ts"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">swap</span>&lt;<span class="hljs-title">S</span>&gt;(<span class="hljs-params">atom: Atom&lt;S&gt;, updateFn: (state: S) =&gt; S</span>): <span class="hljs-title">void</span></span>;</code></pre>
<p><code>updateFn</code> is applied to <code>atom</code>&#39;s state and its return value is set as <code>atom</code>&#39;s new state. There are just two simple rules for <code>updateFn</code>:</p>
<p><code>updateFn</code> is applied to <code>atom</code>&#39;s state and the return value is set as <code>atom</code>&#39;s new state. There are just two simple rules for <code>updateFn</code>:</p>
<ol>
<li>it must return a value of the same type/interface as the previous state</li>
<li>it must not mutate the previous state</li>
Expand All @@ -126,14 +125,14 @@ <h3 id="update-the-state-with-swap">Update the state with <code>swap</code></h3>
<span class="hljs-attr">color</span>: color
}));</code></pre>
<p>Take notice that our <code>updateFn</code> is <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax">spread</a>ing the old state onto a new object before overriding <code>color</code>. This is an easy way to obey the rules of <code>updateFn</code>.</p>
<h3 id="side-effects-just-use-swap-">Side-Effects? Just use <code>swap</code>.</h3>
<h3 id="side-effects-just-use-swap">Side-Effects? Just use <code>swap</code></h3>
<p>You don&#39;t need to do anything special for managing side-effects. Just write your IO-related logic as per usual, and call <code>swap</code> when you&#39;ve got what you need. For example:</p>
<pre><code class="language-js"><span class="hljs-keyword">const</span> saveColor = <span class="hljs-keyword">async</span> color =&gt; {
<span class="hljs-keyword">const</span> { userId, color } = deref(appState);
<span class="hljs-keyword">const</span> theme = <span class="hljs-keyword">await</span> post(<span class="hljs-string">`/api/user/<span class="hljs-subst">${userId}</span>/theme`</span>, { color });
swap(appState, state =&gt; ({ ...state, <span class="hljs-attr">color</span>: theme.color }));
};</code></pre>
<h3 id="use-atoms-in-components-with-useatom-">Use Atoms in components with ✨ <code>useAtom</code></h3>
<h3 id="-useatom-to-subscribe-to-atom-state-in-components"><code>useAtom</code>✨ to subscribe to Atom state in components</h3>
<p><code>useAtom</code> is a <a href="https://github.com/reactjs/reactjs.org/blob/b7262e78b6efe1d7901afd851fb9cbef5414b361/content/docs/hooks-custom.md">custom React Hook</a>. It does two things:</p>
<ol>
<li>returns the current state of an atom (like <code>deref</code>), and</li>
Expand Down Expand Up @@ -182,8 +181,10 @@ <h2 id="why-use-react-atom-">Why use <code>react-atom</code>?</h2>
<summary>
😬 <strong><code>React.useState</code> doesn't play nice with <code>React.memo</code></strong>
</summary>
<p><code>useState</code> is cool until you realize that in most cases it forces you to pass new function instances through props on every render because you usually need to wrap the <code>setState</code> function in another function. That makes it hard to take advantage of <code>React.memo</code>. For example:</p>
<pre><code class="language-jsx"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Awkwardddd</span>(<span class="hljs-params">props</span>) </span>{
<blockquote>
<code>useState</code> is cool until you realize that in most cases it forces you to pass new function instances through props on every render because you usually need to wrap the <code>setState</code> function in another function. That makes it hard to take advantage of <code>React.memo</code>. For example:
<div>---</div>
<pre><code class="language-jsx"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Awkwardddd</span>(<span class="hljs-params">props</span>) </span>{
<span class="hljs-keyword">const</span> [name, setName] = useState(<span class="hljs-string">""</span>);
<span class="hljs-keyword">const</span> [bigState, setBigState] = useState({ ...useYourImagination });

Expand All @@ -197,22 +198,17 @@ <h2 id="why-use-react-atom-">Why use <code>react-atom</code>?</h2>
&lt;/&gt;
);
}</code></pre>
<p>Every time <code>input</code> fires <code>onChange</code>, <code>ExpensiveButMemoized</code> has to re-render because <code>handleDidComplete</code> is not strictly equal (===) to the last instance passed down.</p>
<p>The React docs admit this is awkward and <a href="https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down">suggest using Context to work around it</a>, because <a href="https://reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback">the alternative is super convoluted</a>.</p>
<p>With <code>react-atom</code>, this problem doesn&#39;t even exist. You can define your update functions outside the component so they are referentially stable across renders.</p>
<pre><code class="language-jsx"><span class="hljs-keyword">const</span> state = Atom.of({ name, <span class="hljs-attr">bigState</span>: { ...useYourImagination } });
<span class="hljs-keyword">const</span> updateName = <span class="hljs-function"><span class="hljs-params">evt</span> =&gt;</span>
swap(state, s =&gt; ({
...s,
<span class="hljs-attr">name</span>: evt.target.value
}));
<p>Every time <code>input</code> fires <code>onChange</code>, <code>ExpensiveButMemoized</code> has to re-render because <code>handleDidComplete</code> is not strictly equal (===) to the last instance passed down.</p>
<p>The React docs admit this is awkward and <a href="https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down">suggest using Context to work around it</a>, because <a href="https://reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback">the alternative is super convoluted</a>.</p>
<p>With <code>react-atom</code>, this problem doesn&#39;t even exist. You can define your update functions outside the component so they are referentially stable across renders.</p>
<pre><code class="language-jsx"><span class="hljs-keyword">const</span> state = Atom.of({ name, <span class="hljs-attr">bigState</span>: { ...useYourImagination } });

<span class="hljs-keyword">const</span> updateName = <span class="hljs-function">(<span class="hljs-params">{ target }</span>) =&gt;</span> swap(state, prev =&gt; ({ ...prev, <span class="hljs-attr">name</span>: target.value }));

<span class="hljs-keyword">const</span> handleDidComplete = <span class="hljs-function"><span class="hljs-params">val</span> =&gt;</span>
swap(state, s =&gt; ({
...s,
<span class="hljs-attr">bigState</span>: {
...s.bigState,
<span class="hljs-attr">inner</span>: val
}
swap(state, prev =&gt; ({
...prev,
<span class="hljs-attr">bigState</span>: { ...prev.bigState, <span class="hljs-attr">inner</span>: val }
}));

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">SoSmoooooth</span>(<span class="hljs-params">props</span>) </span>{
Expand All @@ -225,6 +221,7 @@ <h2 id="why-use-react-atom-">Why use <code>react-atom</code>?</h2>
&lt;/&gt;
);
}</code></pre>
</blockquote>
</details>
<details>
<summary>
Expand Down
11 changes: 5 additions & 6 deletions src/react-atom-internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ const a3 = Atom.of({ count: 0 })
public readonly id: number;

/** @ignore */
// tslint:disable-next-line:variable-name
private constructor(state: S) {
this.id = nextAtomUid++;
stateByAtomId[this.id] = state;
Expand Down Expand Up @@ -385,12 +384,12 @@ export function swap<S>(atom: Atom<S>, updateFn: (state: S) => S): void {
//

/**
* Takes an [[Atom]] with state of some type, `S`, and sets its
* state to a value of the same type, `S`.
* Sets `atom`s state to `nextState`.
*
* Once the [[Atom]] is set, all function components that have called
* [[useAtom]] on the [[Atom]] will automatically re-render so they
* read its new state.
* It is equivalent to `swap(atom, () => newState)` and actually calls `swap` internally.
*
* Once the `atom`'s state is set, all components that have called [[useAtom]] on the
* `atom` will automatically re-render so they read its new state.
*
* @param atom a react-atom [[Atom]] instance
* @param nextState the value to which to set the [[Atom]]'s state. It should be of the same type/interface as the current state
Expand Down

0 comments on commit 98c20c6

Please sign in to comment.