Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/kiliman/remix-component-data

This is a proof of concept for showing how you can expose loader functions from your components to use in your routes.
https://github.com/kiliman/remix-component-data

Last synced: 6 days ago
JSON representation

This is a proof of concept for showing how you can expose loader functions from your components to use in your routes.

Awesome Lists containing this project

README

        

# Remix Component Data

Screenshot of Component Data with error handling

This is a proof of concept for showing how you can expose `loader` and `actions` functions
from your components to use in your routes.

Once the API has settled down, I'll create a NPM package.

View example site at https://remix-component-data.herokuapp.com/

## 🛠 Usage

Your route module still controls the current page's loader. To add component
loaders, you will need to import them manually into your route.

```ts
// route.tsx
import Widget1, { loader as widget1loader } from '~/components/widget1'
import Widget2, { loader as widget2loader } from '~/components/widget2'

// widget1.tsx
export let loader: LoaderFunction = async () => {
return json({ message: 'Hello from Widget 1' })
}
```

You then need to call these component loaders and return the data with your
existing route loader.

```ts
// route.tsx
import { callComponentLoaders } from '~/utils/remix-component-data'

export let loader: LoaderFunction = async () => {
// your route's data
let routeData = { ... }
// call all component loaders
const componentData = await callComponentLoaders({
widget1: widget1Loader,
widget2: widget2Loader,
})
return json({ routeData, componentData })
}
```

You can now access the component data in your default route export. Wrap your
component inside `` and pass the specific component's data with
the `data` prop.

```ts
// route.tsx
import ComponentData from '~/components/ComponentData'

export default function Index() {
let { routeData, componentData } = useLoaderData()
return (


{/* Wrap widget in and pass in widget loader data */}







)
}
```

## 🗂 Accessing Component Loader Data

You can access the loader data for the component using the `useComponentData` hook.
This does not provide access to the route's loader data. You can pass that in
manually using props or with the `useMatches` hook.

```ts
// widget1.tsx
function Widget1() {
const { message } = useComponentData()
}
```

## 🏂 Using Component Actions

You can now export actions from your components. As with loaders, the enclosing
route is still responsible for dispatching the action to the component. Once
an action has been successfully handled, Remix will call the route's loaders
as it usually does.

```ts
// route.tsx
export let action: ActionFunction = async args => {
// call the component actions
const componentResponse = await callComponentActions(args, {
// the action id must be unique
// you can add params and this will be matched
// post?postId=1 will match post?postId
['post?postId']: componentAction,
})
// check to see if the action was handled
if (componentResponse.handled) {
return componentResponse.response
}
// if not, process as route action
return null
}

// wrap component in and pass in props: id and actionData
export default function () {
const { routeData, componentData } = useComponentData()
const actionData = useActionData()
return (
<>
{routeData.postIds
.map(postId => `post?postId=${postId}`)
.map(postId => (



))}
>
)
}
```

```ts
// widget.tsx
// componentParams added to match data from
export let action: ActionFunction = async ({ request, componentParams }) => {
const session = await getSession(request.headers.get('Cookie'))
// get param value from pattern `post?postId`
const { postId } = componentParams
const key = `liked-${postId}`
const isLiked = session.get(key)
if (isLiked) {
session.unset(key)
} else {
session.set(key, true)
}

return json(
{ message: `You liked Post ${postId}`, isLiked },
{ headers: { 'Set-Cookie': await commitSession(session) } },
)
}

function Widget4() {
let { postId, isLiked } = useComponentData()
const actionData = useComponentActionData()
if (actionData) {
isLiked = actionData.isLiked
}

return (


Post {postId}




{/* adds hidden input with unique key */}

{isLiked ? 'Unlike' : 'Like'}



)
}
```

## 💣 Handling errors

Your component can also have a custom `CatchBoundary` and `ErrorBoundary`. Simply
add it as a property to your default export. Just like a route, you can access the
data using the `useComponentCatch` hook and the `error` prop.

```ts
// widget1.tsx
function Widget1() {}
export default Widget1

Widget1.CatchBoundary = function () {
const caught = useComponentCatch()
...
}
Widget1.ErrorBoundary = function({error}) {
return

{error.message}

}
```

## 🙏 Feedback welcome

Please provide feedback either via Issues or on the Remix Discord. Thanks for
taking a look.