Monaco editor intellisense font sizing wrong making it unusable - reactjs

I have the Monaco editor embedded in a React component. For some reason the intellisense font sizes are all hosed up. Anyone have any ideas? I have changed a bunch of settings to try and fix things but nothing I have tried is having any impact.
import { useRef } from 'react'
import MonacoEditor from '#monaco-editor/react'
import codeShift from 'jscodeshift'
import Highlighter from 'monaco-jsx-highlighter'
import './jsx-syntax.css'
const CodeEditor = ({ height, initialValue, language, onChange, readOnly }) => {
const editorRef = useRef()
const onEditorDidMount = (getValue, editor) => {
editorRef.current = editor
editor.onDidChangeModelContent(() => (onChange) ? onChange(getValue()) : {})
editor.getModel()?.updateOptions({ tabSize: 4 })
const highlighter = new Highlighter(window.monaco, codeShift, editor);
highlighter.highLightOnDidChangeModelContent(
() => {}, () => {}, undefined, () => {}
);
}
const options = {
minimap: {enabled: false},
//showUnused: false,
lineNumbersMinChars: 3,
//fontSize: 13,
scrollBeyondLastLine: false,
automaticLayout: true,
readOnly
}
return (
<div className="editor-wrapper">
<MonacoEditor theme="dark" language={language ?? 'javascript'} height={(height ?? 400) + 'px'} value={initialValue ?? ''}
editorDidMount={onEditorDidMount} options={options}
/>
</div>
);
};
export default CodeEditor;

After much playing around I can confirm that the reason for the distortions is that my project is using the Bulma css framework. It looks like Monaco does not properly namespace its CSS and that Bulma is able to change things that completely mess up the toolbars and intellisense popups from the editor. To fix it I am manually going through and figuring out which styles need to be loacally applied to the wrapper around Monaco to get it working again.
It turned out to be the padding added to the tabs style in Bulma since the Monaco intellisense apparently uses a embedded tab on each line:
.tabs a {
...
padding: 0.5em 1em;
...
}

There's a FAQ somewhere, which says that if Monaco has measured its fonts before you set new ones, values are computed wrongly.
That's why I call:
public componentDidMount(): void {
Monaco.remeasureFonts();
}

Related

react openlayers popup offsetWidth issue

I am trying to implement openlayers popup in react.
It is implemented in plain js here: https://openlayers.org/en/latest/examples/popup.html
Here is my code:
import {createRef, useEffect, useRef, useState} from 'react';
import './Map.css';
import Map from '../node_modules/ol/Map.js';
import Overlay from '../node_modules/ol/Overlay.js';
import TileLayer from '../node_modules/ol/layer/Tile.js';
import View from '../node_modules/ol/View.js';
import XYZ from '../node_modules/ol/source/XYZ.js';
import {toLonLat} from '../node_modules/ol/proj.js';
import {toStringHDMS} from '../node_modules/ol/coordinate.js';
const MapExample = () => {
const [popupContent, setPopupContent] = useState('');
const containerRef = createRef();
const contentRef = createRef();
const closerRef = createRef();
const key = 'CvOgKFhRDDHIDOwAPhLI';
const overlay = new Overlay({
element: containerRef.current,
autoPan: {
animation: {
duration: 250,
},
},
});
const handleCloser = () => {
overlay.setPosition(undefined);
closerRef.current.blur();
return false;
};
useEffect(() => {
const map = new Map({
layers: [
new TileLayer({
source: new XYZ({
url: 'https://api.maptiler.com/maps/streets/{z}/{x}/{y}.png?key=' + key,
tileSize: 512,
}),
}),
],
overlays: [overlay],
target: 'map',
view: new View({
center: [0, 0],
zoom: 2,
}),
});
map.on('singleclick', (evt) => {
const coordinate = evt.coordinate;
const hdms = toStringHDMS(toLonLat(coordinate));
setPopupContent('You clicked here: ' + hdms)
overlay.setPosition(coordinate);
});
}, [])
return (
<div>
<div id="map" className="map"></div>
<div ref={containerRef} className="ol-popup">
<a ref={closerRef} href="#" className="ol-popup-closer" onClick={handleCloser}></a>
<div ref={contentRef}>{popupContent}</div>
</div>
</div>
)
}
export default MapExample;
I've got an issue with offset width:
picture with an issue: Cannot read properties of null (reading 'offsetWidth') The problem is popup appears on the left bottom corner, not in the clicked place of the map.
How I understand this bug appears, because popup's properties are null. I tried to fix this bug, but could not find a working solution. Will be really grateful for any help )))
There are a number of React lifecycle issues in your code. Do not use createRef in functional components as it will create a new reference at every render - while the useEffect code will run only once. This means that the overlay will lose its connection to the map - especially since you create a new Overlay at each render.
Also I strongly advise you to not use useEffect to create Openlayers components - the reasons are very subtle - but the main problem is that your components won't be reusable or even rerendable.
As the author of rlayers I can tell you that proper wrapping of Openlayers components is not easy - you must manually manage the components' lifecycle. This works best with class-based components on which you should override all lifecycle methods.

nextjs react recoil persist values in local storage: initial page load in wrong state

I have the following code,
const Layout: React.FC<LayoutProps> = ({ children }) => {
const darkMode = useRecoilValue(darkModeAtom)
console.log('darkMode: ', darkMode)
return (
<div className={`max-w-6xl mx-auto my-2 ${darkMode ? 'dark' : ''}`}>
<Nav />
{children}
<style jsx global>{`
body {
background-color: ${darkMode ? '#12232e' : '#eefbfb'};
}
`}</style>
</div>
)
}
I am using recoil with recoil-persist.
So, when the darkMode value is true, the className should include a dark class, right? but it doesn't. I don't know what's wrong here. But it just doesn't work when I refresh for the first time, after that it works fine. I also tried with darkMode === true condition and it still doesn't work. You see the styled jsx, that works fine. That changes with the darkMode value and when I refresh it persists the data. But when I inspect I don't see the dark class in the first div. Also, when I console.log the darkMode value, I see true, but the dark class is not included.
Here's the sandbox link
Maybe it's a silly mistake, But I wasted a lot of time on this. So what am I doing wrong here?
The problem is that during SSR (server side rendering) there is no localStorage/Storage object available. So the resulted html coming from the server always has darkMode set to false. That's why you can see in cosole mismatched markup errors on hydration step.
I'd assume using some state that will always be false on the initial render (during hydration step) to match SSR'ed html but later will use actual darkMode value. Something like:
// themeStates.ts
import * as React from "react";
import { atom, useRecoilState } from "recoil";
import { recoilPersist } from "recoil-persist";
const { persistAtom } = recoilPersist();
export const darkModeAtom = atom<boolean>({
key: "darkMode",
default: false,
effects_UNSTABLE: [persistAtom]
});
export function useDarkMode() {
const [isInitial, setIsInitial] = React.useState(true);
const [darkModeStored, setDarkModeStored] = useRecoilState(darkModeAtom);
React.useEffect(() => {
setIsInitial(false);
}, []);
return [
isInitial === true ? false : darkModeStored,
setDarkModeStored
] as const;
}
And inside components use it like that:
// Layout.tsx
const [darkMode] = useDarkMode();
// Nav.tsx
const [darkMode, setDarkMode] = useDarkMode();
codesandbox link
Extending on #aleksxor solution, you can perform the useEffect once as follows.
First create an atom to handle the SSR completed state and a convenience function to set it.
import { atom, useSetRecoilState } from "recoil"
const ssrCompletedState = atom({
key: "SsrCompleted",
default: false,
})
export const useSsrComplectedState = () => {
const setSsrCompleted = useSetRecoilState(ssrCompletedState)
return () => setSsrCompleted(true)
}
Then in your code add the hook. Make sure it's an inner component to the Recoil provider.
const setSsrCompleted = useSsrComplectedState()
useEffect(setSsrCompleted, [setSsrCompleted])
Now create an atom effect to replace the recoil-persist persistAtom.
import { AtomEffect } from "recoil"
import { recoilPersist } from "recoil-persist"
const { persistAtom } = recoilPersist()
export const persistAtomEffect = <T>(param: Parameters<AtomEffect<T>>[0]) => {
param.getPromise(ssrCompletedState).then(() => persistAtom(param))
}
Now use this new function in your atom.
export const darkModeAtom = atom({
key: "darkMode",
default: false,
effects_UNSTABLE: [persistAtomEffect]
})

How to apply code highlights within markdown-to-jsx package in react?

I'm using the markdown-to-jsx package in order to render documentation content inside my react project.
This package provides a Markdown component, which accepts an options prop to override HTML elements's default style, and more.
const markdownOptions = {
wrapper: DocsContentWrapper,
forceWrapper: true,
overrides: {
h1: LeadTitle,
h2: SecondaryTitle,
h3: ThirdTitle,
p: Paragraph,
pre: CodeWrapper,
ol: ListWrapper,
li: ListItem,
},
};
<Markdown
options={MarkdownOptions}
>
{MockDoc}
</Markdown>
Now, the Markdown component accept a markdown, so I pass it a string which is formatted accoring to markdown rules.
It contains some code blocks, like the following, which I want to add colors to:
I have created a component using 'react-syntax-highlighter' package, and it looks like the following:
import React from 'react';
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
import { tomorrow } from "react-syntax-highlighter/dist/esm/styles/prism"
const SyntaxHighligher = ({ language, markdown }) => {
return (
<SyntaxHighlighter
language={language}
style={tomorrow}
>
{markdown}
</SyntaxHighlighter>
);
};
export default SyntaxHighligher;
And here comes the question - how can I integrate the two?
I was thinking that it would have made sense if the options object would accept such configuration, but looking at 'markdown-to-jsx' docs via their GitHub page, shows no option.
I have seen a package called 'react-markdown' that is able to accept my SyntaxHighligher component and to the task, but I want to apply the same functionality with 'markdown-to-jsx' package.
markdown-to-jsx generates code blocks as <pre><code>...</code></pre>, but we can't simply override code tag since inline code uses it as well. The README from markdown-to-jsx suggests that we can override pre > code somehow:
Some element mappings are a bit different from other libraries, in particular:
span: Used for inline text.
code: Used for inline code.
pre > code: Code blocks are a code element with a pre as its direct ancestor.
But based on my experiments and reading of the source code, I think the only way is to override pre and check for code in its children. For example:
import {Prism as SyntaxHighlighter} from 'react-syntax-highlighter';
import {materialDark as CodeStyle} from 'react-syntax-highlighter/dist/esm/styles/prism';
const CodeBlock = ({className, children}) => {
let lang = 'text'; // default monospaced text
if (className && className.startsWith('lang-')) {
lang = className.replace('lang-', '');
}
return (
<SyntaxHighlighter language={lang} style={CodeStyle}>
{children}
</SyntaxHighlighter>
);
}
// markdown-to-jsx uses <pre><code/></pre> for code blocks.
const PreBlock = ({children, ...rest}) => {
if ('type' in children && children ['type'] === 'code') {
return CodeBlock(children['props']);
}
return <pre {...rest}>{children}</pre>;
};
const YourComponent = () => {
return (
<Markdown
options={{
overrides: {
pre: PreBlock,
},
}}
>
{yourMarkdown}
</Markdown>
);
};
Unfortunately, I don't have a good solution if you want to override BOTH inline code and code blocks. When overriding both pre and code tags, the code tag generated by SyntaxHighlighter also gets overridden, so the inline code and code blocks are rendered identically.

Gatsby w/ Chakra UI ColorMode not working

I'm using Gatsby w/ Chakra UI and have an issue with either local storage or how the ColorMode is being accessed.
Here's my repo: https://github.com/RyanPinPB/pdm-gatsby
Live site: https://pearsondigitalmarketing.com
ColorMode and components are styled correctly on localhost, but in production the site has an issue when it renders the header after local storage has saved darkMode=true.
You can reproduce this issue by going to the live site, toggling dark mode, and refreshing. Or, if your browser has theme settings or depending on your OS (or time of day), it will show the issue at night, or if your browser/OS prefers dark mode.
What is weird, is that certain components receive the correct darkMode styling (colorings and background), but my logo, menu, and header background are not correct. Even tho all 3 of these are using colorMode styling conditionals as follows:
const bgColor = {
light: "rgba(255,255,255,.6)",
dark: "rgba(26, 32, 44, .6)",
}
const color = { light: "brand.400", dark: "brand.900" }
bg={bgColor[colorMode]}
color={color[colorMode]}
I added a console log to both index and header files to see if one of them is rendering before the colorMode is triggered/called from local storage, but both console logs print the correct colorMode state. I'm having a hard time figuring out why my header in darkmode has the wrong background color and color styling.
The issue is correct after any click of the "toggle button" in the header. It's just on the initial rendering of the page, if the page thinks it needs to be in DarkMode.
Any help is greatly appreciated.
ThemeContext.js:
import React from "react"
import { ColorModeProvider } from "#chakra-ui/core"
//dont have to use this file if we use Chakra UI
const defaultState = {
dark: false,
toggleDark: () => {},
}
const ThemeContext = React.createContext(defaultState)
// Getting dark mode information from OS!
// You need macOS Mojave + Safari Technology Preview Release 68 to test this currently.
const supportsDarkMode = () =>
window.matchMedia("(prefers-color-scheme: dark)").matches === true
class ThemeProvider extends React.Component {
state = {
dark: false,
}
toggleDark = () => {
console.log("ThemeContext.js: toggle dark/light mode")
let dark = !this.state.dark
localStorage.setItem("dark", JSON.stringify(dark))
this.setState({ dark })
}
componentDidMount() {
// Getting dark mode value from localStorage!
console.log("ThemeContext.js component did mount, dark: " + this.state.dark)
const lsDark = JSON.parse(localStorage.getItem("dark"))
if (lsDark) {
console.log("ThemeContext.js: lsDark: " + lsDark)
this.setState({ dark: lsDark })
} else if (supportsDarkMode()) {
console.log("ThemeContext.js: supports Dark Mode: true")
this.setState({ dark: true })
}
}
render() {
const { children } = this.props
const { dark } = this.state
return (
<ThemeContext.Provider
value={{
dark,
toggleDark: this.toggleDark,
}}
>
<ColorModeProvider>{children}</ColorModeProvider>
</ThemeContext.Provider>
)
}
}
export default ThemeContext
export { ThemeProvider }

gatsby + react hook masonry breaks on build because there is no window

I have a react hook set up to handle masonry on certain pages of my gatsby site. The problem is it references the window object, which does not exist on the server side gatsby build. I've read that the solution is to wrap useEffect with:
if (typeof window === 'undefined') {
}
however I just can't seem to wrap the right part of my masonry file. I've also read that using the above hack makes the server side rendering sort of pointless, not sure.
Could someone tell me where that if statement should go in my masonry file below? It's not a plugin, it's a hook in my utils folder. Using code from this tut. I've tried the if statement inside the useEffects, around the useEffects, around the whole eventListener, but no dice. Thank you!!
import React, { useEffect, useRef, useState } from "react"
import styled from "styled-components"
const useEventListener = (eventName, handler, element = window) => {
const savedHandler = useRef()
useEffect(() => {
savedHandler.current = handler
}, [handler])
useEffect(() => {
const isSupported = element && element.addEventListener
if (!isSupported) return
const eventListener = event => savedHandler.current(event)
element.addEventListener(eventName, eventListener)
return () => {
element.removeEventListener(eventName, eventListener)
}
}, [eventName, element])
}
const fillCols = (children, cols) => {
children.forEach((child, i) => cols[i % cols.length].push(child))
}
export default function Masonry({ children, gap, minWidth = 500, ...rest }) {
const ref = useRef()
const [numCols, setNumCols] = useState(3)
const cols = [...Array(numCols)].map(() => [])
fillCols(children, cols)
const resizeHandler = () =>
setNumCols(Math.ceil(ref.current.offsetWidth / minWidth))
useEffect(resizeHandler, [])
useEventListener(`resize`, resizeHandler)
const MasonryDiv = styled.div`
margin: 1rem auto;
display: grid;
grid-auto-flow: column;
grid-gap: 1rem;
`
const Col = styled.div`
display: grid;
grid-gap: 1rem;
`
return (
<MasonryDiv ref={ref} gap={gap} {...rest}>
{[...Array(numCols)].map((_, index) => (
<Col key={index} gap={gap}>
{cols[index]}
</Col>
))}
</MasonryDiv>
)
}
In your gatsby-node.js add the following snippet:
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
if (stage === "build-html") {
actions.setWebpackConfig({
module: {
rules: [
{
test: /masonry/,
use: loaders.null(),
},
],
},
})
}
}
Note: use /masonry/ library path from your node_modules.
From Gatsby documentation about debugging HTML builds:
Errors while building static HTML files generally happen for one of
the following reasons:
Some of your code references “browser globals” like window or
document. If this is your problem you should see an error above like
“window is not defined”. To fix this, find the offending code and
either a) check before calling the code if window is defined so the
code doesn’t run while Gatsby is building (see code sample below) or
b) if the code is in the render function of a React.js component, move
that code into a componentDidMount lifecycle or into a useEffect hook,
which ensures the code doesn’t run unless it’s in the browser.
Alternatively, you can wrap your import your Masonry hook usage inside this statement:
if (typeof window !== `undefined`) {
const module = require("module")
}
Note the !== comparison, not the === like the one you've provided.

Resources