material-ui icon indicating copy to clipboard operation
material-ui copied to clipboard

Improve Remix support

Open samuelsycamore opened this issue 9 months ago • 10 comments

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 and entry.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', to remix.config.js to get around this problem, but it didn't work for me when I tried it—maybe I'm missing something

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 avatar Nov 06 '23 01:11 samuelsycamore

@samuelsycamore I have MUI working with renderToPipeableStream using this sample:

https://github.com/remix-run/examples/blob/main/chakra-ui/app/entry.server.tsx

arunmmanoharan avatar Nov 17 '23 18:11 arunmmanoharan

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,

  1. use the remix material UI example
  2. make a new route
  3. paste in an example from Material UI datagrid that uses useDemoData

yehudamakarov avatar Nov 24 '23 18:11 yehudamakarov

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

rahad06 avatar Dec 08 '23 08:12 rahad06

@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 avatar Dec 17 '23 09:12 mbarni99

@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,
                    })
                );

Brocktho avatar Dec 27 '23 01:12 Brocktho

Is it possible to use material ui with remix spa mode?

michael-land avatar Jan 20 '24 04:01 michael-land

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

mahmoudmoravej avatar Mar 21 '24 18:03 mahmoudmoravej

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.

khera avatar May 01 '24 01:05 khera

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 avatar May 01 '24 05:05 mnajdova

@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.

mahmoudmoravej avatar May 02 '24 13:05 mahmoudmoravej