kasinskas / react-native-media-query

Media queries for react-native and react-native-web
76 stars 8 forks source link

Support for nextjs 13 + with app router #16

Open AareFabrik opened 1 month ago

AareFabrik commented 1 month ago

Hello

Nice package, it is really useful. But unfortunately I was not able to use it for the new app router for nextjs.

Is there a possibility to make this work?

kasinskas commented 1 day ago

are you using react-native and react-native-web or just pure next.js?

kasinskas commented 1 day ago

it's been a while since last time I worked with next, so I'm not familiar with app router yet, but i think i got it working. I'll update the readme with examples and will update rnmq with some helpers like RNMQProvider to make this easier when I have some time, but in the meantime to get app router working this is what needs to be done:

if using react-native + react-native-web + rnmq:

need to update aliases and extensions in next.config:

const nextConfig = {
...
webpack(config) {
    config.resolve.alias = {
      ...(config.resolve.alias || {}),
      "react-native$": "react-native-web",
      "react-native/Libraries/EventEmitter/RCTDeviceEventEmitter$":
        "react-native-web/dist/vendor/react-native/NativeEventEmitter/RCTDeviceEventEmitter",
      "react-native/Libraries/vendor/emitter/EventEmitter$":
        "react-native-web/dist/vendor/react-native/emitter/EventEmitter",
      "react-native/Libraries/EventEmitter/NativeEventEmitter$":
        "react-native-web/dist/vendor/react-native/NativeEventEmitter",
    };
    config.resolve.extensions = [
      ".web.js",
      ".web.jsx",
      ".web.ts",
      ".web.tsx",
      ...(config.resolve?.extensions ?? []),
    ];
    return config;
  },
...
}

create or update globals.css with:

html,
body,
#__next {
  width: 100%;
  /* To smooth any scrolling behavior */
  -webkit-overflow-scrolling: touch;
  margin: 0px;
  padding: 0px;
  /* Allows content to fill the viewport and go beyond the bottom */
  min-height: 100%;
}
#__next {
  flex-shrink: 0;
  flex-basis: auto;
  flex-direction: column;
  flex-grow: 1;
  display: flex;
  flex: 1;
}
html {
  scroll-behavior: smooth;
  /* Prevent text size change on orientation change https://gist.github.com/tfausak/2222823#file-ios-8-web-app-html-L138 */
  -webkit-text-size-adjust: 100%;
  height: 100%;
}
body {
  display: flex;
  /* Allows you to scroll below the viewport; default value is visible */
  overflow-y: auto;
  overscroll-behavior-y: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -ms-overflow-style: scrollbar;
}

create RNMQProvider.tsx:

"use client";
import { useServerInsertedHTML } from "next/navigation";
import { StyleSheet } from "react-native";
import { flush } from "react-native-media-query";
export function RNMQProvider({ children }: { children: React.ReactNode }) {
  useServerInsertedHTML(() => {
    const style = flush();
    const sheet = StyleSheet.getSheet();

    return (
      <>
        {style}
        <style
          dangerouslySetInnerHTML={{ __html: sheet.textContent }}
          id={sheet.id}
        />
      </>
    );
  });
  return <>{children}</>;
}

then import RNMQProvider and globals.css into layout.tsx and wrap children with it:

...
import "./globals.css";
import { RNMQProvider } from "./RNMQProvider";
...

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body>
        <RNMQProvider>{children}</RNMQProvider>
      </body>
    </html>
  );
}

then use RNMQ as usual:

"use client";

import StyleSheet from "react-native-media-query";
import { Text, View } from "react-native";

const { ids, styles } = StyleSheet.create({
  example: {
    backgroundColor: "green",
    borderRadius: 5,
    "@media (max-width: 1600px) and (min-width: 800px)": {
      backgroundColor: "red",
      borderRadius: 10,
    },
    "@media (max-width: 800px)": {
      backgroundColor: "blue",
      borderRadius: 15,
    },
  },
});

export default function IndexPage() {
  return (
    <View>
      <Text style={styles.example} dataSet={{ media: ids.example }}>
        Hello, world!
      </Text>
    </View>
  );
}

not using rn and rn-web makes this a bit easier:

globals.css from above is not needed

RNMQProvider.tsx looks like this:

"use client";
import { useServerInsertedHTML } from "next/navigation";
import { flush } from "react-native-media-query";
export function RNMQProvider({ children }: { children: React.ReactNode }) {
  useServerInsertedHTML(() => {
    const style = flush();
    return style;
  });
  return <>{children}</>;
}

and then use it:

"use client";
import StyleSheet from "react-native-media-query";

const { ids, styles } = StyleSheet.create({
  example: {
    backgroundColor: "green",
    borderRadius: 5,
    "@media (max-width: 1600px) and (min-width: 800px)": {
      backgroundColor: "red",
      borderRadius: 10,
    },
    "@media (max-width: 800px)": {
      backgroundColor: "blue",
      borderRadius: 15,
    },
  },
});

export default function IndexPage() {
  return (
    <div>
      <p style={styles.example} data-media={ids.example}>
        Hello, world!
      </p>
    </div>
  );
}

let me know if this makes sense. I will look into supporting styling without 'use client', but if you're using react-native-web then I don't think it supports running without 'use client' directive anyway