How to get the width of an element using ref in react - reactjs

I'm trying to get the width of an element, and to do this I'm using the following...
export default () => {
const { sidebarOpen } = useContext(AuthContext)
const containerRef = useRef()
const getWidth = () => myRef.current.getBoundingClientRect().width
const [width, setWidth] = useState(0)
console.log(sidebarOpen)
useEffect(() => {
const handleResize = () => setWidth(getWidth())
if(myRef.current) setWidth(getWidth())
window.addEventListener("resize", handleResize)
console.log('abc', myRef.current.offsetWidth)
return () => window.removeEventListener("resize", handleResize)
}, [myRef, sidebarOpen])
console.log(width)
return (
<div ref={containerRef}>
...
</div>
)
}
When the width of the screen is changed in dev tools page resize, it works fine, but when the value of sidebar changes from 'true' to 'false' (or vice-versa). It returns the previous value of the container width. Does anyone know what I'm doing wrong, I need to get the most up to date value of the container width every time it changes, whether this is caused by a change to 'sidebarOpen' or not.

By default, a div element takes the full width of its parent.
I guess you're using flexbox for the sidebar, so when it closes, the element that contains the content (not the sidebar) expands to the full width of the available space.
This might be the issue that you're facing.
The div with flexbox (on the left we have the Sidebar component and on the right the content):
And right now, when there is no Sidebar:
As another example, let's create a div that has no other property rather than backgroundColor:
Eg:
<div style={{backgroundColor: 'red'}}>Hello</div>
And the result:

Related

Is there anyway to detect the margin of a div?

I want to know the margin-left of div1, so I can use it in my code. Setting it this way, the margin on the left will change depending on the screen size. How can I detect the margin left of this div?
<div class="max-w-6xl m-auto">
</div>
In react, you can make use of the ref callback, which receives the HTML DOM element as an argument. Inside of this callback, you could query the margin-left style from the element and store it in a state. Here's an example:
const App = () => {
const [marginLeft, setMarginLeft] = useState(0); // in pixels
const retrieveMargin = (elm) => {
if (elm != null) {
let styles = window.getComputedStyle(elm);
let ml = styles.getPropertyValue("margin-left");
setMarginLeft(Number.parseInt(ml.replace("px", "")));
}
};
return <div class="mydiv" ref={retrieveMargin}></div>;
};

React, insert/append/render existing <HTMLCanvasElement>

In my <App> Context, I have a canvas element (#offScreen) that is already hooked in the requestAnimationFrame loop and appropriately drawing to that canvas, verified by .captureStream to a <video> element.
In my <Canvas> react component, I have the following code (which works, but seems clunky/not the best way to copy an offscreen canvas to the DOM):
NOTE: master is the data object for the <App> Context.
function Canvas({ master, ...rest } = {}) {
const canvasRef = useRef(master.canvas);
const draw = ctx => {
ctx.drawImage(master.canvas, 0, 0);
};
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
let animationFrameId;
const render = () => {
draw(ctx)
animationFrameId = window.requestAnimationFrame(render)
}
render();
return () => {
window.cancelAnimationFrame(animationFrameId);
}
}, [ draw ]);
return (
<canvas
ref={ canvasRef }
onMouseDown={ e => console.log(master, e) }
/>
);
};
Edited for clarity based on comments
In my attempts to render the master.canvas directly (e.g. return master.canvas; in <Canvas>), I get some variation of the error "Objects cannot be React children" or I get [object HTMLCanvasElement] verbatim on the screen.
It feels redundant to take the #offScreen canvas and repaint it each frame. Is there, instead, a way to insert or append #offScreen into <Canvas>, so that react is just directly utilizing #offScreen without having to repaint it into the react component canvas via the ref?
Specific Issue: Functionally, I'm rendering a canvas twice--once off screen and once in the react component. How do I (replace/append?) the component's <canvas> element with the offscreen canvas (#offScreen), instead of repainting it like I'm doing now?
For anyone interested, this was actually fairly straightforward, as I overcomplicated it substantially.
export function Canvas({ canvas, ...rest }) {
const container = useRef(null);
useEffect(() => {
container.current.innerHTML = "";
container.current.append(canvas);
}, [ container, canvas ]);
return (
<div ref={ container } />
)
}

Trap non interactive element's focus

I have an non interactive element like a div on the page that serves as a loading modal. When it is loading, I want to show this modal as well as the rest of the page containing interactive elements with a opacity of 50%. When it is loading(loading modal shows up), I want the focus trapped in this modal and make it so the user cannot tab through the rest of the elements on the page. I've tried the code below by adding the ReactFocusLock around the div and give the div a tabIndex of -1 so it is focusable and set it to be focusable at the beginning but it does not work. However, if I put a link or something interactable inside the div, it works well, which means the ReactFocusLock is fine. Could you please help?
const MyComponent = () => {
const {loading, data} = fetchData();
return <>
{loading && <LoadingModal />}
{data && <div>data</div>}
<input></input>
<button></button>
<>;
}
// original modal
const LoadingModal = () => {
return <div>loading data....<div>;
}
// modal that with ReactFocusLock from react-focus-lock
const LoadingModal = () => {
const ref = React.useRef(null);
React.useEffect(() => {
ref.current.focus();
}, []);
return <ReactFocusLock><div ref={ref} tabIndex={-1}>loading data....</div></ReactFocusLock>;
}

React.useEffect stack execution prevents parent from setting defaults

I have attached a simplified example that demonstrates my issue:
https://codesandbox.io/s/reactusehook-stack-issue-piq15
I have a parent component that receives a configuration, of which screen should be rendered. the rendered screen should have control over the parent appearance. In the example above I demonstrated it with colors. But the actual use case is flow screen that has next and back buttons which can be controlled by the child.
in the example I define common props for the screens:
type GenericScreenProps = {
setColor: (color: string) => void;
};
I create the first screen, that does not care about the color (parent should default)
const ScreenA = (props: GenericScreenProps) => {
return <div>screen A</div>;
};
I create a second screen that explicitly defines a color when mounted
const ScreenB = ({ setColor }: GenericScreenProps) => {
React.useEffect(() => {
console.log("child");
setColor("green");
}, [setColor]);
return <div>screen B</div>;
};
I create a map to be able to reference the components by an index
const map: Record<string, React.JSXElementConstructor<GenericScreenProps>> = {
0: ScreenA,
1: ScreenB
};
and finally I create the parent, that has a button that swaps the component and sets the default whenever the component changes
const App = () => {
const [screenId, setScreenId] = useState(0);
const ComponentToRender = map[screenId];
const [color, setColor] = useState("red");
React.useEffect(() => {
console.log("parent");
setColor("red"); // default when not set should be red
}, [screenId]);
const onButtonClick = () => setScreenId((screenId + 1) % Object.keys(map).length)
return (
<div>
<button style={{ color }} onClick={onButtonClick}>
Button
</button>
<ComponentToRender setColor={setColor} />
</div>
);
};
In this example, the default color should be red, for screen A. and green for the second screen.
However, the color stays red because useEffect is using a stack to execute the code. if you run the code you will see that once the button clicked there will be child followed by parent in log.
I have considered the following solution, but they are not ideal:
each child has to explicitly define the color, no way to enforce it without custom lint rules
convert the parent into a react class component, there has to be a hooks solution
This might be an anti-pattern where child component controls how its parent behave, by I could not identify a way of doing that without replicating the parent container for each screen. the reason I want to keep a single parent is to enable transition between the screens.
If I understood the problem correctly, there is no need to pass down setColor to the children. Making expressions more explicit might make a bit longer code, but I think it helps in readability. As what you shared is a simplified example, please let me know if it fits your real case:
const ScreenA = () => {
return <div>screen A</div>;
};
const ScreenB = () => {
return <div>screen B</div>;
};
const App = () => {
const [screen, setScreen] = useState<"a" | "b">("a");
const [color, setColor] = useState<"red" | "green">("red");
const onButtonClick = () => {
if (screen === "a") {
setScreen("b");
setColor("green");
} else {
setScreen("a");
setColor("red");
}
};
return (
<div>
<button style={{ color }} onClick={onButtonClick}>
Button
</button>
{screen === "a" ? <ScreenA /> : <ScreenB />}
</div>
);
};
render(<App />, document.getElementById("root"));

react initial state calculated from DOM

I am using https://www.npmjs.com/package/react-slick and I'm trying to make it est "slidesToShow" dynamically based on available width.
I've managed to get it working, but only after window resize. I need to supply it with an initial state calculated from an element width. The problem is that the element doesn't exist at that point.
This is my code (the interesting parts):
const Carousel = (props: ICarouselProps) => {
const slider = useRef(null);
const recalculateItemsShown = () => {
const maxItems = Math.round(
slider.current.innerSlider.list.parentElement.clientWidth / 136, // content width is just hardcoded for now.
);
updateSettings({...props.settings, slidesToShow: maxItems});
};
useEffect(() => {
window.addEventListener('resize', recalculateItemsShown);
return () => {
window.removeEventListener('resize', recalculateItemsShown);
};
});
const [settings, updateSettings] = useState({
...props.settings,
slidesToShow: //How do I set this properly? slider is null here
});
return (
<div id="carousel" className="carousel">
<Slider {...settings} {...arrows} ref={slider}>
<div className="test-content/>
<div className="test-content/>
/* etc. */
<div className="test-content/>
<div className="test-content/>
<div className="test-content/>
</Slider>
</div>
);
};
export default Carousel;
if I call updateSettings in my useEffect I get an infinite loop.
So I would have to either:
Somehow get the width of an element that hasn't yet been created
or
call updateSettings once after the very first render.
You could have a function that returns maxItems and use that everywhere, so:
const getMaxItems = () => Math.round(slider.current.innerSlider.list.parentElement.clientWidth / 136)
You use its return result within recalculateItemsShown to update the settings.
const recalculateItemsShown = () => {
updateSettings({...props.settings, slidesToShow: getMaxItems()});
};
And you also use its return value to set the state initially.
const [settings, updateSettings] = useState({
...props.settings,
slidesToShow: getMaxItems()
});
If the element doesn't exist initially, you could use useEffect with an empty array as the second argument. This tells useEffect to watch changes to that array and call it everytime it changes, but since its an empty array that never changes, it will only run once - on initial render.
useEffect(() => {
updateSettings({...props.settings, slidesToShow: getMaxItems()});
}, []);
You can read more about skipping applying events here: https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
I believe the uselayouteffect hook is designed for this exact use case.
https://reactjs.org/docs/hooks-reference.html#uselayouteffect
It works exactly the same way as useEffect except that it fires after the dom loads so that you can calculate the element width as needed.

Resources