Skip to content

Commit

Permalink
Merge 89e9565 into 430a3ce
Browse files Browse the repository at this point in the history
  • Loading branch information
ryansolid committed Jan 15, 2020
2 parents 430a3ce + 89e9565 commit 759632e
Show file tree
Hide file tree
Showing 33 changed files with 12,410 additions and 11,726 deletions.
134 changes: 69 additions & 65 deletions documentation/api.md
Original file line number Diff line number Diff line change
@@ -1,65 +1,69 @@
# API

### `createRoot(disposer => <code>)`

Creates a new non-tracked context that doesn't auto-dispose. All Solid code should be wrapped in one of these top level as they ensure that all memory/computations are freed up.

### `createState(initValue): [state, setState]`

Creates a new State object and setState pair that can be used to maintain your componenents state.

### `createEffect(prev => <code>, initialValue): void`

Creates a new effect that automatically tracks dependencies. 2nd argument is the initial value.

### `createSignal(initialValue, comparatorFn): [getValueFn, setValueFn]`

Creates a new signal that can be used for reactive tracking. By default signals always notify on setting a value. However a comparator can be passed in to indicate whether the values should be considered equal and listeners not notified.

### `createMemo(prev => <code>, initialValue, comparatorFn): getValueFn`

Creates a readonly signal that recalculates it's value whenever the executed codes dependencies update. Memos only notify dependents when returned value changes. You can also set a custom comparator.

### `createDeferred(prev => <code>, options: { timeoutMs: number }): getValueFn`

Creates memo that only notifies downstream changes when the browser is idle. `timeoutMS` is the maximum time to wait before forcing the update.

### `createDependentEffect(() => <code>, dependencies, defer): void`

Creates a new effect that explicitly tracks dependencies. The 2nd optional argument is an explicit array of dependencies. The 3rd optional argument is whether to defer initial execution of the effect until a value has changed (this only works with explicit dependencies).

### `onCleanup((final: boolean) => <code>)`

Registers a cleanup method that performs that executes on disposal or recalculation of the current context.

### `afterEffects(() => <code>)`

Registers a method that will run after the current execution process is complete. These are useful when waiting on refs to resolves or child DOM nodes to render.

### `sample(() => <code>): any`

Ignores tracking any of the dependencies in the executing code block and returns the value.

### `freeze(() => <code>): any`

Ensures that all updates within the block happen at the same time to prevent unnecessary recalculation. Solid State's setState method and computations(useEffect, useMemo) automatically wrap their code in freeze blocks.

### `createContext(defaultContext): Context`

Creates a new context object that can be used with useContext and the Provider control flow. Default Context is used when no Provider is found above in the hierarchy.

### `useContext(Context): any`

Hook to grab context to allow for deep passing of props with hierarchal resolution of dependencies without having to pass them through each Component function.

### `lazy(() => <Promise>): Component`

Used to lazy load components to allow for things like code splitting and Suspense.

### `loadResource(() => <Promise>): { value, error, loading, failedAttempts, reload }`

Creates a memo that updates when promise is resolved. It tracks dependency changes to retrigger. This works with the Suspend control flow.

### `setDefaults(props, defaultProps): void`

Sets default props for function components in case caller doesn't provide them.
# API

### `createRoot(disposer => <code>)`

Creates a new non-tracked context that doesn't auto-dispose. All Solid code should be wrapped in one of these top level as they ensure that all memory/computations are freed up.

### `createState(initValue): [state, setState]`

Creates a new State object and setState pair that can be used to maintain your componenents state.

### `createEffect(prev => <code>, initialValue): void`

Creates a new effect that automatically tracks dependencies. 2nd argument is the initial value.

### `createSignal(initialValue, comparatorFn): [getValueFn, setValueFn]`

Creates a new signal that can be used for reactive tracking. By default signals always notify on setting a value. However a comparator can be passed in to indicate whether the values should be considered equal and listeners not notified.

### `createMemo(prev => <code>, initialValue, comparatorFn): getValueFn`

Creates a readonly signal that recalculates it's value whenever the executed codes dependencies update. Memos only notify dependents when returned value changes. You can also set a custom comparator.

### `createDeferred(prev => <code>, options: { timeoutMs: number }): getValueFn`

Creates memo that only notifies downstream changes when the browser is idle. `timeoutMS` is the maximum time to wait before forcing the update.

### `createDependentEffect(() => <code>, dependencies, defer): void`

Creates a new effect that explicitly tracks dependencies. The 2nd optional argument is an explicit array of dependencies. The 3rd optional argument is whether to defer initial execution of the effect until a value has changed (this only works with explicit dependencies).

### `onCleanup((final: boolean) => <code>)`

Registers a cleanup methodthat executes on disposal or recalculation of the current context.

### `onError((err: any) => <code>)`

Registers a error handler method that executes when child context errors. Only nearest context error handlers execute. Rethrow to trigger up the line.

### `afterEffects(() => <code>)`

Registers a method that will run after the current execution process is complete. These are useful when waiting on refs to resolves or child DOM nodes to render.

### `sample(() => <code>): any`

Ignores tracking any of the dependencies in the executing code block and returns the value.

### `freeze(() => <code>): any`

Ensures that all updates within the block happen at the same time to prevent unnecessary recalculation. Solid State's setState method and computations(useEffect, useMemo) automatically wrap their code in freeze blocks.

### `createContext(defaultContext): Context`

Creates a new context object that can be used with useContext and the Provider control flow. Default Context is used when no Provider is found above in the hierarchy.

### `useContext(Context): any`

Hook to grab context to allow for deep passing of props with hierarchal resolution of dependencies without having to pass them through each Component function.

### `lazy(() => <Promise>): Component`

Used to lazy load components to allow for things like code splitting and Suspense.

### `load(() => <Promise>, value => void, (error, failedAttempts) => boolean | undefined): [loading, reload]`

Creates a memo that updates when promise is resolved. It tracks dependency changes to retrigger. This works with the Suspend control flow.

### `setDefaults(props, defaultProps): void`

Sets default props for function components in case caller doesn't provide them.
2 changes: 1 addition & 1 deletion documentation/rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Refs come in 2 flavours. `ref` which directly assigns the value, and `forwardRef
```jsx
import { renderToString } from "solid-js/dom";

const HTMLString = renderToString(() => <App />);
const HTMLString = await renderToString(() => <App />);
```

### To rehydrate on the client:
Expand Down
10 changes: 1 addition & 9 deletions documentation/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,7 @@ setState(s => {

## Modifiers

This library also provides of state setter modifiers which can optionally be included to provide different behavior when setting state.

### `force(changes)`

By default state only updates on value change. To get typical signal like behavior on a change you can force update using the force modifier.

```js
setState(force({ name: "John" }));
```
This library also provides a state setter modifiers which can optionally be included to provide different behavior when setting state.

### `reconcile(value, options)`

Expand Down
84 changes: 52 additions & 32 deletions documentation/suspense.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ function Deferred(props) {
</Deferred>
</>;
```

Solid does include a scheduler similar to React's Concurrent Mode scheduler which allows work to be scheduled in idle frames. The easiest way to leverage it is to use `createDeferred` which creates a memo that will only be read as the cpu is available.

```jsx
Expand All @@ -43,7 +44,7 @@ function App() {
<MySlowList text={deferredText()} />
</div>
);
}
}
```

## Placeholders & Transitions
Expand Down Expand Up @@ -93,7 +94,7 @@ import { Suspense } from "solid-js/dom";
function App() {
const [state, setState] = createState({ activeTab: 1 }),
// delay showing fallback for up to 500ms
[startTransition, isPending] = useTransition({ timeoutMs: 500 });
[isPending, startTransition] = useTransition({ timeoutMs: 500 });

return (
<>
Expand Down Expand Up @@ -173,59 +174,78 @@ const App = () => {

## Data Loading

The other supported use of Suspense currently is a more general promise resolver, `loadResource`. `loadResource` accepts reactive function expression that returns a promise and returns a state object with properties:
Solid ships with two resource containers to handle async loading. One is a signal created by `createResource` and the other a state object created by `createResourceState`.

```jsx
import { createResource } from "solid-js";

const fetchUser = id =>
fetch(`https://swapi.co/api/people/${id}/`).then(r => r.json());

- value - the resolved data from the promise
- error - the error from the promise rejection
- loading - a boolean indicator to whether the promise is currently executing
- reload - a function to retry the request after ms;
- failedAttempts - count of consecutive failed requests;
export default const UserPanel = props => {
let [user, load] = createResource(),
isLoading;
createEffect(() => {
isLoading = load(props.userId && fetchUser(props.userId));
})

`loadResource` can be used independent of Suspense if desired. The reactive form is where the power of this method resides, as you can retrigger promise execution on reactive updates, and there is built in promise cancellation. In example below as the `userId` prop updates the API will be queried.
return <div>
<Switch fallback={"Failed to load User"}>
<Match when={isLoading()}>Loading...</Match>
<Match when={user()}>
<h1>{user().name}</h1>
<ul>
<li>Height: {user().height}</li>
<li>Mass: {user().mass}</li>
<li>Birth Year: {user().birthYear}</li>
</ul>
</Match>
</Switch>
</div>
}
```

```jsx
import { loadResource, createEffect } from "solid-js";
import { createResourceState } from "solid-js";

const fetchUser = id =>
fetch(`https://swapi.co/api/people/${id}/`).then(r => r.json());

export default const UserPanel = props => {
const result = loadResource(() => props.userId && fetchUser(props.userId));
// retry up to 3 times with linear backoff
let [user, load] = createResourceState(),
loading;
createEffect(() => {
if (result.error && result.failedAttempts <= 3) {
result.reload(result.failedAttempts * 500);
}
loading = load({ user: props.userId && fetchUser(props.userId) });
})

return <div>
<Switch>
<Match when={result.loading}>Loading...</Match>
<Match when={result.error}>Error: {result.error}</Match>
<Match when={result.value}>
<h1>{result.value.name}</h1>
<Switch fallback={"Failed to load User"}>
<Match when={loading.user}>Loading...</Match>
<Match when={state.user}>
<h1>{state.user.name}</h1>
<ul>
<li>Height: {result.value.height}</li>
<li>Mass: {result.value.mass}</li>
<li>Birth Year: {result.value.birth_year}</li>
<li>Height: {state.user.height}</li>
<li>Mass: {state.user.mass}</li>
<li>Birth Year: {state.user.birthYear}</li>
</ul>
</Match>
</Switch>
</div>
}
```

This examples handles the different states. However, you could have Suspense handle the loading state for you instead by wrapping with the `Suspense` Component.
These examples handle the different loading states. However, you can expand this example to use Suspense instead by wrapping with the `Suspense` Component.

> **For React Users:** At the time of writing this React has not completely settled how their Data Fetching API will look. Solid ships with this feature today, and it might differ from what React ultimately lands on.
## Render as you Fetch

It is important to note that Suspense is tracked based on data requirements of the the reactive graph not the fact data is being fetched. Suspense is inacted when a child of a Suspense Component accesses the `value` property on the resource not when the fetch occurs. In so, it is possible to start loading the Component data and lazy load the Component itself at the same time, instead of waiting for the Component to load to start loading the data.
It is important to note that Suspense is tracked based on data requirements of the the reactive graph not the fact data is being fetched. Suspense is inacted when a child of a Suspense Component accesses a Resource not when the fetch occurs. In so, it is possible to start loading the Component data and lazy load the Component itself at the same time, instead of waiting for the Component to load to start loading the data.

```jsx
// start loading data before any part of the page is executed.
const resource = loadResource(() => /* fetch user & posts */);
const [state, load] = createResourceState()
load({user: fetchUser(), posts: fetchPosts()});

function ProfilePage() {
return (
Expand All @@ -240,14 +260,14 @@ function ProfilePage() {

function ProfileDetails() {
// Try to read user info, although it might not have loaded yet
return <h1>{resource.value.user.name}</h1>;
return <h1>{state.user && state.user.name}</h1>;
}

function ProfileTimeline() {
// Try to read posts, although they might not have loaded yet
return (
<ul>
<For each={resource.value.posts}>{post => (
<For each={state.posts}>{post => (
<li key={post.id}>{post.text}</li>
)}</For>
</ul>
Expand All @@ -262,15 +282,15 @@ render(ProfilePage, document.body);
Sometimes you have multiple `Suspense` Components you wish to coordinate. Sure you could put everything under a single `Suspense` but that limits us to a single loading behavior. A single fallback state and everything always needs to wait until the last thing is loaded. Instead we introduce the `SuspenseList` Component to coordinate. Consider:

```jsx
function ProfilePage() {
function ProfilePage(props) {
return (
<>
<ProfileDetails />
<Suspense fallback={<h1>Loading posts...</h1>}>
<ProfileTimeline />
<ProfileTimeline feed={props.feed} />
</Suspense>
<Suspense fallback={<h2>Loading fun facts...</h2>}>
<ProfileTrivia resource={resource} />
<ProfileTrivia facts={props.facts} />
</Suspense>
</>
);
Expand All @@ -281,4 +301,4 @@ If we wrap this with a `SuspenseList` configured with `revealOrder` of `forwards

A `SuspenseList` can contain other `SuspenseList`'s to create flowing tables or grids etc.

> **For React Users:** Again this works a bit different than its React counterpart as it uses the Context API. In so nesting Suspense Components are perfectly fine. However, do not put them under dynamic areas like control flows as order is based on execution so conditional rendering can cause unpredictable behavior. Also unlike the current Suspense implication even if you are not seeing the "next" Suspense element they are all evaluated immediately on render. This unblocking behavior allows further downstream evaluation that currently does not happen in React.
> **For React Users:** Again this works a bit different than its React counterpart as it uses the Context API. In so nesting Suspense Components are perfectly fine. However, do not put them under dynamic areas like control flows as order is based on execution so conditional rendering can cause unpredictable behavior. Also unlike the current Suspense implication even if you are not seeing the "next" Suspense element they are all evaluated immediately on render. This unblocking behavior allows further downstream evaluation that currently does not happen in React.
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"packages": [
"packages/*"
],
"version": "0.15.5"
"version": "0.16.0-beta.6"
}
Loading

0 comments on commit 759632e

Please sign in to comment.