material-ui
material-ui copied to clipboard
Improve Remix support
I've started experimenting with using Material UI and Joy UI components with Remix and I've run into a few bugs and blockers along the way. Browsing the issues here, I see several open threads where folks are discussing the same problems I've bumped into, so I thought it might be helpful to create an umbrella issue to bring all those discussions to one centralized, up-to-date location where folks can track the progress.
Here are all of the open issues I could find—some are overlapping, others are probably pretty outdated and possibly no longer relevant but I haven't dug too deeply into each one:
Open
- [ ] #34170
- [ ] #31957
- [ ] #30359
- [ ] #31233
- [ ] #31140
- [ ] #31835
- [ ] #30922
- [ ] #38965
Problems
Here are the biggest things I see after reviewing these issues:
- Emotion doesn't play well with Remix / React 18
-
renderToPipeableStream()
function doesn't work with Emotion, causing hydration issues- supposedly this workaround can resolve the hydration stuff but I haven't tried it yet myself, and it looks like it came before Remix v2 so I don't know if that changes things
-
- MUI doesn't support ES modules - https://github.com/remix-run/remix/issues/7314#issuecomment-1706998472
- Remix uses esbuild under the hood - #31835
- as mentioned in #39641, it's not ideal to have to customize
entry.client.tsx
andentry.server.tsx
if it can be avoided/minimized
Potential solutions
- migrate away from Emotion - WIP - #38137
- publish packages as ES modules - #30671
- I've read that you can add
serverModuleFormat: 'cjs',
toremix.config.js
to get around this problem, but it didn't work for me when I tried it—maybe I'm missing something
- I've read that you can add
Examples and docs
There are several things we could do to improve the documentation around Remix + MUI!
- We have a nice example of Remix v2 + Material UI: #39229
- We could use a similar example for Joy UI: #39641
- Base UI + Remix example too: #36193
- It would also be really nice to have Integration guides for Remix like we do with the Next.js App Router
- but maybe it's better to wait on "official" integration guides until we've resolved the bigger problems?
If there are any other known issues or requests to improve the DX, please share them here!
@samuelsycamore I have MUI working with renderToPipeableStream using this sample:
https://github.com/remix-run/examples/blob/main/chakra-ui/app/entry.server.tsx
I have this issue where if I import like so:
import { useDemoData } from "@mui/x-data-grid-generator";
I get
Uncaught TypeError: Cannot read properties of undefined (reading 'A400')
at theme.ts:25:17
(anonymous) @ theme.ts:25
The same problem came up for me before and I solved it by changing
import { Alert, Grid } from "@mui/material";
to
import Alert from "@mui/material/Alert";
import Grid from "@mui/material/Grid";
Basically to reproduce,
- use the remix material UI example
- make a new route
- paste in an example from Material UI datagrid that uses
useDemoData
the provided remix template is old and not compatible, packages and layout is obsolete. please provide an up-to-date remix admin template for MUI with internationalization
@samuelsycamore I have MUI working with renderToPipeableStream using this sample:
https://github.com/remix-run/examples/blob/main/chakra-ui/app/entry.server.tsx
Hello,
I have just tried this one out and it may be outdated as I got the following error: Module '"@remix-run/node"' has no exported member 'Response'.
I skipped importing that Response (whatever it is) and tried with the base FetchAPI Response class, and got the following error: Argument of type 'ReadWriteStream' is not assignable to parameter of type 'BodyInit | null | undefined'.
The full code of trying this one is the following
import { CacheProvider as EmotionCacheProvider } from '@emotion/react';
import { createReadableStreamFromReadable } from '@remix-run/node';
import { PassThrough } from 'node:stream';
import { RemixServer } from '@remix-run/react';
import { renderToPipeableStream } from 'react-dom/server';
import createEmotionCache from '@emotion/cache';
import createEmotionServer from '@emotion/server/create-instance';
import isbot from 'isbot';
import type { AppLoadContext, EntryContext } from '@remix-run/node';
const ABORT_DELAY = 5000;
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
// This is ignored so we can keep it in the template for visibility. Feel
// free to delete this parameter in your app if you're not using it!
// eslint-disable-next-line @typescript-eslint/no-unused-vars
loadContext: AppLoadContext
) {
return isbot(request.headers.get('user-agent'))
? handleBotRequest(request, responseStatusCode, responseHeaders, remixContext)
: handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext);
}
function handleBotRequest(request: Request, responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext) {
return new Promise((resolve, reject) => {
const emotionCache = createEmotionCache({ key: 'css' });
let shellRendered = false;
const { pipe, abort } = renderToPipeableStream(
<EmotionCacheProvider value={emotionCache}>
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />
</EmotionCacheProvider>,
{
onAllReady() {
shellRendered = true;
const body = new PassThrough();
const emotionServer = createEmotionServer(emotionCache);
// const stream = createReadableStreamFromReadable(body);
const bodyWithStyles = emotionServer.renderStylesToNodeStream();
body.pipe(bodyWithStyles);
responseHeaders.set('Content-Type', 'text/html');
resolve(
new Response(bodyWithStyles, {
headers: responseHeaders,
status: responseStatusCode,
})
);
pipe(body);
},
onShellError(error: unknown) {
reject(error);
},
onError(error: unknown) {
responseStatusCode = 500;
// Log streaming rendering errors from inside the shell. Don't log
// errors encountered during initial shell rendering since they'll
// reject and get logged in handleDocumentRequest.
if (shellRendered) {
console.error(error);
}
},
}
);
setTimeout(abort, ABORT_DELAY);
});
}
function handleBrowserRequest(request: Request, responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext) {
return new Promise((resolve, reject) => {
let shellRendered = false;
const { pipe, abort } = renderToPipeableStream(<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />, {
onShellReady() {
shellRendered = true;
const body = new PassThrough();
const stream = createReadableStreamFromReadable(body);
responseHeaders.set('Content-Type', 'text/html');
resolve(
new Response(stream, {
headers: responseHeaders,
status: responseStatusCode,
})
);
pipe(body);
},
onShellError(error: unknown) {
reject(error);
},
onError(error: unknown) {
responseStatusCode = 500;
// Log streaming rendering errors from inside the shell. Don't log
// errors encountered during initial shell rendering since they'll
// reject and get logged in handleDocumentRequest.
if (shellRendered) {
console.error(error);
}
},
});
setTimeout(abort, ABORT_DELAY);
});
}
@mbarni99
...I got the following error: Module '"@remix-run/node"' has no exported member 'Response'.
I skipped importing that Response (whatever it is) and tried with the base FetchAPI Response class, and got the following error: >Argument of type 'ReadWriteStream' is not assignable to parameter of type 'BodyInit | null | undefined'.
Remix used to ship a specific Response type that allowed you to dump in a ReadWriteStream into the Response, instead of maintaining that full Response object they now maintain a createReadableStreamFromReadable
You would do mostly the same as what you had posted, but before slapping it all into the body of the Response you need to put it into the createReadableStreamFromReadable function like:
// Import
import { createReadableStreamFromReadable } from "@remix-run/node";
// Other Code
//
//
resolve(
new Response(createReadableStreamFromReadable(stream), {
headers: responseHeaders,
status: responseStatusCode,
})
);
Is it possible to use material ui with remix spa mode?
The current Remix example is a bit outdated and hard to integrate with new Remix templates. I created a brand new integration (with Remix 2.8 + Vite). It helps alot in quick and smooth MUI + Remix integration and also addresses some challenges mentioned here.
Headsup! I am not great with Emotions. But just cross checking with current Remix + MU integration example, it seems it is working as expected with no need to use complex cache/style management.
cc @oliviertassinari @mnajdova
The current Remix example is a bit outdated and hard to integrate with new Remix templates. I created a brand new integration (with Remix 2.8 + Vite). It helps alot in quick and smooth MUI + Remix integration and also addresses some challenges mentioned here.
Thank you so much for this. I was pounding my head for the last 4 hours until I just found this. Incorporating your MuiProvider.tsx file into my entry files was the trick I needed to incorporate MUI into my Remix app. The current "official" example is impossible to decipher as a newbie to Remix working on the current default template.
The current Remix example is a bit outdated and hard to integrate with new Remix templates. I created https://github.com/mahmoudmoravej/remix-mui/pull/1. It helps alot in quick and smooth MUI + Remix integration and also addresses some challenges mentioned here.
@mahmoudmoravej would you like to update the current example we have and open a PR, we can then check what changes you've made.
@mnajdova I did it but I am not sure it will be so helpful, becasue:
- It is upgraded to Remix/Vite template, so you will see a bunch of unnecssary changes.
- The approach I used is completely different, so there will not be a good side-by-side comparison.
I still think the PR I provided above(which simply added MUI on top of the latest version of Remix/Vite template) shows a better understanding of what and how it has been doe.