When is it safe to call `measureInWindow`? - javascript

I'm trying to figure out at what point in a component lifecycle I can call measureInWindow on a view and be guaranteed to get the right values back. For example:
const Measure = () => {
const ref = React.useRef();
React.useLayoutEffect(() => {
ref.current.measureInWindow((x, y) => {
// y is 0
})
});
return <View ref={ref} />;
};
I get 0 for y in the above example, rather than 88 which is the right value because the navigation bar is that tall. If I put the measureInWindow call inside a setTimeout with 0ms then I get the correct value back.
I have also tried using using useEffect instead of useLayoutEffect and I get the same result.
EDIT
A comment on this post got me looking at the onLayout prop for View. My initial tests indicate that I can call measureInWindow successfully there. However, I'd still like to find some concrete documentation beyond just my cursory testing.
<View
ref={ref}
onLayout={e => {
ref.current.measureInWindow((x, y) => {
console.log('onLayout', y);
});
}}
/>

Related

React - How to change number of array items mapped based on screen width?

I have an array that I am mapping over and rendering a component for each item. I want to reduce the number of items mapped from 5 to 4 at screen widths below 1194px, and to increase it back to 5 items above this width.
This is what I have tried so far but it causes the app to freeze if I mess around with the window size, and also initially renders 5 regardless of screen width.
This seems complex, is there an easier way to do this?
P.S - I have only included the relevant parts of the component.
// Section for loading specific number of cards
// set number of anime to map via state so it can be change in media queries
const [numberToMap, setNumberToMap] = useState(5);
//dynamically load the anime cards so that they can use the 'number' variable
const mapAnime = (number) => (
<div className='anime-list-wrapper'>
{animeData?.data?.slice(0, (number)).map((anime) => (
<AnimeCard anime={anime} key={anime.id} />
))}
</div> )
window.matchMedia('(min-width: 1194px)').addEventListener('change', () => setNumberToMap(4));
window.matchMedia('(max-width: 1195px)').addEventListener('change', () => setNumberToMap(5));
return (
<div className='container'>
<h3 className='category-title'>{categoryTitle}</h3>
{!isFetching
? <div className="wrapper">
{mapAnime(numberToMap)}
</div>
You're not cleaning up when the component unmounts. Basically you have a lot of event handlers for the change event whenever the components unmounts.
you should reverse addEventListener by using removeEventListerner("change", handler), but the requires to keep a reference to the handler.
Practically speaking, you need to set the event listeners in useEffect and return a clean up function.
const minHandler = () => setNumberToMap(4);
const maxHandler = () => setNumberToMap(5);
useEffect(() => {
const minMedia = window.matchMedia("(min-width: 1194px)");
minMedia.addEventListener("change", minHandler);
const maxMedia = window.matchMedia("(max-width: 1195px)");
maxMedia.addEventListener("change", maxHandler);
return () => {
minMedia.removeEventListener("change", minHandler);
maxMedia.removeEventListener("change", maxHandler);
};
}, []);
Or better still, you use a custom hook for matchMedia, which will take care of the cleanup.
You're updating the react state on each screen width change. So when you were messing with it in the dev tools, you got freezes because of the sheer amount of state changes. The screen won't change often, but generally it is a good idea to avoid frequent unoptimized state updates. Anyways, your listeners are listening to the same thing, so one is unnecessary. Also, update the state only when events match.
Do this and you're good:
useEffect(() => {
const mediaQuery = window.matchMedia('(max-width: 1194px)');
const handler = (event: MediaQueryListEvent) => {
if (event.matches) {
setNumberToMap(4);
} else {
setNumberToMap(5);
}
};
mediaQuery.addEventListener('change', handler);
return () => mediaQuery.removeEventListener('change', handler);
}, []);

react-spring animation only working on click, not on hover

I have a basic rotating cube I've made with react-three-fiber, and onPointerOver (also tried onPointerEnter) I want to smoothly rotate it to its starting position via useSpring. However, not only does it not do so when I hover, but it will only do so onClick. I have a totally different function that executes onClick -- and if I disabled that prop then the rotation reset fails altogether.
I've got a pretty simple set up in my Box component:
export const Box = () => {
const [active, setActive] = useState(false);
const boxRef = useRef<Mesh>();
const starterRotation = [0, 1, 1];
useFrame(() => {
if (boxRef.current) {
boxRef.current.rotation.x += 0.01;
boxRef.current.rotation.y -= 0.01;
}
});
const [resetRotation, setSpring] = useSpring(() => ({
rotation: starterRotation
})); // trying to use the `set` syntax to call the useSpring and smoothly animate to a certain rotation.
const springs = useSpring({ scale: active ? 1.5 : 1, config: config.wobbly });
return (
<animated.mesh
// #ts-ignore
rotation={resetRotation.rotation}
scale={springs.scale}
onClick={() => setActive(!active)}
onPointerOver={() => setSpring.start()}
ref={boxRef}
>
<boxGeometry args={[2, 1, 2]} />
<meshStandardMaterial color="royalblue" />
</animated.mesh>
);
};
Here's a live example: https://codesandbox.io/s/react-spring-rotating-cube-np4v6
From what I can tell in their docs this is the correct way. I also saw some github issues discussions on it, and there seemed to be some concern about the second argument of the useSpring deconstruction not working properly, but the rotation works for me -- it's jut the triggering event that won't work.
It does work, the issue is you're still updating the rotation based on the ref in a useFrame call so that's updated every frame. Which will override the animation value.
The second issue is that if you stoped the useFrame from animating rotation it won't work because the spring's internal value will be set to [0,1,1] but that's what you want it to animate to.
This is a good opportunity to use from and to props of useSpring, what I would do is use a ref to stop the useFrame animation and then use the ref to get the current values of the rotation to use as from in the springSet.start function and then to which is the starterRotation value you've declared.
You can see this in effect here – https://codesandbox.io/s/react-spring-rotating-cube-forked-1j02g?file=/src/components/Box.tsx

What is the proper way to smoothly scale an element to nothing and then back to its original size

This is how I do it in React:
const animate_boxes = () => {
inner_ref.current.style.transform = "scale(0)";
setTimeout(() => {
if (inner_ref && inner_ref.current) {
inner_ref.current.style.transform = "scale(1)";
}
}, 200);
};
For some reason, it is not dependable. the setTimeout may not always get run. Still haven't figured out why yet. But If there is an alternative way to do it, then please write a solution.
thanks
May I suggest the react-spring library?
Use framer motion library with scale property passed in as an array of values
eg :
import {motion} from 'framer-motion'
const Component = () => {
return (
<motion.div
initial={{scale: 1}}
animate={{scale: [0.1, 1]}}
>
hi
</motion.div>
)
}
Refer this docs for more examples - Docs

Using React states with delay

I am using a library called react-easy-state for my state management but I believe this questions is not directly related to this library rather a general question.
Having a code like this in my App.js :
function App() {
const [isChanged, setIsChanged] = React.useState(false);
console.log(store.counter);
React.useEffect(() => {
setTimeout(() => {
setIsChanged(true);
}, store.counter);
}, []);
return (
<div className="App">
{isChanged && <h1>Hello CodeSandbox</h1>}
<Change />
</div>
);
}
Which has a child <Changed /> that looks like this :
export default function Change() {
React.useEffect(() => {
store.rndNum = ~~(Math.random() * 6) + 11;
}, []);
store.counter = ~~Math.pow(1.555, store.rndNum) * 20;
The child is supposed to generate a random number and then modify the random number and store it in another variable in my store. The modified value is supposed to slow down the appearance of an HTML element which toggled and triggered by setTimeout. However my console shows that number that is being generated is not matching with my code and as it generates 20 for some reason and then proceeds to generate the correct number. In the mean time I would like to wait until the value in my store in updated and then run setTimeout. Whereas currently setTimeout takes either 0 or 20 as the timer and executes the code without any delay.
code to reproduce : https://codesandbox.io/s/hopeful-cdn-vg7kh
You are getting 20 first because of this operation.
store.counter = ~~Math.pow(1.555, store.rndNum) * 20;
Why? Because store.rndNum at that time is still "" (declared on store as "") and this part
~~Math.pow(1.555, store.rndNum)
will always be 1.
Hooks are asynchronous, so useEffect will not change the rndNum value first.
Edit for my comment:
React.useEffect(() => {
if (!isNaN(store.rndNum)) {
store.counter = ~~Math.pow(1.555, store.rndNum) * 20;
}
}, [store.rndNum]);

React parent component check if child will render

I have a child component that depending on some of its props will end up rendering something or not. The render function of the children looks something like this:
render() {
if (props.a == 'foo' && props.b == 'bar') {
return (<p> Hey There </p>);
} else if {props.a == 'z') {
return (<p> Hey There </p>);
} // more conditions
} else {
return null
}
}
In the parent component I am rendering several child components, and I need to know how many of them will render, because depending on that number I will do something or not. I don't want to repeat the conditional logic from the child to the parent, but I don't know how from a parent I can find out if the children will render or not.
What you're trying to do is a bit of an anti-pattern in React.
If I understand your question, the children render output would influence their parent's render ouptput, which is likely to get you stuck in a render loop.
I suggest you keep the children components as simple as possible and hoist the conditional logic to the parent which will allow you to count how many children you'll be rendering in place.
Please let me know if I got your question wrong.
Although I agree with perpetualjourney's answer, I thought I could give you a possibility of counting the children.
The easiest would be to save the rendering of the children first to a variable and then to render that result.
var kids = this.props.items.map( (k, i) => <Kid condition={k} key={i} /> );
var totalKids = kids.reduce( (k, i) => k + (i.type( i.props ) ? 1 : 0), 0);
Now, this will render your children twice, so it is not the best when you already have a performance heavy method
const Kid = ({ condition }) => condition % 3 === 0 ? <h1>{ condition }</h1> : null;
class ConditionalKids extends React.Component {
render() {
var kids = this.props.items.map( (k, i) => <Kid condition={k} key={i} /> );
var totalKids = kids.reduce( (k, i) => k + (i.type( i.props ) ? 1 : 0), 0);
return <div>
<p>Rendered in total { totalKids }</p>
{ kids }
</div>;
}
}
const items = [...new Array(5)].map( i => parseInt( Math.random() * 10 ) );
console.log( items );
const target = document.querySelector('#container');
ReactDOM.render( <ConditionalKids items={items} />, target );
<script id="react" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script id="react-dom" src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<div id="container"></div>
One way of handling this problem is to use state.
In this CodeSandbox example, I use the if/else logic to update the component's state. This is done inside the componentWillReceiveProps lifecycle method.
Your render method is simplified to a switch statement.
If you want to call an imperative API based on the if/else logic you have, you can do that inside your componentDidUpdate lifecycle method. This is also simplified to a switch statement.
That's a very pertinent question that affects applications of any complexity. Unfortunately there's no clear way to model this using React today. I've done a ton of React and would recommend what #perpetualjourney said: hoist the logic to a common parent. Sometimes you'll need to move it way up, but do it instead of messing around with React.Children.map or React.Context since they'd create dependency between the components and would make it harder for you to move them around if needed.
const hasA = computeHasA({ ... });
const hasB = computeHasB({ ... });
const hasAny = hasA || hasB;
if (hasAny) {
return (
<Tabs>
{hasA && <Tab title="A">Some content</Tab>}
{hasB && <Tab title="B">Another content</Tab>}
</Tabs>
);
}
When you really can't move to logic upwards, for example, if the children do network requests, which is probably an edge-case, I'd recommend passing a prop used to report whether they have content or not and storing it into the parent's state:
const [numberOfRenderedChildren, setNumberOfRenderedChildren] = useState(0);
const incrementNumberOfRenderedChildren = () => {
// Use a callback to prevent race conditions
setNumberOfRenderedChildren(prevValue => prevValue + 1);
};
return (
<MyContainer isVisible={numberOfRenderedChildren > 0}>
{items.map(item => (
<ComplexChild
key={item.id}
// Avoid spreading props like ...item. It breaks semantics
item={item}
reportHasContent={incrementNumberOfRenderedChildren}
/>
)}
</MyContainer>
);
It could get more complex if you need loaders and error messages, but then I'd fire all the requests in the parent. Try to keep it simple, easy to read, even if it means more verbosity. You won't regret when you need to come back to those files a few weeks later.

Categories

Resources