why importing CSSRulePlugin in Next.js results in error? - reactjs

I am trying to use GSAP library in my Next.js project i downloaded the npm version of the library
from react jsap.
but when i import it like this:
import { gsap } from "gsap";
import { CSSRulePlugin } from "gsap/CSSRulePlugin";
it throws an error, the error seems to be caused by CSSRulePlugIn since when i remove it from imports everything is fine.
the error:

apparently this error occurs because GSAP tries to access the window element of the client browser but since i was using it in Next.js (SSR) so it would result in that error since there was no window to get a hold of.
so i ended up solving the problem by importing CSSRulePlugin only after making sure that the code runs in the client side, and for that i imported it inside useEffect method and it worked.
here is the code in my case:
useEffect(() => {
const GSAP = require("gsap/CSSRulePlugin");
const { CSSRulePlugin } = GSAP;
gsap.registerPlugin(CSSRulePlugin);
// do whatever you want to do with the plugin, its Working now...
// for example
let imageReveal = CSSRulePlugin.getRule(".container:after");
}, []);

Related

How to solve hydration errors related to dates in a React / Remix application?

I'm building an application as a hobby project and as an effort to try and learn server rendered React, but I've stumbled on a seemingly easy to fix error, but I do not know how I should approach the problem. Using Remix 1.10.
While my code runs, it is flawed. The server renders one thing and the client another, causing the rendered element to flicker on pageload. It also throws a multitude of errors in the console, like:
Uncaught Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.
24x react-dom.development.js:12507 Uncaught Error: Hydration failed because the initial UI does not match what was rendered on the server.
react_devtools_backend.js:4012 Warning: Text content did not match. Server: "1/29/2023, 10:44:09 AM" Client: "1/29/2023, 12:44:09 PM"
The server is on UTC timezone but the client can be anything. In this case it's GMT+2. What should I do? I think I could set the server timezone to what the client timezone is but I also think that might be a terrible idea.
The best barebones dumbed down example I could make is this.
// routes/example.tsx
import { useLoaderData } from "#remix-run/react"
import {json, LoaderArgs } from "#remix-run/server-runtime"
export async function loader({ request }: LoaderArgs) {
const timestampFromDB = "2023-01-29T10:44:09.672Z"
return json({ time: timestampFromDB })
}
export default function HydrationError() {
const loaderData = useLoaderData<typeof loader>()
const time = new Date(loaderData.time)
const stamp = time.toLocaleString("en-US")
return (
<div>
Time:
<time>{stamp}</time>
</div>
)
}
I tried to look for answers before asking, but the closest thing I found isn't even close to what my problem is; Remix Hydration failed: UI on server and client do not match. In my case, it's not fine locally, it's not fine at all.
The toLocaleString spec allows output variations across implementations so you're probably better off avoiding the client's implementation and just using the server's implementation by moving toLocaleString to the loader.
// routes/example.tsx
import { useLoaderData } from "#remix-run/react"
import {json, LoaderArgs } from "#remix-run/server-runtime"
export async function loader({ request }: LoaderArgs) {
const timestampFromDB = "2023-01-29T10:44:09.672Z"
return json({ stamp: new Date(timestampFromDB).toLocaleString('en-US') })
}
export default function HydrationError() {
const { stamp } = useLoaderData<typeof loader>()
return (
<div>
Time:
<time>{stamp}</time>
</div>
)
}
Alternatively you might want to look at Intl.DateTimeFormat which gives you greater control over date rendering and may offer more consistency.
React Intl is a library built on top of Intl.DateTimeFormat which is worth checking out.

Expo freezes if using ReactNative's StyleSheet

I have encountered a bug with react-native's Stylesheet component. It causes my iphone simulation to stay stuck on expo's logo after loading the js bundle. No errors to be found.
import { Text, View, StyleSheet } from "react-native"
var styles = StyleSheet.create({
innerText: {
color: 'red',
}
})
export var TestComponent = function() {
return (<View><Text style={styles.innerText}>NOW Lets get this working</Text></View>)
}
If I just put the style inline, it works and renders correctly. No freezing after bundle loading.
return (<View><Text style={{color:'red'}}>NOW Lets get this working</Text></View>
One thing to note is that this TestComponent is coming from a node_module. Stylesheet's work as expected in the app's codebase, but fail when coming from this node_module.
The module that exports the TestComponent was using react-native 0.68.0, but the app that uses the module was using react-native 0.64.3. When I downgraded the module to the same react-native version, it worked.
It also worked when updating the app's version instead of downgrading the other, but this time expo gives a warning: Warning: Invalid version react-native#0.68.1 for expo sdkVersion 44.0.0. Use react-native#0.64.3

I'm unable to load #apollo/client on a React Native Expo app

I'm trying to load #apollo/client on a React Native Expo app.
And I get this error:
While trying to resolve module #apollo/client from file /Users/andrepena/git/anglio-mobile-rn/screens/dictionary/index.tsx, the package /Users/andrepena/git/anglio-mobile-rn/node_modules/#apollo/client/package.json was successfully found. However, this package itself specifies a main module field that could not be resolved (/Users/andrepena/git/anglio-mobile-rn/node_modules/#apollo/client/main.cjs. Indeed, none of these files exist
Then I searched Stackoverflow and someone said I should add this to my metro.config.json
const { getDefaultConfig } = require("#expo/metro-config");
const defaultConfig = getDefaultConfig(__dirname);
defaultConfig.resolver.assetExts.push("cjs");
module.exports = defaultConfig;
But now, all imports from #apollo/client simply return undefined.
Example:
import { ApolloClient, InMemoryCache } from "#apollo/client";
console.log(ApolloClient); // undefined
console.log(InMemoryCache); // undefined
In fact, #apollo/client is exporting this object:
Object {
"default": 17,
}
Any suggestion?
This metro.config.js worked for me: (remember to install #expo/metro-config)
const { getDefaultConfig } = require('#expo/metro-config');
const config = getDefaultConfig(__dirname, {
// Initialize in exotic mode.
// If you want to preserve `react-native` resolver main field, and omit cjs support, then leave this undefined
// and skip setting the `EXPO_USE_EXOTIC` environment variable.
mode: 'exotic',
});
module.exports = config;
The exotic thing makes Metro to be able to find the weird cjs module that #apollo/client exports

Stripe + NextJs - window is not defined

I'm trying to use Stripe in NextJs https://github.com/stripe/react-stripe-elements/blob/master/README.md#server-side-rendering-ssr
I keep getting the error "window is not defined". Am I missing something? The code is at the link above.
"window is not defined" is shown due to the fact that your code is server-side rendered and can't access the global window object because that is something only a client will understand. move your code inside lifecycle methods as they run only on the client-side.
Another option is to use a dynamic import for the Stripe component and disable SSR.
StripeForm component file (export as default)
component/StripeForm.tsx
Import it dynamically in pages/stripe like so
const StripeForm = dynamic(() => import("../components/StripeForm"), { ssr: false } )
return (
...
<StripeForm />
...
)

React application with external plugins

I'm building a React application bundled using Parcel or Webpack.
The application should be able to embed external React components
developed by third-parties and hosted elsewhere as modern javascript modules:
// https://example.com/scripts/hello-plugin.js
import React from 'react';
export default class HelloPlugin extends React.Component {
render() {
return "Hello from external plugin!";
}
}
Host application loads these components using asynchronous import like this, for example:
// createAsyncComponent.tsx
import * as React from 'react';
import { asyncComponent } from 'react-async-component';
export default function createAsyncComponent(url: string) {
return asyncComponent({
resolve: () => import(url).then(component => component.default),
LoadingComponent: () => <div>Loading {url}....</div>,
ErrorComponent: ({ error }) => <div>Couldn't load {url}: {error.message}</div>,
})
}
But looks like bundlers don't allow importing arbitrary urls as external javascript modules.
Webpack emits build warnings: "the request of a dependency is an expression" and the import doesn't work. Parcel doesn't report any errors, but fails when import(url) occurs at runtime.
Webpack author recommends using scriptjs or little-loader for loading external scripts.
There is a working sample that loads an UMD component from arbitrary URL like this:
public componentDidMount() {
// expose dependencies as globals
window["React"] = React;
window["PropTypes"] = PropTypes;
// async load of remote UMD component
$script(this.props.url, () => {
const target = window[this.props.name];
if (target) {
this.setState({
Component: target,
error: null,
})
} else {
this.setState({
Component: null,
error: `Cannot load component at ${this.props.url}`,
})
}
});
}
Also, I saw a similar question answered a year ago where the suggested approach also involves passing variables via a window object.
But I'd like to avoid using globals given that most modern browsers support modules out of the box.
I'm wondering if it's possible. Perhaps, any way to instruct the bundler that my import(url) is not a request for the code-split chunk of a host application, but a request for loading an external Javascript module.
In the context of Webpack, you could do something like this:
import(/* webpackIgnore: true */'https://any.url/file.js')
.then((response) => {
response.main({ /* stuff from app plugins need... */ });
});
Then your plugin file would have something like...
const main = (args) => console.log('The plugin was started.');
export { main };
export default main;
Notice you can send stuff from your app's runtime to the plugin at the initialization (i.e. when invoking main at the plugin) of the plugins so you don't end up depending on global variables.
You get caching for free as Webpack remembers (caches) that the given URL has already loaded so subsequent calls to import that URL will resolve immediately.
Note: this seems to work in Chrome, Safari & firefox but not Edge. I never bothered testing in IE or other browsers.
I've tried doing this same sort of load with UMD format on the plugin side and that doesn't seem to work with the way Webpack loads stuff. In fact it's interesting that variables declared as globals, don't end up in the window object of your runtime. You'd have to explicitly do window.aGlobalValue = ... to get something on the global scope.
Obviously you could also use requirejs - or similar - in your app and then just have your plugins follow that API.
Listen to the Webpack author. You can't do (yet) what you're trying to do with Webpack.
You will have to follow his suggested route.

Resources