ReactJS App: Is it possible to order the rendering of specific divs? - css

I'm trying to create a ReactJS (using react bootstrap) mobile app that can resize (expand or contract) itself based on the screensize. The dimensions of one part of the app need to be calculated based on how much space is left on the screen after all other parts have rendered.
For example, consider the below markup -
var calcWidth = (100 / tableSize).toString() + '%';
return(
<Container>
<Row id='1'>Header and other static stuff here</Row>
<Row id='2'>
//A db driven table with square shaped cells goes here. It has the below structure -
<Container style={{width:'100%'}}>
<Row><Col style={{width:calcWidth, paddingBottom:calcWidth}}></Col>...</Row>
...
</Container>
</Row>
<Row id='3'>Footer and other static stuff here</Row>
</Container>
);
In the above markup, Row id 1 and 3 contain static stuff like headers, footers, buttons, titles etc. The Row id 2 contains a table that can contain "n" number of cells and each cell needs to be square shaped with content horizontally and vertically centerd.
The above code correctly calculates the width of each cell from the width of the container (which is 100%) and creates square shaped cells and fits perfectly horizontally. But since the height is same as the width, it gets bigger vertically and drives the footer beyond the screen. We want to avoid scrollbars. The solution seems to be to calculate calcWidth based on the remaining height available to the table, something like the below -
var remainingHeight = <total height of the container> - <height taken up by Row 1> - <height taken up by Row 3>
var width = <width of the screen>
var calcWidth = ((remainingHeight < width ? remainingHeight : width) / tableSize).toString() + '%';
My questions are -
How to calculate the remainingHeight variable above? How to let Row1 and Row3 render before Row2 and then calculate the remaining height?
How to find the total height and width of the container?
Any other better way of doing this? I'm just a newbie, probably there are some css tools to do it more efficiently?

Here you can find an example on how to calculate the height of react components after rendering:
export default function App() {
const [height1, setHeigt1] = useState(0);
const [height2, setHeight2] = useState(0);
const [height3, setHeight3] = useState(0);
const [remainingHeight, setRemainingHeight] = useState(0);
useEffect(() => {
const remainingHeight = 100 - height1 - height2 - height3;
console.log(remainingHeight);
setRemainingHeight(remainingHeight);
}, [setRemainingHeight, height1, height2, height3]);
return (
<div
id="container"
style={{
height: "100px",
backgroundColor: "firebrick",
padding: "15px"
}}
>
<ResizableComponent
id="component-1"
content={`Initial component 1 height = ${height1}`}
onHeightUpdated={setHeigt1}
/>
<ResizableComponent
id="component-2"
content={`Initial component 2 height = ${height2}`}
onHeightUpdated={setHeight2}
/>
<ResizableComponent
id="component-3"
content={`Initial component 3 height = ${height3}`}
onHeightUpdated={setHeight3}
remainingHeight={remainingHeight}
/>
</div>
);
}
export function ResizableComponent({
id,
content,
onHeightUpdated,
remainingHeight
}) {
const [height, setHeight] = useState(0);
const [isFirstRender, setIsFirstRender] = useState(true);
useEffect(() => {
const newHeight = document.getElementById(id).clientHeight;
if (height !== newHeight && isFirstRender) {
setHeight(newHeight);
setIsFirstRender(false);
}
}, [isFirstRender, id, height, onHeightUpdated, remainingHeight]);
useEffect(() => {
onHeightUpdated(height);
}, [height, onHeightUpdated]);
return (
<div
id={id}
style={
remainingHeight
? {
backgroundColor: "pink",
height: `calc(${height}px + ${remainingHeight}px)`
}
: { backgroundColor: "pink" }
}
>
{content}
</div>
);
}

Related

How to center items inside konvajs?

I am currently building some sort of meme-editor in react and for that I am using konvajs to function similar to a Canvas.
My problem is that I am unable to center the items inside the canva stage, as there seems to be some property that just overrides my styling.
This is (part of) the return statement in my react-component:
<div className="mycanvas">
<Stage width={500} height={500} className="stage">
<Layer>
<Image image={image} className="meme" />
{textFields.map((text) => (
<Text
text={text}
draggable
fontFamily="Impact"
fontSize="30"
stroke="white"
/>
))}
</Layer>
</Stage>
</div>
And this is how the output gets rendered.
I have coloured the background of the wrapper blue, to show in which box the image should be centered.
I have already tried CSS on the classes "mycanvas", "stage" and "meme" and also on "konvajs-content" (as that showed up in my inspector for some reason). I have used align-items: center, margin: auto and a couple others, but I think normal CSS does not really apply here.
I think it is an issue regarding the generaly styling of konvajs components, but unfortunately I could not find any solution on stackoverflow or the konva documentation.
This is an instance where CSS can't help. When the image is applied to the canvas using its height and width at the x and y coordinates you supply, the pixels of the image become part of the rasterized canvas. In other words, the image doesn't exist independent of the canvas.
Therefore, if you want to center the image inside of your canvas, you need to do a little math to calculate the x and y coordinates that will place the image centered inside the canvas.
Demo
For example, if your canvas size is 500px tall and your image has a height of 350px, then you need to set the y position to 75px (i.e., (500 - 350) / 2).
The demo code below shows how to replicate the behavior of CSS object-fit: contain. This will adjust the image to fill the canvas in one direction, and then center the image in the other direction.
import { useState, useEffect } from "react";
import { Stage, Layer, Image, Text } from "react-konva";
function Example() {
const w = window.innerWidth;
const h = window.innerHeight;
const src = "https://konvajs.org/assets/yoda.jpg";
const [image, setImage] = useState(null);
const [pos, setPos] = useState({ x: 0, y: 0 });
useEffect(() => {
const image = new window.Image();
image.src = src;
image.addEventListener("load", handleLoad);
function handleLoad(event) {
const image = event.currentTarget;
/* after the image is loaded, you can get it's dimensions */
const imgNaturalWidth = image.width;
const imgNaturalHeight = image.height;
/*
calculate the horizontal and vertical ratio of the
image dimensions versus the canvas dimensions
*/
const hRatio = w / imgNaturalWidth;
const vRatio = h / imgNaturalHeight;
/*
to replicate the CSS Object-Fit "contain" behavior,
choose the smaller of the horizontal and vertical
ratios
if you want a "cover" behavior, use Math.max to
choose the larger of the two ratios instead
*/
const ratio = Math.min(hRatio, vRatio);
/*
scale the image to fit the canvas
*/
image.width = imgNaturalWidth * ratio;
image.height = imgNaturalHeight * ratio;
/*
calculate the offsets so the image is centered inside
the canvas
*/
const xOffset = (w - image.width) / 2;
const yOffset = (h - image.height) / 2;
setPos({
x: xOffset,
y: yOffset
});
setImage(image);
}
return () => {
image.removeEventListener("load", handleLoad);
};
}, [src, h, w]);
return (
<Stage width={w} height={h} style={{ background: "black" }}>
<Layer>
<Image x={pos.x} y={pos.y} image={image} />
<Text
text="I am centered"
fontFamily="Impact"
fontSize={50}
stroke="white"
strokeWidth={1}
x={pos.x}
y={pos.y}
/>
</Layer>
</Stage>
);
}

Changing react-leaflet map width with button click

I am working on a map with react-leaflet. I placed a button on the map that will ideally open a menu on the left side of the map. The way I want to open up the menu is by changing the width of the map from 100% to 80%. The menu button toggles a boolean, which ideally the map will rerender and resize when the button clicks.
this is the code in my App.js
export default function App() {
const [isMarkerClick, setIsMarkerClick] = React.useState(false);
const [isMenuOn, setIsMenuOn] = React.useState(false);
function toggleMarker() {
setIsMarkerClick(prevClick => !prevClick);
}
function toggleMenu() {
setIsMenuOn(prevMenu => !prevMenu);
}
return (
<div>
<MainMap isMenuOn={isMenuOn} />
<MarkerButton isMarkerClick={isMarkerClick} toggleMarker={toggleMarker} />
<MenuButton toggleMenu={toggleMenu} />
</div>
)
}
this is where the Map Code lives
export default function MainMap(props) {
const isMenuOn = props.isMenuOn;
const isMarkerClick = props.isMarkerClick;
const zoom = 15;
const position = ['39.9526', '-75.1652'];
const [marker, setMarker] = React.useState(["", ""])
return (
<div>
<MapContainer
center={position}
zoom={zoom}
zoomControl={false}
style={{
height: "100vh",
width: isMenuOn ? "80vw" : "100vw"
}}
>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker position={marker}></Marker>
{isMarkerClick && <ClickTrack setMarker={setMarker} />}
</MapContainer>
</div>
)
}
as of now, when I click the menu button, the isMenuOn state updates and which then feeds back into the MapContainer and should rerender with a new width but it doesn't. Any Ideas on how to get change the map size with a click of button using react?
example of the menu button
I thought that when clicking the menu button and changing the state of isMenuOn to "true", the mapcontainer would be listening and rerender with using the new width. Although it seems like setting the width through style ={{}}, might not allow for changes?
React Leaflet will only set the width and height when the component mounts and does not update it with the state changes, in order to update the map's width you need to re-render the component fully and to do that set the component's key to something that will change with the isMenuOpen value
here is an example
<MapContainer
center={position}
zoom={zoom}
zoomControl={false}
key={isMenuOn ? "open-state": "closed-state"}
style={{
height: "100vh",
width: isMenuOn ? "80vw" : "100vw"
}}
>
The accepted answer works, but I suspect it would cause a full map reload. You can try keeping the width value static in the MapContainer element, and instead change it the wrapping div like this:
<div style={{ width: isMenuOn ? '80vw' : '100vw' }}>
<MapContainer
center={position}
zoom={zoom}
zoomControl={false}
style={{
height: '100vh',
width: '100%',
}}
/>
</div>
There is a caveat though; the map needs to be informed of the change in size, otherwise it will seem buggy. I suspect that's why the developers don't update the style after the map mount.
You can however add a map child that monitors the isMenuOn prop (or any other prop that changes your layout), and let the map instance know they should invalidate everything about sizing:
function LayoutListener({ isMenuOn }) {
const map = useMap();
useEffect(() => {
map?.invalidateSize();
}, [map, isMenuOn]);
return null;
}
And then just put in the MapContainer children:
<MapContainer>
<LayoutListener isMenuOn={isMenuOn} />
{otherChildren}
</MapContainer>

Autoheight in MUI DataGrid

I'm using the MUI DataGrid component, and the behavior I hope to have is that:
When there's a small number of rows, the table is only the size it needs to for those rows.
When there's a large number of rows, more than the current viewport can hold (given whatever else is on the screen), the table takes up the available space in the layout (given its flex: 1) and the extra rows scroll inside the table.
I can achieve each of these behaviors, but only one at a time.
If I use the autoHeight property on the DataGrid, then the table will be as small as it can be. BUT it will also be as big as it can be, so with a large number of rows the container scrolls the entire table, rather than the rows scrolling within the table.
If I don't use autoHeight, and wrap the DataGrid in a container with flex: 1, then the table will grow to fill the available space and the rows will scroll within the table. BUT a table with only a few rows will also grow to fill its container, so that there is empty space under the rows (above the footer, "Table rows: #")
You can see the situation in this screenshot, showing the exact same page, with different data.
I've tried what feels like every permutation of heights and flexes under the sun. For example:
Setting autoHeight with a maxHeight (and .MuiDataGrid-main { overflow: scroll; } ) allows few-rows to be small, and many-rows to be not too small, but obviously any discrete maxHeight, be it px or %, is not the flexible layout I'm going for.
Turning off autoHeight (as in scenario #2) and setting flex-grow: 0 on the rows container within the table (.MuiDataGrid-main) just makes the rows disappear since they then shrink to a height of 0.
The code for the component:
const S = {
Wrapper: styled.div`
width: 100%;
display: flex;
flex: 1;
background: white;
border: solid thick red;
`,
DataGrid: styled(DataGridPro)`
&& {
.MuiDataGrid-main {
//overflow: scroll;
//flex-grow: 0;
}
background: lightgreen;
font-size: 14px;
}
`,
};
type Props = {
columns: ReadonlyColumns;
rows: AnyObject[];
filterModel?: GridFilterModel;
} & Omit<DataGridProps, 'columns'>;
const DataTable: React.FC<Props> = ({
columns = [],
rows = [],
filterModel,
className,
...props
}) => {
const memoizedColumns = useMemo(
() =>
columns.map(col => ({
headerClassName: 'columnHeader',
flex: 1, // all columns expand to fill width
...col, // but could override that behavior
})),
[columns],
);
return (
<S.Wrapper className={className}>
<S.DataGrid
// autoHeight
rows={rows}
columns={memoizedColumns}
filterModel={filterModel}
{...props}
/>
</S.Wrapper>
);
};
Using a combination of autoHeight and pageSize will create a table whose height is only as big as needed for the current number of rows as long as the number of rows is <= pageSize. Additional rows will be added to a new page.
<DataGrid
rows={rows}
columns={columns}
pageSize={20} //integer value representing max number of rows
autoHeight={true}
/>
Below solved the issue:
<DataGrid getRowHeight={() => 'auto'} />
Source:
https://mui.com/x/react-data-grid/row-height/#dynamic-row-height
I had a similar issue days ago and I solved recalculating the row height every time a new item was added to my row.
getRowHeight={(props: GridRowHeightParams) => {
const serviceRowHeight = 45 // <-- default height, if I have no data for the row
const addServiceBtnHeight = 45 // <-- a component that's always on the row
const height = props.model?.services // services is each dynamic item for my row, so you should access like props.yourObj
? props.model.services.length * serviceRowHeight + addServiceBtnHeight
: 115
return height < 115 ? 115 : height // final height to be returned
}}

Can Material-UI TextField width be set to match width of input text?

Is it possible to have Material-UI automatically adjust the width of the TextField element to match the width of the input text?
I am creating a form view/edit page and I'm rendering the data back into the same fields, however there is also a series of parameters which the server sets. It would be nice to render in disabled form elements and have their width automatically fit.
I've played with the width properly of both TextField and the underlying Input with no success. I could potentially count the characters and set a width in JS, but I'd rather a CSS solution.
<TextField
disabled={true}
label={"UUID"}
value={"7be093a5647d41ff8d958928b63d11f5"}
style={{width: "auto"}}
InputProps={{
style: {width: "100%"}
}}
/>
https://codesandbox.io/s/material-demo-forked-c3llv
You could base the width of the input on the length of the text
const FONT_SIZE = 9
const DEFAULT_INPUT_WIDTH = 200
const [textValue, setTextValue] = useState("")
const [inputWidth, setInputWidth] = useState(DEFAULT_INPUT_WIDTH)
useEffect(() => {
if (textValue.length * FONT_SIZE > DEFAULT_INPUT_WIDTH) {
setInputWidth((textValue.length + 1) * FONT_SIZE)
} else {
setInputWidth(DEFAULT_INPUT_WIDTH)
}
}, [textValue])
return (
<div>
<TextField
label={"UUID"}
value={textValue}
onChange={(e) => setTextValue(e.target.value)}
InputProps={{
style: { width: `${inputWidth}px` },
}}
/>
</div>
)
Below is the forked codesandbox
Reference: this answer

How to make non-interactive Grid overlay?

I want to have the Grid visible on my site while i'm developing it, a-la Adobe XD style (link to image), to better understand how much place elements are taking(or should take).
The code underneath works but I have to manually specify Width of the grid when setting position to 'fixed' or 'absolute'.
There must be a more elegant way to do this.
Any suggestions on how to do this?
const testGridItems = () =>{
var list = new Array();
for(let i=0; i<12; i++){
list.push(
<Grid item xs={1}>
<div align='center'
style={{
height: '100vh',
position:'relative',
backgroundColor: '#F6BB42',
opacity:0.2}
}>
{i+1}
</div>
</Grid>
)
}
return list;
}
const testGrid = () => {
return (
<Grid container
style={{
width:1200,
position:'fixed'
}}
spacing={24} >
{ testGridItems() }
</Grid>
)
}

Resources