Ace: Create a custom mode in NextJS - javascript

I am trying to use ace to create an sql editor, but I need a unique highlight. So, I need to create a custom mode that inherits from the existing sql mode.
The issue is, because ace uses window, and nextjs is ssr, I am unable to create a custom mode using ace's tutorials as I get the window is not defined error in the .ts file.
I can get around this error in the .tsx file by importing it with next/dynamic and disabling ssr for that component, but for the mode I am stumped.
SqlMode.js (it's not .ts because ace typing just doesn't work for me)
import ace from 'ace-builds/src-noconflict/ace';
import 'ace-builds/src-noconflict/mode-sql'
export class SqlHighlightRules extends window.ace.acequire('ace/mode/text_highlight_rules').TextHighlightRules {
constructor() {
super();
this.$rules.start.unshift({
token: 'text-orange-main',
regex: '{{*.}}',
})
}
}
export class SqlMode extends window.ace.acequire('ace/mode/sql').Mode {
constructor() {
super()
this.HighlightRules = SqlHighlightRules;
}
}
The compilation fails in this file, as it uses window, which is undefined.
Editor.tsx
import { useEffect, useRef, useState } from 'react';
import AceEditor from 'react-ace';
import 'ace-builds/src-noconflict/ace';
import 'ace-builds/src-noconflict/theme-tomorrow';
import { SqlMode } from './config/SqlMode';
import 'ace-builds/src-noconflict/ext-language_tools';
const styles = { borderRadius: 8 };
export default function Editor() {
const editor = useRef<AceEditor>();
const [code, setCode] = useState<string>('');
useEffect(() => {
const mode = new SqlMode();
//#ts-ignore
editor.current.editor.getSession().setMode(mode);
}, []);
return (
<AceEditor
ref={editor}
theme="tomorrow"
value={code}
onChange={setCode}
enableBasicAutocompletion
enableLiveAutocompletion
style={styles}
/>
);
}
This file works if used through next/dynamic with { ssr: false } and without the custom mode. But as soon as I use the custom mode, errors.
sql.page.tsx
import dynamic from 'next/dynamic';
const Editor = dynamic(() => import('../components/editor/Editor'), { ssr: false });
export default function SqlEditor() {
return (
<div className="w-full h-full flex justify-center items-center">
<Editor />
</div>
);
}
I would like to be able to create a custom highlight for the sql mode, while working with nextjs. If there is a solution I am not aware of, I'd be happy to learn.
Alternatively if there is another FE editor I could use that does largely the same thing as ace, that could also be useful.
Thank you.

Related

Editor is uneditable and options appear vertically

I am trying to use draft js to present a wysiwyg editor.
When I load the component, I am unable to edit anything and the options are coming up vertically.
Expecting it to appear horizontally. What am I doing wrong?
This is how it looks currently.
Implementation.
import React from 'react';
import { EditorState, convertToRaw } from 'draft-js';
import draftToHtml from 'draftjs-to-html';
import dynamic from 'next/dynamic'
import { EditorProps } from 'react-draft-wysiwyg'
const TextEditor = () => {
// getting window undefined error thus importing this dynamically
const Editor = dynamic<EditorProps>(
() => import('react-draft-wysiwyg').then((mod) => mod.Editor),
{ ssr: false }
)
const [editorState, setEditorState] = React.useState(
EditorState.createEmpty()
);
return (
<div>
<Editor
editorState={editorState}
wrapperClassName="wrapper"
editorClassName="editor"
onEditorStateChange={() => setEditorState(editorState)}
/>
<textarea
disabled
value={draftToHtml(convertToRaw(editorState.getCurrentContent()))}
/>
</div>
);
}
export default TextEditor
One thing is wrong for sure, you wrote:
onEditorStateChange={() => setEditorState(editorState)}
It should be:
onEditorStateChange={(newEditorState) => setEditorState(newEditorState)}
// or shorter form:
onEditorStateChange={setEditorState}
Now regarding the style, two thing to look into.
double check that you have included the css somewhere, like import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'; but check against your bundler config, not sure about the path on your machine.
It looks like you’re trying to customize the style with wrapperClassName="wrapper" editorClassName="editor". Try remove them for now and see if them interfere. I suspect this is part of the cause.

Is there a way to import a function using Next.js dynamic import? react-component-export-image issues with Next.js ssr

I was getting the 'window is not defined' error when importing react-component-export-image so I used a dynamic import to get around that. I don't get that error anymore but now I get 'exportComponentAsPNG(componentRef) is not a function'. Is there a better way to deal with the 'window is not defined' error or a way to use the function I am importing dynamically? If not, is there a different npm library that works to generate an image from a react component?
import React, { useRef } from 'react'
// import { exportComponentAsPNG } from 'react-component-export-image' *This gave window not defined error so I used dynamic import*
import dynamic from 'next/dynamic'
import ProductCard from '../ProductCard/ProductCard.component'
import Button from '../Button/Button.component'
const { exportComponentAsPNG } = dynamic(
() => import('react-component-export-image'),
{
ssr: false
}
)
const Plaque = () => {
const componentRef = useRef()
// eslint-disable-next-line react/display-name
const ComponentToPrint = React.forwardRef((props, ref) => {
return (
<div ref={ref}>
<ProductCard />
</div>
)
})
return (
<ComponentToPrint ref={componentRef} />
<button onClick={() => exportComponentAsPNG(componentRef)}> // "Error: exportComponentAsPNG is not a function"
Export As PNG
</button>
)
}
export default Plaque
next/dynamic is used to dynamically import React components, not regular JavaScript functions or libraries.
For that, you can use a regular dynamic import on exportComponentAsPNG inside the onClick callback.
<button onClick={async () => {
const { exportComponentAsPNG } = await import('react-component-export-image')
exportComponentAsPNG(componentRef)
}}>
The exportComponentAsPNG function needs access to window which is undefined with server side rendering. I was able to fix the issue by dynamically importing the Plaque component that used exportComponentAsPNG to the page where it is called with sever side rendering set to 'false'.
import dynamic from 'next/dynamic'
const Plaque = dynamic(() => import('../compnonents/Plaque'), {
ssr: false
})
const Page = () => {
return <Plaque />
}
export default Page
Now that the component is no longer using SSR I was able to import and use the function normally.
import { exportComponentAsPNG } from 'react-component-export-image'
Here you can find the documentation for the library: https://www.npmjs.com/package/react-component-export-image

Storybook not showing styles

I have a dialog component which is using the Primereact dialog internally. When I make a storybook for the same, the custom css for button is being imported as it is imported inside dialog.jsx. But the default css of Primereact dialog is not loading and reflecting in the storybook. Although it is being loaded in my React app.
dialogComp.jsx
import { Dialog } from "primereact/dialog";
const DialogComp = (props) => {
return (
<Dialog
className="dialog-modal"
header={props.header}
visible={true}
>
{props.children}
</Dialog>
);
};
export default DialogModal;
dialog.storybook.js
import React from "react";
import DialogModal from "./dialogComp";
import { addDecorator, addParameters } from "#storybook/react";
import { Store, withState } from "#sambego/storybook-state";
import { store } from "./../../utils/storyStore";
const DialogModalComp = (props) => {
return [
<div>
<DialogModal
header="Dialog Modal"
displayModal={true}
>
Modal content
</DialogModal>
</div>,
];
};
addDecorator(withState());
addParameters({
state: {
store,
},
});
export default {
title: "dialog",
};
export const DialogModalComponent = () => DialogModalComp;
storybook---main.js
module.exports = {
"stories": [
"../src/**/*.stories.mdx",
"../src/**/*.stories.#(js|jsx|ts|tsx)"
],
"addons": [
"#storybook/addon-links",
"#storybook/addon-essentials",
"#storybook/preset-create-react-app"
]
}
Am I missing something in the configuration?
You'll need to import any styles you use in App.js globally in Storybook, by importing them in .storybook/preview.js (create the file if it doesn't already exist).
Every component in React is self contained - your DialogModal component won't get styled because in Storybook it is not being rendered within your App component (where you're importing your styles).
To simulate your app when using Storybook, you import the css in a preview.js file.
Docs:
To control the way stories are rendered and add global decorators and
parameters, create a .storybook/preview.js file. This is loaded in the
Canvas tab, the “preview” iframe that renders your components in
isolation. Use preview.js for global code (such as CSS imports or
JavaScript mocks) that applies to all stories.
TL;DR
import your styles in .storybook/preview.js
import "../src/index.css";
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
};
If you use storybook and emotion, and if you implement Global styles or Theming, you may add a decorator into the .storybook/preview.js like this:
I'm using Create React App, therefore I'm using jsxImportSource
/** #jsxImportSource #emotion/react */
import { Global } from '#emotion/react'
import { GlobalStyles } from '../src/styles'
const withGlobalProvider = (Story) => (
<>
<Global styles={GlobalStyles} />
<Story />
</>
)
export const decorators = [withGlobalProvider]
You may find more information on: https://storybook.js.org/docs/react/essentials/toolbars-and-globals#global-types-and-the-toolbar-annotation

usePreventScroll causes useLayoutEffect warning in Nextjs

I'm learning Next.js and I'm trying to integrate the #react-aria/overlays package in my project. I have a layout component, where I'm simply invoking the usePreventScroll method like this:
usePreventScroll({
isDisabled: true
});
This layout component is used in my _app.js.
import { useEffect } from 'react'
import { useRouter } from 'next/router'
import * as gtag from '../lib/gtag'
import 'styles/vendor.scss';
import 'styles/globals.scss';
import Layout from 'components/layout';
import { SSRProvider } from '#react-aria/ssr';
const App = ({ Component, pageProps }) => {
return (
<SSRProvider>
<Layout>
<Component {...pageProps} />
</Layout>
</SSRProvider>
)
}
export default App;
When going to my browser and loading a page, it gives me the following error:
Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://reactjs.org/link/uselayouteffect-ssr for common fixes.
at Layout (/home/bas/projects/test-website/build/server/pages/_app.js:718:3)
at div
at $c5f9596976ab8bd94c5879001549a3e$var$OverlayContainerDOM (/home/bas/projects/test-website/node_modules/#react-aria/overlays/dist/main.js:864:7)
at ModalProvider (/home/bas/projects/test-website/node_modules/#react-aria/overlays/dist/main.js:810:5)
at OverlayProvider
at SSRProvider (/home/bas/projects/test-website/node_modules/#react-aria/ssr/dist/main.js:33:13)
at UIContextProvider (/home/bas/projects/test-website/build/server/pages/_app.js:1144:74)
at ManagedUIContext (/home/bas/projects/test-website/build/server/pages/_app.js:1105:3)
at App (/home/bas/projects/test-website/build/server/pages/_app.js:5171:3)
at AppContainer (/home/bas/projects/test-website/node_modules/next/dist/next-server/server/render.js:23:748)
What's the problem here and how would I be able to solve it?
I tried wrapping the the Layout component in the packages <SSRProvider>.
You can dynamically load the component and disable SSR:
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
function Home() {
return (
<div>
<Header />
<DynamicComponentWithNoSSR />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home
The code example has been taken from the NextJS docs. If that's not your thing, you can call the hook or render the component as long as processs.browser is true.
Next js is computes your 1st page on server. so it does not understand browser scroll or localstorage or other browser api.
you can add a check in your code block if window object is present or execution is running in server and then execute usePreventDefault.
import {useIsSSR} from '#react-aria/ssr';
function Layout() {
let isSSR = useIsSSR();
useEffect(() => {
!isSSR && usePreventScroll({ ... })
}, [isSSR])
}

Decorator feature not working (unexpected token)

Just tried to use decorators in React:
import React from 'react';
import Fade from './Transitions/Fade'
import withVisible from './withVisible'
#withVisible()
const App = props =>
<Fade visible={props.visible} duration={500}>
Hello
</Fade>
export default App
If I use the common way ( withVisible()(App) ) then it's working properly.
(My guess is that NodeJS can't compile my code with the # ) [Syntax error: Unexpected token (#) ]
import React from 'react'
const withVisible = () => Component =>
class WithVisible extends React.Component {
state = {
visible: true
}
render() {
return (
<Component visible={this.state.visible} {...this.props}/>
)
}
}
export default withVisible
Probably your .babelrc do not have added decorator plugin.
Try this: https://babeljs.io/docs/plugins/transform-decorators
You need transform-decorators-legacy babel plugin to make this syntax work.

Categories

Resources