INTEGRATION
Element Editor with Remix
Similar pattern to Next.js — client-only rendering plus loader/action symmetry for the data round-trip.
Install
npm install @element-editor/react maplibre-glElement needs React and React DOM 19.2 or newer.
Client-only rendering
The map engine needs window. In Remix, the pattern is ClientOnly from remix-utils or a manual dynamic check:
// app/components/Editor.tsx
import { ClientOnly } from 'remix-utils/client-only'
import { ElementEditor, type ElementEditorProps } from '@element-editor/react'
import '@element-editor/react/style.css' // includes MapLibre's CSS
export function Editor(props: ElementEditorProps) {
return (
<ClientOnly fallback={<div>Loading editor…</div>}>
{() => (
<div style={{ height: '80vh' }}>
<ElementEditor {...props} />
</div>
)}
</ClientOnly>
)
}ClientOnly defers render to the client, which is what Element needs.
Loader and action round-trip
Remix's loader/action model pairs well with Element's FeatureCollection types:
// app/routes/map.tsx
import type { LoaderFunctionArgs, ActionFunctionArgs } from '@remix-run/node'
import { json } from '@remix-run/node'
import { useLoaderData, useFetcher } from '@remix-run/react'
import type { FeatureCollection } from '@element-editor/react'
import { Editor } from '~/components/Editor'
export async function loader({ params }: LoaderFunctionArgs) {
const data: FeatureCollection = await db.getFeatures(params.id)
return json({ data })
}
export async function action({ request }: ActionFunctionArgs) {
const data: FeatureCollection = await request.json()
await db.saveFeatures(data)
return json({ ok: true })
}
export default function MapRoute() {
const { data } = useLoaderData<typeof loader>()
const fetcher = useFetcher()
return (
<Editor
initialData={data}
onChange={(updated) => {
fetcher.submit(JSON.stringify(updated), {
method: 'POST',
encType: 'application/json',
})
}}
/>
)
}Loader gives you the initial GeoJSON; action receives the updated shape. Types line up both ways.
Known gotchas
- Keep
ClientOnly. The map only starts in the browser. The Leaflet entry (@element-editor/react/leaflet) toucheswindowas soon as it's imported, so with it, also move the import into a.client.tsxmodule. - Map CSS.
@element-editor/react/style.cssincludes MapLibre's CSS. Import it atapp/root.tsxor alongside the editor component. - Remix Vite vs Remix classic. Both work. The Vite version needs no special config beyond
ClientOnly.