Dynamic Import Node Module With Next.js - javascript

I was getting an error of window undefined when using react-leaflet node module because it relies on window and of course SSR does not support window. I found next/dynamic, however, all the examples I found show how to import a component and not a node module. Is it possible to include a node module and if so how? As an example this is what I'm importing that is giving the window undefined error import { Map, TileLayer, Marker } from 'react-leaflet';

The issue is that next.js dynamic import fails on named exports
Looking at the source code of react-leaflet I can see that each named export can be accessed from a particular file e.g. import Map from 'react-leaflet/lib/Map'
Combine it with dynamic import without SSR
const Map = dynamic(() => import('react-leaflet/lib/Map'), {
ssr: false
});
This should do a trick for you.

A better way of getting named exports with a dynamic import is to do this:
const NamedExport = dynamic(() => import('package-name').then((module) => module.NamedExport));
Note: this will chunk the full 'package-name' package and then simply extract the named export. If you are able to path into the module package like the accepted example, that will probably be better as it will likely give you a smaller chunk.

That error happened when you call that dependency's component (Map, TileLayer, Marker) on your component.
Window of undefined occured when your application is being rendered on server side, because window object is belong to the browser.
To avoid error of window of undefined on server side, you can use process.browser in your component.
ref: https://github.com/zeit/next.js/issues/2473?#issuecomment-362119102

I kept hitting type definition issues with the accepted answer, and I didn't want to import the whole library. This is what I came up with:
reexport/index.ts:
export { Marker, /* any other type you'll need */ } from 'react-leaflet';
Then, in your component/page, simply import anything from the module above.
const Marker = useMemo(() => dynamic<MarkerProps>(
() => import('./reexport').then(re => re.Marker),
{ ssr: false }
), []);
Note that I've wrapped the call to dynamic in a useMemo hook, and also provided a type for the props.

Related

Unable to import an API module into a nested component (Vue)

I've been struggling with a very odd bug(?) with regards to importing an API module into a nested component in a Vue app.
This is the simplest I could reduce the issue down to.
https://codesandbox.io/s/rough-tree-fqj7o
Essentially, the DogsCreate component renders the CreateDogsModal, which is importing the dogs module from the API directory.
As you can see, the codesandbox errors out even on the base URL with the error Cannot read property 'default' of undefined. If running this code locally not on codesandbox, the base URL renders ok, but if you go to /dogs/create, the error then becomes Failed to resolve component: CreateDogsModal.
The things I've found that fix this are:
Commenting out the API import statement in CreateDogsModal (not an option for us, we need to be able to create and import API modules)
Commenting out the TopNav component in main.js (...also not an option for us)
Importing the TopNav component in App.vue with a relative import or #/components/TopNav.vue works fine, but strangely importing CreateDogsModal and CreateTemplate in DogsCreate.vue with a relative import or #/components/[component-name].vue does not. Also, the latter would be somewhat acceptable as a long-term solution, but I'd prefer the #/components shorthand and that still leaves the root cause undetermined.
I'm using the default vue-cli webpack configuration and have checked via vue inspect that the alias seems to be set properly.
I've been spinning my wheels for a week trying to figure this out and just...cannot. Does anyone have any ideas for what may be happening?
It seems like a race condition in Webpack, using parallel builds, but I'm honestly not sure. I can see CreateDogsModal being pulled in from two places, starting from main.js:
'main.js'
- import 'App.vue'
- import '#/components/index.js'
- import and export 'CreateDogsModal.vue'
- import 'router/index.js'
- import '#/views/Dogs/DogsCreate.vue'
- import '#/components/index.js'
- import and export 'CreateDogsModal.vue'
One workaround is to remove the race by making the CreateDogsModal an async component in DogsCreate:
// DogsCreate.vue
import { defineAsyncComponent } from "vue";
import { CreateTemplate } from "#/components";
export default {
name: "DogsCreate",
components: {
CreateTemplate,
CreateDogsModal: defineAsyncComponent(() => import("#/components/CreateDogsModal.vue")),
},
};
demo

How to force Nextjs to create a chunk for particular module?

I'm trying to use dynamic import for importing a different modules depending on the props passed to the component. Paths to the components are kept in an object
const components = {
'apple': './Apple.js',
'orange': './Orange.js',
// ...
}
Then
// ...
import(components[this.props.type])
// ...
Unfortunately, I get the following error:
Unhandled Rejection (Error): Cannot find module './Apple.js'
It disappears when I explicitly specify the filename (import('./Apple.js')). Is it possible to force nextjs to serve these dynamic modules?
You need to use the dynamic utility from next/dynamic for dynamic import in Next.js. This function is inspired by react-loadable library and uses similar syntax.
The simplest way to dynamically import React component in Next.js is:
const DynamicComponent1 = dynamic(import('../components/hello1'))
If you want to import module that is not a React component than you need to use customizing rendering feature:
const DynamicDeliveryOptions = dynamic({
loader: () => import('../components/delivery-options'),
render(props, loaded) {
const options = loaded.deliveryOptions.map(
option => <option key={option.id}>{option.title}</option>
);
return <select>{options}</select>
},
});
Notice that signature of the render function in Next.js is different from
its signature in react-loadable library (props is the first argument in Next.js).
Returning to your example: I think the best way to import multiple modules will be to declare them all as dynamic modules and conditionally render depending on the passed props (modules won't be loaded before render).
You can tell webpack to put a dynamically named module inside a particular chunk using this syntax:
const module = await import(
/* webpackChunkName: "your_custom_chunk_name" */
"path/to/your/module"
);
Webpack will extract your module's code inside the file: /.next/server/chunks/your_custom_chunk_name.js.
(Tested on NextJs version 12.1.6)

Next.js: How to dynamically import external client-side only React components into Server-Side-Rendering apps developed?

I know this question has been asked multiple times before but none of the solution seems to work.
I'm trying to use the library 'react-chat-popup' which only renders on client side in a SSR app.(built using next.js framework) The normal way to use this library is to call import {Chat} from 'react-chat-popup' and then render it directly as <Chat/>.
The solution I have found for SSR apps is to check if typedef of window !=== 'undefined' in the componentDidMount method before dynamically importing the library as importing the library normally alone would already cause the window is not defined error. So I found the link https://github.com/zeit/next.js/issues/2940 which suggested the following:
Chat = dynamic(import('react-chat-popup').then(m => {
const {Foo} = m;
Foo.__webpackChunkName = m.__webpackChunkName;
return Foo;
}));
However, my foo object becomes null when I do this. When I print out the m object in the callback, i get {"__webpackChunkName":"react_chat_popup_6445a148970fe64a2d707d15c41abb03"} How do I properly import the library and start using the <Chat/> element in this case?
Next js now has its own way of doing dynamic imports with no SSR.
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
Here is the link of their docs: next js
I've managed to resolve this by first declaring a variable at the top:
let Chat = ''
then doing the import this way in componentDidMount:
async componentDidMount(){
let result = await import('react-chat-popup')
Chat = result.Chat
this.setState({
appIsMounted: true
})
}
and finally render it like this:
<NoSSR>
{this.state.appIsMounted? <Chat/> : null}
</NoSSR>
You may not always want to include a module on server-side. For
example, when the module includes a library that only works in the
browser.
Import the library normally in child component and import that component dynamically on parent component.
https://nextjs.org/docs/advanced-features/dynamic-import#with-no-ssr
This approach worked for me.

React: How to add new value to props from imported file?

I am (very) new to React and have been given the task of adding some data to a component that's being brought in from another file. This file spits out some JSON and I want to access certain pieces of data from it, for example:
config.forms.enquiry.title
I am importing the file fine - no problems there. But I am not sure how to include config into my props.
I found a working example, in another file, and have copied what it does. My code is as such
Brings in file with JSON:
import { withSettings } from 'services/settingsFile';
Add config in render function:
render () {
const styles = getStyles(this.props, this.context, this.state);
const { config } = this.props;
// other stuff
Add to propTypes:
enquiryForm.propTypes = {
config: PropTypes.object.isRequired,
// other stuff
Add to compose:
export const enquiryForm = compose(
withSettings,
// other stuff
However, I get the error:
Failed context type: The context config is marked as required in
n, but its value is undefined.
And from here I am not sure what to do. I know it's a tough question, but I know very little about React and have been thrown in the deep end.
Would anyone know what/where I should be searching for to fix this?
If you can import it like,
import { withSettings } from 'services/settingsFile';
why dont you use it like,
const { config } = withSettings;
OK, so the issue was that there was no wrapping element setting config as am attribute.
I had to go up a level to where my component was being brought in and wrap:
<SettingsFile config={window.settingsFile}>
around:
<Component conf={config} />
Then, the component I was working on was able to read config.

Typescript + Google Maps API + infobox.js Uncaught TypeError: InfoBox is not a constructor

Have a project Vue.js + Typescript using Google Maps API, try to import infobox.js (npm package google-maps-infobox-window) library into and getting an error in the console:
Uncaught TypeError: InfoBox is not a constructor
Library doesn't typed yet, so all is remains - import as any, without typings. I tried 3 ways to do it:
import * as InfoBox 'google-maps-infobox-window'
var InfoBox = require('google-maps-infobox-window')
making *d.ts file with manual module/function declaration
And none of these actions has no effect, InfoBox constructor still not found.
Before import infobox.js I already has imported googlemaps (via script tag) and RichMarker.js (via require('')) and those are works perfectly.
Can anyone help with it?
Also, I tried to add "allowJs": true to tsconfig.json, still nothing
Code:
import { } from '#types/googlemaps';
const RichMarker = require('js-rich-marker');
//problems started below when I use
import { InfoBox } from 'google-maps-infobox-window';
// or this
const InfoBox = require('google-maps-infobox-window');
//...
//and try to use imported function (constructor)
let infoBox = new InfoBox({content: boxText});
In search of problem solution I tried a lot of possible ways and all npm packages related to infobox.js.
Finally, I found 'google-maps-infobox' (https://www.npmjs.com/package/google-maps-infobox), which has no exhaustive description, but it was working one at least. Moreover, it has even *.d.ts file, which provide proper import in ts-project.
So, I installed this dependency and added in import section of component
import InfoBox from 'google-maps-infobox';
and use it in this way
//in some method (function)
let infoBox: InfoBox = new InfoBox({
content: //some html markup
});
infoBox.setPosition(mapObject.getPosition());
infoBox.open(this.map);
Also, this package works good with require() import, except of InfoBox-object will be typed as any

Categories