How to style a map container with CSS using ReactJS - css

My map is implemented using MapLibre GL, but this issue also happens with Google Maps API or Mapbox GL.
I have the following ReactJS component:
import React, { useRef, useEffect } from "react";
import maplibregl from "maplibre-gl";
import map_style from "./style/style.json";
export default function Map() {
const mapContainer = useRef(null);
const map = useRef(null);
useEffect(() => {
if (map.current) return; //stops map from intializing more than once
map.current = new maplibregl.Map({
container: mapContainer.current,
style: map_style,
center: [12, 26],
});
});
return (
<div
ref={mapContainer}
className="my-map"
style={{ height: 500, width: 500 }}
/>
);
}
The above code renders the map, but I do not want a fixed height and width. I need to apply different styles depending on parent DOM elements, screen sizes, etc.
Problem is, right now if I assign a width and height in my CSS file, it's not being applied to the map. On the other hand, if I remove or change the syntax of style={{ height: 500, width: 500 }} in my ReactJS component, the map fails to display.
How can I style with CSS in this scenario?

The map library needs to read the container dimensions to compute the inner view.
As long as you provide explicit size, possibly through className, you should be fine.
Most mapping libraries also offer some API to request a re-read of the container dimensions, so that the view can be adjusted when the container is revealed or changes size (possibly for responsiveness). In the case of MapLibre GL JS, see resize() map method:
Resizes the map according to the dimensions of its container element.
Checks if the map container size changed and updates the map if it has changed. This method must be called after the map's container is resized programmatically or when the map is shown after being initially hidden with CSS.

A solution to get the map to render according to the parent div styling is to change the following code:
style={{ height: 500, width: 500 }}
to:
style={{ height: null, width: null }}
Now the map renders according to CSS styling for its related div.

Related

changing css code and setting focus at the same time (why doesn't it work?)

I tried to make...
There is no input on the screen at the first rendering. When I click the button, input appears. And I want to set focuse on the input at the same time.
Let me explain what i made.
At first, the input is not visible on the screen.
Because the display property of the Box(the div tag), which is the parent component of the input, is none.
But when i click the button, the display property of the Box changes to block.
And here is what i want to do.
i'm going to set focus on the input on the screen.
In the function called when the button is clicked, I wrote a code that changes the css code and sets the focus on the input.
But it didn't work.
Please take a look at the following code.
const [inputDisplay, setInputDisplay] = useState("none");
const refInput = useRef(null);
const HandleShowInput = () => {
setInputDisplay("block");
refInput.current.focus();
};
return (
<>
<Box theme={inputDisplay}>
<Input ref={refInput}/>
<Box/>
<Button onClick={HandleShowInput}/>
</>
)
Below is the code that is dynamically changing the css of the Box component.
import styled, { css } from "styled-components";
const Box = ({ children, ...props }) => {
return <StBox {...props}>{children}</StBox>;
};
const StBox = styled.div`
${({ theme }) => {
switch (theme) {
case "block":
return css`
display: ${theme} !important;
`;
default:
break;
}
}}
`;
export default Box;
But this below code is worked. I separated the code by putting it in useEffect.
const [inputDisplay, setInputDisplay] = useState("none");
const refInput = useRef(null);
const HandleShowInput = () => {
setInputDisplay("block");
};
useEffect(() => {
refInput.current.focus();
}, [inputDisplay]);
return (
<>
<Box theme={inputDisplay}>
<Input ref={refInput}/>
<Box/>
<Button onClick={HandleShowInput}/>
</>
)
I want to know why the upper case not works and the lower case works. I don't know if I have lack react knowledge or css knowledge. I would be very grateful if you could help a beginner in react. Also, please understand if there are any unnatural sentences because i'm not good at English. thank you.
When you are trying to focus on the input element by HandleShowInput this function.Here two things are happening your changing the state and focus of input.It will focus the input but time will be so less that we can't see on the ui.And also due to the state change render will happen and again ref will get the input element. Thus you are not able to see this focussed input
But in case of useEffect this will happen after the render. After this no rendering. So we can see the focussed input
The way of thinking about React is a little different from Javascript.
You may expect the below two run in the same way.
setInputDisplay("block");
refInput.current.focus();
and
document.querySelector('#canFocus').style.display='block'
document.querySelector('#canFocus').focus();
NO~ It's not.
JS block the Dom and then focus it, it works well.
But React works like the code below.
setTimeout(()=>{
// next react render cycle callback
document.querySelector('#canNotFocus').style.display='block'
}, 1000)
document.querySelector('#canNotFocus').focus();
While focus method is called, the dom is display as none;
You set state in react, ReactDom will make it as a display block in the next life cycle of function component.
demo here : https://codesandbox.io/s/confident-wilson-q01ktj?file=/index.html
useEffect(() => {
refInput.current.focus();
}, [inputDisplay]);
is a watching function. While inputDisplay changed, the function inside will be called.
you set state to block
react re-render the component as a newer state
render function called, and dom is block
Effect watching function is called and the focus() called.

Windowing with Antd in Next.js breaks tooltips and popovers

I'm experimenting with the windowing technique in Next.js with Antd, and I made this simple app that has this index.tsx page:
import { CSSProperties } from 'react'
import type { NextPage } from 'next'
import Head from 'next/head'
import Image from 'next/image'
import AutoSizer from 'react-virtualized-auto-sizer'
import { FixedSizeList as List } from 'react-window'
import { Card, Tooltip } from 'antd'
import data from '../data'
import styles from '../styles/Home.module.css'
const people = data();
const Row = ({ index, style }: { index: number, style?: CSSProperties }) => (
<div style={style}>
<Card bordered={true}>
<p>{people[index].name}</p>
<Tooltip title={`${Math.floor(people[index].age/15)} in dog years`}>
<p>{people[index].age}</p>
</Tooltip>
</Card>
</div>
)
const Home: NextPage = () => {
return (
<AutoSizer>
{({ height, width }) => (
<List
height={height}
itemCount={people.length}
itemSize={100}
width={width}
>
{Row}
</List>
)}
</AutoSizer>
)
}
export default Home
The FixedSizeList won't work until I add the following style in globals.css:
html, body, div {
height: 100%;
}
However, when I do that, it breaks the Tooltip by Antd. Usually what happens is that when I hover above a relevant element, the tooltip appears for a split second with 100% height and then disappears and it doesn't appear anymore on a page no matter where I hover.
How can I solve this?
I finally found a solution after much agony.
Apparently, Nextjs wraps the entire layout with a div with id __next. That's the outer-most container of the entire page, and its height is not set, so since the content of FixedSizeList is positioned such that it's outside of the regular page flow, the __next div gets a height of 0.
I don't know if there is any better solution, but I simply added this little style in the globals.css file:
div#__next {
height: 100%;
}
That fixed the issue without forcing every other div in the page to have height at 100% (which includes the tooltips and popovers).

Override React AgGrid styling settings using createStyles and withStyles

I'm aware that it's possible to override Ag Grid properties by editing the CSS itself, however I'm wondering if it's possible to use the functionalities built into react to do this instead. I'm relatively new to the two frameworks, so apologies if there's something I'm not understanding.
Ultimately, what I want to do is something like this:
styles.js
---------
const styles = (theme: Theme) =>
createStyles({
root: {
position: 'relative',
height: 'calc(100vh - 128px)',
},
agHeaderCellLabel: {
agHeaderCellText: {
writingMode: 'vertical-lr',
marginTop: '100px',
},
},
})
export default styles
GridComponent.tsx
-----------------
import styles from './styles'
...
return (
<Paper className={classes.root}>
<div
id="myGrid"
style={{
height: '100%',
width: '100%',
}}
className={`ag-theme-material ${classes.agHeaderCellLabel}`}
>
<AgGridReact
// listening for events
onGridReady={onGridReady}
onRowSelected={onRowSelected}
onCellClicked={onCellClicked}
onModelUpdated={calculateRowCount}
// Data
columnDefs={cDef}
defaultColDef={defaultColumnFormat}
suppressRowClickSelection={true}
groupSelectsChildren={true}
debug={true}
rowSelection="multiple"
// rowGroupPanelShow={this.state.rowGroupPanelShow}
enableRangeSelection={true}
pagination={true}
rowData={rows}
/>
</div>
</Paper>
)
...
export withStyles(styles)(GridComponent)
In this example I'm just trying to get the header text to be displayed vertically.
I've inherited this project, and I've noticed that all of the styling has been done in this method, as there are no custom css files lying around, so I'm trying to stick with that convention of a styles file alongside the component.
Is this possible, and if so,
I ran into this same situation, and came up with the following solution. Although not necessarily ideal, it allows you to continue with the desired convention.
styles.js
---------
const styles = (theme: Theme) =>
createStyles({
root: {
position: 'relative',
height: 'calc(100vh - 128px)',
},
//Apply changes to agGrid material HeaderRoot
myClassAppliedToGrid: {
'& .ag-header[ref="headerRoot"]':{
writingMode: 'vertical-lr',
marginTop: '100px',
}
}
//OR
//Apply Changes to agGrid material header row
myClassAppliedToGrid: {
'& .ag-header-row':{
writingMode: 'vertical-lr',
marginTop: '100px',
}
}
})
export default styles
The key idea is using the & SASS syntax to "reach into" agGrid and make more specific CSS classes so you can override them. (see https://css-tricks.com/the-sass-ampersand/ for more info)
The key pieces of info are:
.parent {
& .child {}
}
turns into
.parent .child {}
and
.some-class {
&.another-class {}
}
turns into
.some-class.another-class { }
Using this sytanx, you should be able to create CSS classes that you can apply to your grid, columns, rows, etc that will properly override the material ag-grid theme.
Here is another example, but this class gets applied to a cell using agGrid cellStyleRules when a row is dragged over it, rather than applying the class to the grid as a whole. This way it only effects cells that have a row drag occuring over them:
rowDraggedOverCellsTopEdge: {
'&.ag-cell': {
borderTopColor: theme.palette.gray[50],
borderTopWidth: 8,
backgroundColor: fade(theme.palette.gray[50], 0.3)
}
},
Finally, one thing I did not do but would reccommend investigating is looking into agGrid's theme overriding, especially if you are on version 23+
https://www.ag-grid.com/javascript-grid-themes-provided/#customising-themes
It might be a good idea to get your base overrides to the theme done this way if you expect a consistent look and feel of your grids throughout the application.
Cheers!

How to dynamically adjust textarea height with React?

I want to adjust my textarea height dynamically with Refs and pass it to the state but it don't work correctly.
I created a codesandbox to help you to understand what exactly I want.
https://codesandbox.io/s/ol5277rr25
You can solve this by using useRef and useLayoutEffect built-in hooks of react. This approach updates the height of the textarea before any rendering in the browser and therefor avoids any "visual update"/flickering/jumping of the textarea.
import React from "react";
const MIN_TEXTAREA_HEIGHT = 32;
export default function App() {
const textareaRef = React.useRef(null);
const [value, setValue] = React.useState("");
const onChange = (event) => setValue(event.target.value);
React.useLayoutEffect(() => {
// Reset height - important to shrink on delete
textareaRef.current.style.height = "inherit";
// Set height
textareaRef.current.style.height = `${Math.max(
textareaRef.current.scrollHeight,
MIN_TEXTAREA_HEIGHT
)}px`;
}, [value]);
return (
<textarea
onChange={onChange}
ref={textareaRef}
style={{
minHeight: MIN_TEXTAREA_HEIGHT,
resize: "none"
}}
value={value}
/>
);
}
https://codesandbox.io/s/react-textarea-auto-height-s96b2
Here's a simple solution that doesn't involve refs. The textarea is dynamically adusted using some CSS and the rows attribute. I used this myself, recently (example: https://codesandbox.io/embed/q8174ky809).
In your component, grab the textarea, calculate the current number of rows, and add 1:
const textArea = document.querySelector('textarea')
const textRowCount = textArea ? textArea.value.split("\n").length : 0
const rows = textRowCount + 1
return (
<div>
<textarea
rows={rows}
placeholder="Enter text here."
onKeyPress={/* do something that results in rendering */}
... />
</div>
)
And in your CSS:
textarea {
min-height: 26vh; // adjust this as you see fit
height: unset; // so the height of the textarea isn't overruled by something else
}
You can check the repo. Or you can add the package to your project.
https://github.com/andreypopp/react-textarea-autosize
Also if you really willing to learn how the logic working exactly;
https://github.com/andreypopp/react-textarea-autosize/blob/master/src/calculateNodeHeight.js
There is a source code with all calculations together.

How to get a react component's size (height/width) before render?

I have a react component that needs to know its dimensions ahead of time, before it renders itself.
When I'd make a widget in jquery I could just $('#container').width() and get the width of the container ahead of time when I build my component.
<div id='container'></div>
these container's dimensions are defined in CSS, along with a bunch of other containers on the page. who defines the height and width and placement of the components in React? I'm used to CSS doing that and being able to access that. But in React it seems I can only access that information after the component has rendered.
The example below uses react hook useEffect.
Working example here
import React, { useRef, useLayoutEffect, useState } from "react";
const ComponentWithDimensions = props => {
const targetRef = useRef();
const [dimensions, setDimensions] = useState({ width:0, height: 0 });
useLayoutEffect(() => {
if (targetRef.current) {
setDimensions({
width: targetRef.current.offsetWidth,
height: targetRef.current.offsetHeight
});
}
}, []);
return (
<div ref={targetRef}>
<p>{dimensions.width}</p>
<p>{dimensions.height}</p>
</div>
);
};
export default ComponentWithDimensions;
Some Caveats
useEffect will not be able to detect it's own influence to width and height
For example if you change the state hook without specifying initial values (eg const [dimensions, setDimensions] = useState({});), the height would read as zero when rendered because
no explicit height was set on the component via css
only content drawn before useEffect can be used to measure width and height
The only component contents are p tags with the height and width variables, when empty will give the component a height of zero
useEffect will not fire again after setting the new state variables.
This is probably not an issue in most use cases, but I thought I would include it because it has implications for window resizing.
Window Resizing
I also think there are some unexplored implications in the original question. I ran into the issue of window resizing for dynamically drawn components such as charts.
I'm including this answer even though it wasn't specified because
It's fair to assume that if the dimensions are needed by the application, they will probably be needed on window resize.
Only changes to state or props will cause a redraw, so a window resize listener is also needed to monitor changes to the dimensions
There's a performance hit if you redraw the component on every window resize event with more complex components. I found
introducing setTimeout and clearInterval helped. My component
included a chart, so my CPU spiked and the browser started to crawl.
The solution below fixed this for me.
code below, working example here
import React, { useRef, useLayoutEffect, useState } from 'react';
const ComponentWithDimensions = (props) => {
const targetRef = useRef();
const [dimensions, setDimensions] = useState({});
// holds the timer for setTimeout and clearInterval
let movement_timer = null;
// the number of ms the window size must stay the same size before the
// dimension state variable is reset
const RESET_TIMEOUT = 100;
const test_dimensions = () => {
// For some reason targetRef.current.getBoundingClientRect was not available
// I found this worked for me, but unfortunately I can't find the
// documentation to explain this experience
if (targetRef.current) {
setDimensions({
width: targetRef.current.offsetWidth,
height: targetRef.current.offsetHeight
});
}
}
// This sets the dimensions on the first render
useLayoutEffect(() => {
test_dimensions();
}, []);
// every time the window is resized, the timer is cleared and set again
// the net effect is the component will only reset after the window size
// is at rest for the duration set in RESET_TIMEOUT. This prevents rapid
// redrawing of the component for more complex components such as charts
window.addEventListener('resize', ()=>{
clearInterval(movement_timer);
movement_timer = setTimeout(test_dimensions, RESET_TIMEOUT);
});
return (
<div ref={ targetRef }>
<p>{ dimensions.width }</p>
<p>{ dimensions.height }</p>
</div>
);
}
export default ComponentWithDimensions;
re: window resizing timeout - In my case I'm drawing a dashboard with charts downstream from these values and I found 100ms on RESET_TIMEOUT seemed to strike a good balance for me between CPU usage and responsiveness. I have no objective data on what's ideal, so I made this a variable.
As it was already mentioned, you can't get any element's dimensions until it is rendered to DOM. What you can do in React is to render only a container element, then get it's size in componentDidMount, and then render rest of the content.
I made a working example.
Please note that using setState in componentDidMount is an anti-pattern but in this case is fine, as it is exactly what are we trying to achieve.
Cheers!
Code:
import React, { Component } from 'react';
export default class Example extends Component {
state = {
dimensions: null,
};
componentDidMount() {
this.setState({
dimensions: {
width: this.container.offsetWidth,
height: this.container.offsetHeight,
},
});
}
renderContent() {
const { dimensions } = this.state;
return (
<div>
width: {dimensions.width}
<br />
height: {dimensions.height}
</div>
);
}
render() {
const { dimensions } = this.state;
return (
<div className="Hello" ref={el => (this.container = el)}>
{dimensions && this.renderContent()}
</div>
);
}
}
You cannot. Not reliably, anyway. This is a limitation of browser behavior in general, not React.
When you call $('#container').width(), you are querying the width of an element that has rendered in the DOM. Even in jQuery you can't get around this.
If you absolutely need an element's width before it renders, you will need to estimate it. If you need to measure before being visible you can do so while applying visibility: hidden, or render it somewhere discretely on the page then moving it after measurement.
There's an unexpected "gotcha" with #shane's approach for handling window resizing: The functional component adds a new event listener on every re-render, and never removes an event listener, so the number of event listeners grows exponentially with each resize. You can see that by logging each call to window.addEventListener:
window.addEventListener("resize", () => {
console.log(`Resize: ${dimensions.width} x ${dimensions.height}`);
clearInterval(movement_timer);
movement_timer = setTimeout(test_dimensions, RESET_TIMEOUT);
});
This could be fixed by using an event cleanup pattern. Here's some code that's a blend of #shane's code and this tutorial, with the resizing logic in a custom hook:
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useState, useEffect, useLayoutEffect, useRef } from "react";
// Usage
function App() {
const targetRef = useRef();
const size = useDimensions(targetRef);
return (
<div ref={targetRef}>
<p>{size.width}</p>
<p>{size.height}</p>
</div>
);
}
// Hook
function useDimensions(targetRef) {
const getDimensions = () => {
return {
width: targetRef.current ? targetRef.current.offsetWidth : 0,
height: targetRef.current ? targetRef.current.offsetHeight : 0
};
};
const [dimensions, setDimensions] = useState(getDimensions);
const handleResize = () => {
setDimensions(getDimensions());
};
useEffect(() => {
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
useLayoutEffect(() => {
handleResize();
}, []);
return dimensions;
}
export default App;
There's a working example here.
This code doesn't use a timer, for simplicity, but that approach is further discussed in the linked tutorial.
As stated, it is a limitation of the browsers - they render in one go and "in one thread" (from JS perspective) between your script that manipulates the DOM, and between event handlers execution. To get the dimensions after manipulating / loading the DOM, you need to yield (leave your function) and let the browser render, and react to some event that rendering is done.
But try this trick:
You could try to set CSS display: hidden; position: absolute; and restrict it to some invisible bounding box to get the desired width. Then yield, and when the rendering is done, call $('#container').width().
The idea is: Since display: hidden makes the element occupy the space it would take if visible, the computation must be done in the background.
I am not sure if that qualifies as "before render".
Disclaimer:
I haven't tried it, so let me know if it worked.
And I am not sure how it would blend with React.
#Stanko's solution is nice and terse, but it's post-render. I have a different scenario, rendering a <p> element inside an SVG <foreignObject> (in a Recharts chart). The <p> contains text that wraps, and the final height of the width-constrained <p> is hard to predict. The <foreignObject> is basically a viewport and if too long it would block clicks/taps to underlying SVG elements, too short and it chops off the bottom of the <p>. I need a tight fit, the DOM's own style-determined height before the React render. Also, no JQuery.
So in my functional React component I create a dummy <p> node, place it to the live DOM outside the document's client viewport, measure it, and remove it again. Then use that measurement for the <foreignObject>.
[Edited with method using CSS classes]
[Edited: Firefox hates findCssClassBySelector, stuck with hardcoding for now.]
const findCssClassBySelector = selector => [...document.styleSheets].reduce((el, f) => {
const peg = [...f.cssRules].find(ff => ff.selectorText === selector);
if(peg) return peg; else return el;
}, null);
// find the class
const eventLabelStyle = findCssClassBySelector("p.event-label")
// get the width as a number, default 120
const eventLabelWidth = eventLabelStyle && eventLabelStyle.style ? parseInt(eventLabelStyle.style.width) : 120
const ALabel = props => {
const {value, backgroundcolor: backgroundColor, bordercolor: borderColor, viewBox: {x, y}} = props
// create a test DOM node, place it out of sight and measure its height
const p = document.createElement("p");
p.innerText = value;
p.className = "event-label";
// out of sight
p.style.position = "absolute";
p.style.top = "-1000px";
// // place, measure, remove
document.body.appendChild(p);
const {offsetHeight: calcHeight} = p; // <<<< the prize
// does the DOM reference to p die in garbage collection, or with local scope? :p
document.body.removeChild(p);
return <foreignObject {...props} x={x - eventLabelWidth / 2} y={y} style={{textAlign: "center"}} width={eventLabelWidth} height={calcHeight} className="event-label-wrapper">
<p xmlns="http://www.w3.org/1999/xhtml"
className="event-label"
style={{
color: adjustedTextColor(backgroundColor, 125),
backgroundColor,
borderColor,
}}
>
{value}
</p>
</foreignObject>
}
Ugly, lots of assumptions, probably slow and I'm nervous about the garbage, but it works. Note that the width prop has to be a number.
All the solutions I found on Stack overflow were either very slow, or out of date with modern React conventions. Then I stumbled across:
https://github.com/wellyshen/react-cool-dimensions
A React hook that measure an element's size and handle responsive components with highly-performant way, using ResizeObserver.
It's fast and works much better than the solutions I tried here.
import { useState, useEffect } from 'react'
const useContainerDimensions = containerRef => {
const getDimensions = () => ({
width: containerRef.current.offsetWidth,
height: containerRef.current.offsetHeight
})
const [dimensions, setDimensions] = useState({ width: 0, height: 0 })
useEffect(() => {
const handleResize = () => {
setDimensions(getDimensions())
}
let dimensionsTimeout = setTimeout(() => {
if(containerRef.current) {
setDimensions(getDimensions())
}
}, 100)
window.addEventListener("resize", handleResize)
return () => {
clearTimeout(dimensionsTimeout)
window.removeEventListener("resize", handleResize)
}
}, [containerRef])
return dimensions
}
export default useContainerDimensions
You can use useContainerDimensions Custom hook. if you need width and height as pixel you can use clientWidth and clientHeight instead of offsetWidth and offsetHeight.

Resources