I'm trying to create a PDF viewer on my nextjs static page but i dont know how! hope you guys can help me solving this error or showing one other way to do this. (I'm new to Next.js) I was following this working example here
index.js
import SiteLayout from "../../components/SiteLayout";
import React from 'react';
import ReactDOM from "react-dom";
import Viewer from "../resume/viewer.js";
export default function Resume({ resume }) {
return (
<div>
<Viewer />
</div>
);
}
viewer.js
import React, { useRef, useEffect } from "react";
import WebViewer from "#pdftron/webviewer";
const Viewer = (props) => {
const viewer = useRef(null);
useEffect(() => {
WebViewer({
path: "/lib",
initialDoc: "/pdf/GustavoMorilla.pdf"
}, viewer.current);
}, []);
return (
<div className="Viewer">
<div className="header">React sample</div>
<div className="webviewer" ref={viewer}></div>
</div>
);
};
export default Viewer;
WebViewer needs the window object to work.
In nextjs there is a prerender phase server side and on that side window is not defined.
To solve your problem you can use next/dynamic in viewer.js
import dynamic from 'next/dynamic';
const WebViewer = dynamic(() => import('#pdftron/webviewer'), {ssr: false});
Alternatively you can import Viewer in index.js with dynamic import
import dynamic from 'next/dynamic';
const Viewer = dynamic(() => import('../resume/viewer.js'), {ssr: false});
When you import #pdftron/webviewer some code is running even though you haven't called the WebViewer function. The useEffect callback doesn't run in SSR. You can use Dynamic Imports there to import the module:
useEffect(() => {
import('#pdftron/webviewer')
.then(WebViewer) => {
WebViewer({
path: "/lib",
initialDoc: "/pdf/GustavoMorilla.pdf"
}, viewer.current);
})
}, []);
I kept getting 'window is not defined', the only solution that worked for me was following the NextJs way for importing external libraries dynamically:
useEffect(() => {
const loadWebViewer = async () => {
const WebViewer = (await import('#pdftron/webviewer')).default;
if (viewer.current) {
WebViewer(
{
path: '/lib',
initialDoc: `/api/getPdf/${encodeURIComponent(file)}/`,
disabledElements: [
'viewControlsButton',
'viewControlsOverlay',
'toolsOverlay',
'ribbonsDropdown',
'selectToolButton',
'panToolButton',
'leftPanelButton',
'toggleNotesButton',
'toolsHeader',
],
},
viewer.current
);
}
};
loadWebViewer();
}, []);
Related
I am trying to import grapesjs in a nextjs project and I get the error TypeError: Cannot read properties of null (reading 'querySelector')
This seems to be that grapesjs wants to target the "#gjs" container referenced through it's id in order to load the editor inside, and it cannot find the corresponding element as the DOM is not rendered yet.
This is the code in my Editor.js component
import React, { useEffect, useState } from "react";
import grapesjs from "grapesjs";
const Editor = () => {
const [editor, setEditor] = useState(null);
useEffect(() => {
const editor = grapesjs.init({
container: "#gjs",
});
setEditor(editor);
}, []);
return (
<>
<div id="gjs"></div>
</>
);
};
export default Editor;
This is how I try to render the Editor component in the corresponding page for "/editor" route
import { getSession } from "next-auth/react";
import "../i18n/config/config";
import "grapesjs/dist/css/grapes.min.css";
import dynamic from "next/dynamic";
import Editor from "../features/Editor/components/Editor";
// const EditorComponent = dynamic(
// () => import("../features/Editor/components/Editor"),
// {
// ssr: false,
// }
// );
export default function Home() {
return <Editor />;
}
export async function getServerSideProps(context) {
const session = await getSession(context);
return {
props: {
session,
},
};
}
As you can see from the commented section, I have tried to dynamically import the editor component as I have seen this as a fix for alot of issues where an element could not be found because the DOM was not yet loaded, but it does not seem to work for me.
Edit: Adding <script src="//unpkg.com/grapesjs"></script> before the component to be rendered either in Editor.js component or in editor.js page while removing the grapejs import statement from Editor.js component import grapesjs from "grapesjs" allows the application to run but I still get the error in the console.
import { getSession } from "next-auth/react";
import "../i18n/config/config";
import "grapesjs/dist/css/grapes.min.css";
import dynamic from "next/dynamic";
import Editor from "../features/Editor/components/Editor";
// const EditorComponent = dynamic(
// () => import("../features/Editor/components/Editor"),
// {
// ssr: false,
// }
// );
export default function Home() {
return (
<>
<script src="//unpkg.com/grapesjs"></script>
<Editor />
</>
);
}
export async function getServerSideProps(context) {
const session = await getSession(context);
return {
props: {
session,
},
};
}
Edit: Screenshot of the produced error
Because it is wrong type in useState. U can try this.
const Editor = () => {
const [editor, setEditor] = useState<Record<string, any> | null>(null);
useEffect(() => {
const editor = grapesjs.init({
container: "#gjs",
});
setEditor(editor);
}, []);
return (
<>
<div id="gjs"></div>
</>
);
};
export default Editor;
Search the library sources..
I'm trying to integrate CleverTap into my Next.js app. Followed the documentation Web SDK Quick Start Guide but facing issue:
Server Error ReferenceError: window is not defined in Next.js
_app.tsx
import React, { useEffect, useState } from "react";
import type { AppProps } from "next/app";
import { appWithTranslation } from "next-i18next";
import { Hydrate, QueryClient, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import nextI18NextConfig from "../next-i18next.config.js";
import "tailwindcss/tailwind.css";
import "styles/globals.scss";
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import { useRouter } from "next/router";
import SvgPageLoading from "components/color-icons/PageLoading";
// import { PageLoading } from "components/color-icons/";
import { DefaultSeo } from 'next-seo';
import SEO from 'next-seo.config';
import {cleverTap} from "utils/cleverTapHelper";
cleverTap.initialize('TEST-61c-a12');
function MyApp({ Component, pageProps }: AppProps) {
const [queryClient] = React.useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
staleTime: Infinity,
},
},
})
);
const router = useRouter();
const [isAnimating, setIsAnimating] = useState(false);
useEffect(() => {
const handleStart = () => {
setIsAnimating(true);
};
const handleStop = () => {
setIsAnimating(false);
};
router.events.on("routeChangeStart", handleStart);
router.events.on("routeChangeComplete", handleStop);
router.events.on("routeChangeError", handleStop);
return () => {
router.events.off("routeChangeStart", handleStart);
router.events.off("routeChangeComplete", handleStop);
router.events.off("routeChangeError", handleStop);
};
}, [router]);
return (
<QueryClientProvider client={queryClient}>
<Hydrate state={pageProps.dehydratedState}>
<DefaultSeo {...SEO} />
<Component {...pageProps} />
{isAnimating && (
<div className="fixed top-0 left-0 flex items-center justify-center w-screen h-screen overflow-visible bg-white bg-opacity-80 z-overlay top-z-index">
<SvgPageLoading />
</div>
)}
<ReactQueryDevtools initialIsOpen={false} />
</Hydrate>
</QueryClientProvider>
);
}
export default appWithTranslation(MyApp, nextI18NextConfig);
cleverTapHelper.ts
export const cleverTap = {
initialize: function (accountId) {
console.log('I see initialize req')
window.clevertap = {event: [], profile: [], account: [], onUserLogin: [], notifications: []};
window.clevertap.account.push({'id': accountId});
(function () {
var wzrk = document.createElement('script');
wzrk.type = 'text/javascript';
wzrk.async = true;
wzrk.src = ('https:' == document.location.protocol ? 'https://d2r1yp2w7bby2u.cloudfront.net' : 'http://static.clevertap.com') + '/js/a.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(wzrk, s);
})();
},
event: function (name, payload = {}) {
console.log('I see event req')
if (payload) {
window.clevertap.event.push(name, payload);
} else {
window.clevertap.event.push(name);
}
},
profile: function (payload) {
console.log('I see profile req')
window.clevertap.profile.push(payload);
},
logout: function () {
console.log('I see logout req')
window.clevertap.logout();
}
};
cleverTap.d.ts
declare global {
interface Window {
clevertap: any;
}
}
export {};
Window object should not be undefined but getting undefined! What's going on?
This is because NextJS is trying to execute that function on the server because it uses SSR, and window is a browser object. Since the window object is available only in the browser (client-side), the server is unable to identify the window object, hence getting undefined. In order to fix this, you should make sure that any functions/components that contain client-side related code be executed only on the browser or client-side. One way is using hooks such as useEffect that run only after the component is mounted. Another way is to use lazy loading which pretty much does the same thing.
Using useEffect hook.
In your _app.tsx component, add a new useEffect hook and move the initialization code into the newly created useEffect function.
useEffect(()=>{
cleverTap.initialize('TEST-61c-a12');
},[])
Using lazy loading. (Dynamic import)
Instead of directly importing the function, import it dynamically and set server-side rendering to false:
import dynamic from 'next/dynamic'
const cleverTap = dynamic(()=>{
return import("utils/cleverTapHelper")
},
{ssr:false}
)
cleverTap.initialize('TEST-61c-a12');
For TS folks struggling out there with clevertap and nextjs, install sdk and types:
npm i -S clevertap-web-sdk #types/clevertap-web-sdk
then, async import while initializing:
import CleverTap from 'clevertap-web-sdk/clevertap';
// ^^ this only imports types
let clevertap: CleverTap;
export const initClevertap = async (clevertapAccountID: string, region: string): Promise<void> => {
clevertap = (await import('clevertap-web-sdk')).default;
clevertap.init(clevertapAccountID, region);
clevertap.privacy.push({ optOut: false });
clevertap.privacy.push({ useIP: true });
clevertap.setLogLevel(0);
clevertap.spa = true;
};
PS: dynamic import didn't worked for me, looks like it's only for components and not libs
I'm trying to make a component that displays some simple markdown, but just can't seem to get the markdown to display on the page. It's not even creating a component for it in the HTML. This component is displaying properly and the 'Test' is showing up but not the markdown. I tried reinstalling my node_modules and that didn't work, any tips?
import React, {useState, useEffect} from 'react'
import axios from 'axios';
import {API_DEV_URL, API_PROD_URL} from '../env';
import Markdown from 'react-markdown';
const ROOT = (process.env.REACT_APP_ENV === 'production')? API_PROD_URL : API_DEV_URL;
function Hello() {
const [test, setTest] = useState("");
console.log(process.env.REACT_APP_ENV);
useEffect(() => {
axios.get(ROOT+'/test')
.then(res => setTest(res.data))
.catch(err => console.log(err));
}, []);
const input = '# This is a header\n\nAnd this is a paragraph'
return (
<header>
<h1>Test</h1>
<Markdown source={input}/>
</header>
);
}
export default Hello;
The markdown that needs to be parsed should be provided as a children. react-markdown does have make use of source prop in there API.
Change from
<Markdown source={input}/>
to
<Markdown>{input}</Markdown>
I'm getting data via an Axios GET request from a local API and trying to save the data in a Context Object.
The GET request works properly when I run it outside the Context Provider function. But when I put it within a UseEffect function with no dependencies - ie. useEffect( () => /* do something*/, [] )the useEffect hook never fires.
Code here:
import React, { createContext, useReducer, useEffect } from 'react';
import rootReducer from "./reducers";
import axios from 'axios';
import { GET_ITEMS } from "./reducers/actions/types";
export const ItemsContext = createContext();
function ItemsContextProvider(props) {
const [items, dispatch] = useReducer(rootReducer, []);
console.log('this logs');
useEffect(() => {
console.log('this does not');
axios.get('http://localhost:27015/api/items')
.then(data => dispatch({type: GET_ITEMS, payload: data}))
}, [])
return (
<ItemsContext.Provider value={{items, dispatch}}>
{ props.children }
</ItemsContext.Provider>
);
}
export default ItemsContextProvider;
I never see 'this does not' in the console (double and triple checked). I'm trying to initialise the context to an empty value at first, make the GET request on first render, and then update the context value.
I'd really appreciate any help on what I'm doing wrong.
EDIT - Where Context Provider is being rendered
import React from 'react';
import AppNavbar from "./Components/AppNavbar";
import ShoppingList from "./Components/ShoppingList";
import ItemModal from "./Components/ItemModal";
//IMPORTED HERE (I've checked the import directory is correct)
import ItemsContextProvider from "./ItemsContext";
import { Container } from "reactstrap"
import "bootstrap/dist/css/bootstrap.min.css";
import './App.css';
function App() {
return (
<div className="App">
<ItemsContextProvider> //RENDERED HERE
<AppNavbar />
<Container>
<ItemModal />
<ShoppingList /> //CONSUMED HERE
</Container>
</ItemsContextProvider>
</div>
);
}
export default App;
I have it being consumed in another file that has the following snippet:
const {items, dispatch} = useContext(ItemsContext);
console.log(items, dispatch);
I see console logs showing the empty array I initialised outside the useEffect function in the Context Provider and also a reference to the dispatch function.
I had the same problem for quite a while and stumbled upon this thred which did not offer a solution. In my case the data coming from my context did not update after logging in.
I solved it by triggering a rerender after route change by passing in the url as a dependency of the effect. Note that this will always trigger your effect when moving to another page which might or might not be appropriate for your usecase.
In next.js we get access to the pathname by using useRouter. Depending on the framework you use you can adjust your solution. It would look something like this:
import React, { createContext, useReducer, useEffect } from 'react';
import rootReducer from "./reducers";
import axios from 'axios';
import { GET_ITEMS } from "./reducers/actions/types";
import { useRouter } from "next/router"; // Import the router
export const ItemsContext = createContext();
function ItemsContextProvider(props) {
const [items, dispatch] = useReducer(rootReducer, []);
const router = useRouter(); // using the router
console.log('this logs');
useEffect(() => {
console.log('this does not');
axios.get('http://localhost:27015/api/items')
.then(data => dispatch({type: GET_ITEMS, payload: data}))
}, [router.pathname]) // trigger useEffect on page change
return (
<ItemsContext.Provider value={{items, dispatch}}>
{ props.children }
</ItemsContext.Provider>
);
}
export default ItemsContextProvider;
I hope this helps anyone in the future!
<ItemsContextProvider /> is not being rendered.
Make sure is being consumed and rendered by another jsx parent element.
hi everyone I am testing my react application using jest. While testing a component I found that a test breaks unexpectedly throwing error as
Method “props” is only meant to be run on a single node. 0 found instead.
test file
import React from 'react';
import {shallow} from 'enzyme';
import {AddLibraryItem} from '../../components/AddLibraryItem';
import libraryItems from '../fixtures/libraryItems';
let addLibraryItem, history, wrapper;
beforeEach(() => {
addLibraryItem = jest.fn();
history = {push: jest.fn()};
wrapper = shallow(<AddLibraryItem addLibraryItem={addLibraryItem} history={history}/>);
})
test('should execute on submit button successfully', () => {
console.log(wrapper);
wrapper.find('LibraryItemForm').prop('onSubmit')(libraryItems[0]);
expect(addLibraryItem).toHaveBeenLastCalledWith(libraryItems[0]);
expect(history.push).toHaveBeenLastCalledWith("/");
});
Component
import React from 'react';
import {connect} from 'react-redux';
import LibraryItemForm from './LibraryItemForm';
import {addLibraryItem} from '../actions/libraryA';
export class AddLibraryItem extends React.Component {
onSubmit = (libraryItem) => {
this.props.addLibraryItem(libraryItem);
this.props.history.push('/');
}
render () {
return (
<div>
<LibraryItemForm onSubmit={this.onSubmit} />
</div>
);
}
}
const mapDispatchToProps = (dispatch) => {
return {
addLibraryItem: (libraryItem) => dispatch(addLibraryItem(libraryItem))
}
}
const ConnectedAddLibraryItem = connect(undefined, mapDispatchToProps)(AddLibraryItem);
export default ConnectedAddLibraryItem;
The piece of test was earlier working very fine and test of 'LibraryItemForm' is also working fine and also rendering perfectly.
I am not getting what is wrong with it.
Is there any fix of it?
You probably forgot to dive():
wrapper.find(LibraryItemForm).dive().prop('onSubmit')(libraryItems[0]);
Enzyme documentation here.