Set value immediately after another one have been set in Reanimated 2 - reactjs

I am using Reanimated 2 to build a game with React Native its performance is incredibly good but I have a problem.
I am using a shared value to animate a View as we all know setting the value of the shared value will automatically change the style of the View, my problem is that let's say it will be a Button that the user presses to give the View an elevation simply by changing a shared value used in the animated style of the View, the elevation is simply translation in the y axis.
The elevation value is 0 at first. The user clicks the button the value changes to for example 500 immediately with no transition and no animation, the View will immediately show at 500 above its starting position. And from 500 the View will drop back to 0 with animation.
I tried the code below but no help.
const elevation = useSharedValue(0);
const handleClick = () => {
elevation.value = 500;
elevation.value = withTiming(0, { duration: 1000 });
}
const viewAnimatedStyle = useAnimatedStyle(() => ({
transform: [
{
translateY: elevation.value,
}
]
}))
when pressing the button the view doesn't move, it seems that Reanimated skips the first elevation.value assignment, and since the second assignment is to 0 (the same old value) the View doesn't move.
[Edit] Animated.View is imported from Reanimated 2 and used. I left it out for simplicity.

You need to use <Animated.View> for the useAnimatedStyle, If you are now using It will not be working correctly.
function App() {
const width = useSharedValue(50);
const animatedStyle = useAnimatedStyle(() => {
return {
width: width.value,
};
});
// attach animated style to a View using style property
return <Animated.View style={[styles.box, animatedStyle]} />;
}

Related

Get bbox of svg element before render or at least without flickering

I have a need to have some text scale in ways only SVG can as far as I could find. The text will change frequently so it also needs to adapt to that.
I'm making the app in react and would like to know how to calculate the bbox of an SVG (initially and every time it changes) before rendering it or at least without flickering / layout shift.
An example could be found here - the current issue is that it flicker. Everything else works fine more or less.
I've seen some other questions that are similar or nearly identical - however they do not have the requirement of changing text so it's possible to compute the bounding box in advance once or at least a one time flicker is not a big issue. Another question / thread also used a class component that supposedly updated the state at component mount but before render which as they claim does not cause a flicker but a lot has changed since then in react and in the example I tried the flicker is there.
The best compromise I've found so far is to just make the SVG's visibility hidden and measure the bbox of the text first and show it when done. Then every time the text changes measure again. Generally speaking this should not be too crazy in terms of jumping around or any other visual quirks but for best results you'd want to set a certain fixed size box for the SVG to fill up and not cause any layout shift.
import { useState, useEffect, useRef } from "react";
export type BBox = { x: number; y: number; width: number; height: number };
const makeViewBox = (bbox: BBox) => {
return `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`;
};
// Note that this component still sometimes jiggles around a bit
// and it is particularly noticable with nont monospaced fonts.
const ScalableSVGText = ({ text }: { text: string }) => {
// Reference to the SVG element, needed to take bbox measurements
// and adjust the SVG's viewBox
const ref = useRef<null | SVGSVGElement>(null);
// State is somewhat needed to force re-render - other methods
// can be used but whatever...
const [bbox, setbbox] = useState<null | BBox>(null);
// On initial mount & every time the text changes measure the
// bbox and update the state so the component re-renders
useEffect(() => {
if (ref.current) {
setbbox(ref.current.getBBox());
}
}, [text]);
return (
<svg
ref={ref}
width="100%"
height="100%"
viewBox={bbox ? makeViewBox(bbox) : ""}
style={{
// not strictly needed but makes it somwehat easier on the eyes
visibility: bbox ? "visible" : "hidden"
}}
>
<text>{text}</text>
</svg>
);
};
export default ScalableSVGText;

In my React app, why does "setPointerCapture" not work if I am changing the z-index of my (draggable) component (that is using "setPointerCapture")?

I have discovered a very strange, undesired behavior of my React application that I would like to understand and to correct.
Unfortunately, my application is quite large and I was not able to recreate the issue I am encountering in a smaller application (for demonstration purposes), although I really did try.
Here is what I would like to achieve:
In order to control re-rendering of all my visual content represented by data, I use useRef hooks in a couple of custom hooks to represent and manipulate my data without triggering a re-render due to a changed state. Once all my data is “up-to-date” (or once it makes sense from by “business logic’s perspective”) I call requestUpdate in my main component that updates the relevant data represented by useState hooks. This data goes as props into all my components.
Now, I would like to perform a drag-and-drop operation on some visual components. In order to catch all relevant events, I call setPointerCapture on receiving an onPointerDown event. In order to properly see the components I am dragging around I adjust their z-indices in order to have the components being dragged “on top”. That works fine, by the way.
Here is the issue that I am encountering:
What does not work is the setPointerCapture if I change the z-indices. Strangely, if I comment out the respective little part of my code that changes the z-indices, everything works fine. If I don’t (i.e., if I leave these lines in my code), my component never “gets” to “capture the pointer” (I am sure since I observed that the “onGotPointerCapture” event is never fired). The consequence is that I “loose” my element if I am dragging to fast and if the mouse arrow is no longer over my component (which is exactly what I wanted to avoid in the first place with my setPointerCapture).
Please find below the relevant extracts of my code.
This is where I (try to) do my setPointerCapture. This function handles the onPointerDown event "fired" by the visual component that I would like to drag.
const onPointerDown = useCallback(
(event: React.PointerEvent) => {
// Case 1: We are neither allowed to drag nor to select
if (!draggingEnabled && !selectingEnabled) return;
// ...
// Case 2: We are allowed to drag (and we will select before we actually start dragging)
if (draggingEnabled) {
const target: HTMLDivElement = event.target as HTMLDivElement;
target.setPointerCapture(event.pointerId);
// ...
onStartDragging(id, event.shiftKey);
return;
}
// Case 3: We are "only" allowed to select
// ...
},
[id, draggingEnabled, selectingEnabled, onSelect, onStartDragging]
);
This is the interface defining the props that are used by my visual component that I would like to drag. The relevant property is, obviously, the zIndex.
export interface VisualDiagramElementData {
id: string;
top: number;
left: number;
height: number;
width: number;
// ...
zIndex: number;
// ...
}
Here my elements are declared using a useRef hook.
const elements: VisualDiagramElementData[] = useRef<
VisualDiagramElementData[]
>(
initialDiagramElements.map((element: DiagramElement) => ({
id: element.id,
top: element.top,
left: element.left,
height: element.height,
width: element.width,
// ...
zIndex: element.zIndex,
// ...
}))
).current;
This is the critical function. It handles the elements that are represented by my visual components that I would like to drag. elements uses the useRef hook (see above).
const onStartDragging = useCallback(
(id: string, shiftKeyPressed: boolean) => {
// We want to drag or to select. Select element with current id if not yet selected
// ...
// Adjust order of elements to ensure that dragged elememts are (and remain after dragging) on top
// If I comment this out from here ...
const sortedElements: VisualDiagramElementData[] = elements.sort(
(elementA, elementB) => {
if (elementA.selected && !elementB.selected) return 1;
if (!elementA.selected && elementB.selected) return -1;
return 0;
}
);
for (let i = 0; i < sortedElements.length; i++) {
sortedElements[i].zIndex = i + 1;
}
// ... to here, there is no issue any more!
requestUpdate();
},
[elements, isSelected, requestUpdate]
);
requestUpdate() updates the data (represented by an array using a useState hook) that is going as props into the component I would like to drag. auxElements are the "elements" from my useRef hook, i.e., the elements in the code above.
// useRef hooks
const requestUpdate = useRef(() => {
requestAnimationFrame(() => {
setElements([...auxElements]);
// ...
});
}).current;
// useState hooks
const [elements, setElements] = useState<VisualDiagramElementData[]>([]);

Conditional styling on row for dynamic cell value

Conditional row styling on ag grid where I want to do rowstyle on user choice of cell value
gridoptions.getRowStyle = function(params) {
if (params.node.data === 'cell value typed by user in external/custom component i.e outside grid') {
return { 'background': value selected by user in cutom componet outside grid };
}
}
#sandeep's answer works perfectly. I just want to chime in another way to solve the problem which is to use context. context is just another javascript object which contains any information that you want to share within AgGrid. The data will be accessible in most AgGrid callbacks for example cell renderers, editors's render callback and in your case getRowStyle callback
const sickDays = // data from external component
const color = // data from external component
<AgGridReact
getRowStyle={(params) => {
const { styles, data } = params.context;
if (params.node.data["sickDays"] === data.sickDays) {
return { backgroundColor: styles.color };
}
return null;
}}
context={{
data: { sickDays },
styles: { color }
}}
/>
Live Demo
here is a plunkr which should give you idea to solve the problem. since i don't know much about your component hence i used two input boxes with button to set background color to row but you can use complex styles as well.
I am using api.redrawRows() since the operation we are performing needs to work on row.

How to create a resizable component in React

As said in the title. I want to create a React component that will give me a possibility to resize its width by dragging - just like windows in Windows operating system. What is actually the best approach to handle this issue ?
EDIT
I included my current approach to the subject of the matter:
First I placed a "dragger" element in the top-right corner of my container. When i press mouse down on that element i want to create a mousemove event listener which will modify the containerWidth in respect to the X coordinate of the cursor relative to the initial X position of the edge of the container. I already have that event listener firing and logging me the coordinates after holding down the mouse button but unfortunatelly for some reason the event is not being removed after the mouse is unpressed(mouseUp event) which is not what i intended. Any suggestions appreciated, also those about some issues i might expect in the future related to this topic. Thanks.
type Props = MasterProps & LinkStateToProps & LinkDispatchToProps;
const Test3 = (Props: Props) => {
const [containerWidth, setContainerWidth] = React.useState(640)
const [isBeingStretched, setIsBeingStretched] = React.useState(false);
const masterRef = React.useRef(null);
const logMousePosition = React.useCallback((event:MouseEvent)=>{
console.log(event.clientX);
},[])
const handleMouseDown=()=>{
document.addEventListener('mousemove', logMousePosition);
masterRef.current.addEventListener('mouseup', ()=>{
document.removeEventListener('mouseup', logMousePosition)
})
}
const handleMouseUp = () => {
document.removeEventListener('mousemove', logMousePosition);
}
return (
<div className="master-wrapper" ref={masterRef}>
<div className="stretchable-div" style={{ width: `${containerWidth}px` }}>
<div className="dragger-wrapper">
<h2>This is supposed to change width</h2>
<div className="dragger"
id="dragger"
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}/>
</div>
</div>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(Test3);
I'd never done something like this before so I decided to give it a go, and it ended up being quite straightforward to implement with React state management. I can see why you might not know where to start if you are new to React, and that's ok, although two things to note before I go through my solution:
Statements such as document.getElementById or document.addEventListener are not going to function as intended anymore. With React, you are manipulating a virtual DOM, which updates the actual DOM for you, and you should aim to let it do that as much as possible.
Using refs to get around this fact is bad practice. They may act in a similar way to the statements mentioned above but that is not their intended use case. Read up on what the documentation has to say about good use cases for ref.
Here's what the JSX portion of my demo looks like:
return (
<div className="container" onMouseMove={resizeFrame} onMouseUp={stopResize}>
<div className="box" style={boxStyle}>
<button className="dragger" onMouseDown={startResize}>
Size Me
</button>
</div>
</div>
);
We're going to need three different events - onMouseDown, onMouseMove and onMouseUp - to track the different stages of the resize. You already got this far in your own code. In React, we declare all these as attributes of the elements themselves, although they are not actually in-line functions. React adds them as event listeners for us in the background.
const [drag, setDrag] = useState({
active: false,
x: "",
y: ""
});
const startResize = e => {
setDrag({
active: true,
x: e.clientX,
y: e.clientY
});
};
We'll use some state to track the resize as it is in progress. I condensed everything into a single object to avoid bloat and make it more readable, although this won't always be ideal if you have other hooks like useEffect or useMemo dependent on that state. The first event simply saves the initial x and y positions of the user's mouse, and sets active to true for the next event to reference.
const [dims, setDims] = useState({
w: 200,
h: 200
});
const resizeFrame = e => {
const { active, x, y } = drag;
if (active) {
const xDiff = Math.abs(x - e.clientX);
const yDiff = Math.abs(y - e.clientY);
const newW = x > e.clientX ? dims.w - xDiff : dims.w + xDiff;
const newH = y > e.clientY ? dims.h + yDiff : dims.h - yDiff;
setDrag({ ...drag, x: e.clientX, y: e.clientY });
setDims({ w: newW, h: newH });
}
};
The second piece of state will initialise and then update the dimensions of the element as its values change. This could use any measurement you want although it will have to correlate to some CSS property.
The resizeFrame function does the following:
Make the properties of drag easily available via destructuring assignment. This will make the code more readable and easier to type.
Check that the resize is active. onMouseMove will fire for every pixel the mouse moves over the relevant element so we want to make sure it is properly conditioned.
Use Math.abs() to get the difference in value between the current mouse position and the saved mouse position as a positive integer. This will save us from having to do a second round of conditional statements.
Use turnary statements to either add or subtract the difference from the dimensions, based on whether the new mouse position is greater or less than the previous on either axis.
Set the states with the new values, using the spread operator ... to leave the irrelevant part of drag as it is.
const stopResize = e => {
setDrag({ ...drag, active: false });
};
const boxStyle = {
width: `${dims.x}px`,
height: `${dims.y}px`
};
Then we simply set the activity of the drag state to false once the user is finished. The JS style object is passed to the element with the state variable in place so that is taken care of automatically.
Here is the codesandbox with the finished effect.
One of the drawbacks to doing things this way is that it basically requires you to have that mouseMove event listener assigned to the largest site container, because the mouse is not going to stay within the bounds of the box during the resize. That could be an issue if you want to have multiple elements with the same functionality, although nothing that you can't solve with good state management. You could probably fine tune this so that the mouse always stays on the drag element, although that would require a more complex implementation.

How to check to see if a scrollbar is present in React?

I haven't seen a thread that handles this, at least not for React.
My case: I want to conditionally render a back to top button only when scrolling is an option. It makes no sense to have such a feature if it can't affect the page.
The only solutions I can find are in jQuery. I'm using react-scroll but couldn't find any functionality there for this.
When a scrollbar is visible then window.visualViewport.width < window.Width.
var buttonIsVisible = window.visualViewport.width < window.Width;
To check if scrollbar is visible in vertical appearance.
document.body.clientHeight > window.innerHeight
I added this code in a useEffect.
useEffect(() => {
if (document.body.clientHeight > window.innerHeight) {
something()
}
}, [state]);
Luke.
By "scrolling is an option" I am assuming here that you mean "when the scrollbar is visible."
As far as I am aware, there is not any way to check for scrollbar visibility using the React API. There is the DOM boolean window.scrollbars.visible; however, I have not had luck with this. It seems to always return true whether a scrollbar is visible or not. The following approach may work for you:
You could set an event listener for onScroll and check window.scrollY. If window.scrollY > 0, then you could conditionally render the button. If window.scrollY is 0, then the page is already scrolled to the top and there is no need to display the button.
Depending on the design of your web app, checking once for scrollbar visibility (e.g., on componentDidMount) may not be the best option, since some DOM elements may continue to load after the component initially mounts and the height of the page may change.
I hope this is helpful.
If you have a wrapper around the element that has the scroll you can detect the width difference.
<div className="wrapper">
<div className="scrollingContent">
Very long content here
</div>
</div>
const scrollBarWidth = this.wrapper.clientWidth - this.scrollingContent.clientWidth;
this.setState({ scrollBarWidth });
Most of the time (depending on edge cases where elements are sized differently). You can use an element ref to check if the scrollWidth is greater than the current width (or height for vertical scroll). The ref might not update scroll properties with useEffect hence why you need state in the dependencies array. Plus you will likely want to add a window resize event listener to run the same code.
const ref = useRef(null);
const [hasScrollBar, setHasScrollBar] = useState(false);
useEffect(() => {
function updateState() {
const el = ref.current;
el && setHasScrollBar(el.scrollWidth > el.getBoundingClientRect().width);
}
updateState();
window.addEventListener('resize', updateState);
return () => window.removeEventListener('resize', updateState);
}, [state]);
<div ref={ref} style={{ overflowX: 'auto' }}>
{state}
</div>

Resources