Using switch statement with dynamic components in NextJS - reactjs

I am trying to use dynamic import in NextJS and I do not understand why it works only while storing imported component in a variable. It breaks when I try to return it from other function.
It works this way:
import dynamic from "next/dynamic";
const Article = dynamic(() => import("tutorial/ru/welcome.mdx"));
but like this, well, it breaks:
import dynamic from "next/dynamic";
export default ({ route }) => {
switch (route) {
case "ru":
default:
return dynamic(() => import("tutorial/ru/welcome.mdx"));
}
};
I get the Invalid hook call. Hooks can only be called inside of the body of a function component message.

I think you need to export it , then try to use it like so :
import dynamic from "next/dynamic";
const Article = dynamic(() => import("tutorial/ru/welcome.mdx"));
export default Article;
then try to use it in switch statement :
import Article from './Article';
export default ({ route }) => {
switch (route) {
case "ru":
return (<></>)
default:
return <Article />;
}
};

I found a solution to get over this issue!
import dynamic from "next/dynamic";
import Loader from "components/Loader/Loader";
import Error from "pages/_error";
export default ({ route }) => {
const Article = dynamic(
() => import(`tutorial/${route}.mdx`).catch(err => {
return () => <Error />
}),
{ loading: () => <Loader /> }
);
return <Article />
};
I should store the component in the variable after all, but I get the component itself dynamically using literal strings, and after that I return the component as tag (). Works fine now!

Related

How to use lazy loading in data-grid material-ui

When I import data-grid via lazy loading then the error came.
const DataGrid = lazy(async () => await import('#material-ui/data-grid'))
Please tell me whether I am importing correctly because when I import other material-ui components then that component works fine but for data-grid import it occurs an error.
DataGrid is not a default export, so try
const DataGrid = React.lazy(
() => import('#material-ui/data-grid').then(module => ({ default: module.DataGrid }))
);
React.lazy takes a function that must call a dynamic import(). This
must return a Promise which resolves to a module with a default export
containing a React component.
For me the below code works fine, based on answer from #Someone Special
import React, { lazy, Suspense } from 'react';
const loadable = (importFunc, { fallback = null } = { fallback: null }) => {
const LazyComponent = lazy(importFunc);
return props => (
<Suspense fallback={fallback}>
<LazyComponent {...props} />
</Suspense>
);
};
export default loadable;
import React, { Suspense } from 'react';
import { Loading } from 'dan-components';
import loadable from '../../utils/loadable';
const DataGrid = loadable(() =>
import('#material-ui/data-grid').then(module => {
return { default: module.DataGrid };
}),
);
const LazyDataGrid = props => {
return (
<Suspense fallback={<Loading />}>
<DataGrid {...props} />
</Suspense>
);
};
export default LazyDataGrid;

NextJS issue with server side rendering with react-d3-tree

To start off, I have looked at issue number 40 and 95 on the react-d3-tree github
I don't see anything on StackOverflow that would help me. I am trying to implement the parseJSON method so I can take my own JSON file from the project folder and then generate a tree diagram.
Let's start from what I did in the beginning. I copy pasted the example code which worked for 2 seconds before crashing. Reason? Server Side Rendering. Great, so then I find this from NextJS which allows me to disable SSR for some components. Hey, now the example code is working. Let's try the example code where they use external data! Nope, it can't find the parseJSON method. I have no idea what to do, can't find anything to fix this. I am trying to import this function that has some issue with SSR, but because it isn't a component I am not able to import it using dynamic, and I can't import normally because it causes a "window is not defined" error because of SSR.
The following are my main two files.
DynamicComponent.js [Version 1]
import dynamic from 'next/dynamic';
const Tree = dynamic(
() => import('react-d3-tree'),
{ ssr: false },
);
export default Tree;
DynamicComponent.js [Version 2]
import dynamic from 'next/dynamic';
export const Tree = dynamic(
() => import('react-d3-tree'),
{ ssr: false },
);
export const treeUtil = dynamic(
() => import('react-d3-tree/src/util'),
{ ssr: false },
);
Diagram/index.js
import React from 'react';
import { Tree, treeUtil } from '../DynamicComponent';
const myTreeData = require('../fakeData.json');
class Diagram extends React.PureComponent {
constructor() {
super();
this.state = {
data: undefined,
};
}
componentWillMount() {
treeUtil.parseJSON(myTreeData)
.then((data) => {
this.setState({ data });
});
}
render() {
return (
<div
id="treeWrapper"
style={{ width: '50em', height: '20em' }}
>
<Tree data={this.state.data} />
</div>
);
}
}
export default Diagram;
Error I Get with Version 1
ReferenceError: treeUtil is not defined
Error I Get with Version 2
TypeError: _DynamicComponent__WEBPACK_IMPORTED_MODULE_1__.treeUtil.parseJSON is not a function
Please StackOverflow, you're my only hope.
I ran into the same problem with Cytoscape, a similar library (but specifically for graph-network visualization). It took lots of trial and error, but the solution was:
import the component dynamically
remove the import JSON and inline it into a js file. For some stupid reason, that worked for me and was the magic fix. (how big was your JSON file?) Worse-case try copying & pasting into the component itself.
For your component try this:
// convert fakeData.json to fakeData.js
export default {...fake data here };
import React from 'react';
import dynamic from 'next/dynamic'
import myTreeData from 'fakeData';
const Tree = dynamic(
() => import('./dynamicComponent'),
{ ssr: false }
);
// you can also delineate a loading component;
// converted to hooks for '21
const Diagram = () => {
const [data,setData] = useState({});
useEffect(() => {
treeUtil.parseJSON(myTreeData)
.then((data) => {
setData(data);
})
},[treeUtil,myTreeData,setData]);
return (
<div
id="treeWrapper"
style={{ width: '50em', height: '20em' }}
>
<Tree data={data} />
</div>
);
}
export default Diagram;
I guess treeUtil is not a react component, so you can't use dynamic to import it. Just import it normally may be work.
import dynamic from 'next/dynamic';
export const Tree = dynamic(
() => import('react-d3-tree'),
{ ssr: false },
);
export { default as treeUtil } from 'react-d3-tree/src/util';

On click returns null instead of an object

It's really basic I guess. I'm trying to add onClick callback to my script & I believe I'm missing a value that would be responsible for finding the actual item.
Main script
import React from 'react';
import { CSVLink } from 'react-csv';
import { data } from 'constants/data';
import GetAppIcon from '#material-ui/icons/GetApp';
import PropTypes from 'prop-types';
const handleClick = (callback) => {
callback(callback);
};
const DownloadData = (props) => {
const { callback } = props;
return (
<>
<CSVLink
data={data}
onClick={() => handleClick(callback)}
>
<GetAppIcon />
</CSVLink>
</>
);
};
DownloadData.propTypes = {
callback: PropTypes.func.isRequired,
};
export default DownloadData;
Storybook code
import React from 'react';
import DownloadData from 'common/components/DownloadData';
import { data } from 'constants/data';
import { action } from '#storybook/addon-actions';
export default {
title: 'DownloadData',
component: DownloadData,
};
export const download = () => (
<DownloadData
data={data}
callback={action('icon-clicked')}
/>
);
So right now with this code on click in the storybook I'd get null and I'm looking for an object.
One of the potential issues I can see is that your handleClick function is stored as it is in-memory, when you import the component. That means you're keeping reference of something that doesn't exists and expects to use it when rendering the component with the callback prop.
Each instance of a component should have its own function. To fix it, move the function declaration inside the component. Like this:
const Foo = ({ callback }) => {
// handleClick needs to be inside here
const handleClick = callback => {
console.log("clicked");
callback(callback);
};
return <div onClick={() => handleClick(callback)}>Click me!</div>;
};
Check this example.
If this doesn't fix your problem, then there is something wrong with how you're implementing Storybook. Like a missing context.

props.history.push inside functional component renders doesn't render the component

I'm using the context API and inside my AuthContextProvider I have a function that I call and then changes the route;problem is it changes the route but does not render the component and I have no idea why. I believe if I converted my auth-context.js class to a class based one it would work.
I tried return Redirect to ="/ /> but that does not work.
I'm stumped.I'd love if someone could help out
auth-context.js
import React, { useState } from 'react';
import { withRouter, Redirect } from 'react-router-dom';
import axios from 'axios';
export const AuthContext = React.createContext({
isAuth: false,
login: () => {},
seedFirebase: () => {}
});
const AuthContextProvider = props => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
console.log(props);
const loginHandler = () => {
setIsAuthenticated(true);
props.history.push('/'); //this changes the route but does not render the component
};
//THIS FUNCTION SEEDS THE FIREBASE DATABASE
const seedDb = () => {
const data = {
items: 'Folders'
};
// axios.post('/items.json', data).then(resp => console.log(resp.data));
};
return (
<AuthContext.Provider
value={{
login: loginHandler,
isAuth: isAuthenticated,
seedFirebase: seedDb
}}
>
{props.children}
</AuthContext.Provider>
);
};
export default withRouter(AuthContextProvider);
One simple solution is that create a history.js file inside a helper folder in the source directory.
Add the following code inside the history.js file.
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
Then import the history.js file into the following file you need.
import history from '/helpers/history';
Then use history.push('/') to redirect to the home component.
Hope this helps!
Posting this incase anyone else has this peculiar issue. I solved this issue using the following logic: I check in the component that's calling the login function, if the isAuthenticated is true I redirect to the homepage.

Using React.lazy with TypeScript

I am trying to use React.lazy for code splitting in my TypeScript React app.
All I am doing is changing that line:
import {ScreensProductList} from "./screens/Products/List";
to this line:
const ScreensProductList = lazy(() => import('./screens/Products/List'));
But the import('./screens/Products/List') part triggers a TypeScript error, stating:
Type error: Type 'Promise<typeof import("/Users/johannesklauss/Documents/Development/ay-coding-challenge/src/screens/Products/List")>' is not assignable to type 'Promise<{ default: ComponentType<any>; }>'.
Property 'default' is missing in type 'typeof import("/Users/johannesklauss/Documents/Development/ay-coding-challenge/src/screens/Products/List")' but required in type '{ default: ComponentType<any>; }'.
I am not quite sure what I am supposed to do here to get it to work.
You should do export default class {...} from the ./screens/Products/list instead of export class ScreensProductList {...}.
Or, alternatively, you can do:
const ScreensProductList = lazy(() =>
import('./screens/Products/List')
.then(({ ScreensProductList }) => ({ default: ScreensProductList })),
);
One option is to add default export in "./screens/Products/List" like that
export default ScreensProductList;
Second is to change import code to
const ScreensProductList = React.lazy(() =>
import("./screens/Products/List").then((module) => ({
default: module.ScreensProductList,
}))
);
Or if you don't mind using an external library you could do:
import { lazily } from 'react-lazily';
const { ScreensProductList } = lazily(() => import('./screens/Products/List'));
Another solution would be:
1. Import using lazy
const ScreensProductList = lazy(() => import('./screens/Products/List'));
2. Set the type on the export
React hooks
import { FunctionComponent /*, FC */ } from 'react';
const List = () => (
return </>;
);
export default List as FunctionComponent; // as FC;
React class components
import { Component, Fragment, ComponentType } from 'react';
class List extends Component {
render() {
return <Fragment />;
}
}
export default List as ComponentType;
This is the proper syntax. It works also in the Webstorm IDE (the other syntaxes shown here are still showing a warning)
const ScreensProductList = React.lazy(() => import("./screens/Products/List").then(({default : ScreensProductList}) => ({default: ScreensProductList})));
const LazyCart = React.lazy(async () => ({ default: (await import('../Components/market/LazyCart')).LazyCart }))
You can create an index.ts file where you can export all your components like in this eg. :
export {default as YourComponentName} from "./YourComponentName";
After that you can use React.lazy:
React.lazy(() => import("../components/folder-name-where-the-index-file-is-created").then(({YourComponentName}) => ({default: YourComponentName})))
Just to expand on this answer. This also works for the dynamic imports.
const Navbar = dynamic(() => import('../components/Navbar'), {
ssr: false,
});
Where Navbar is a default exported component.
const Navbar = () => ()
export default Navbar

Resources