why would changes to the Ref object trigger rerender in this code - reactjs

as shown in the react-native animation demo on https://reactnative.dev/docs/animated
const fadeAnim = useRef(new Animated.Value(0)).current // Initial value for opacity: 0
React.useEffect(() => {
Animated.timing(
fadeAnim,
{
toValue: 1,
duration: 10000,
}
).start();
}, [fadeAnim])
return (
<Animated.View // Special animatable View
style={{
...props.style,
opacity: fadeAnim, // Bind opacity to animated value
}}
>
{props.children}
</Animated.View>
);
}
the changes to the fadeAnim would trigger rerender of the component, which I don't understand.
the react documentation never mentions the ref object is part of state either.
am I missing something?

Im not sure but I think it will still work even if you remove fadeAnim in the dependency of useEffect. I never tried using refs as a dependency of hooks before and it works as expected.

Related

React - Framer motion: prevent initial prop from triggering

I have a basic CRUD application where I'm able to add and remove items on a list, which is animated. This works well with framer-motion, but I'm having troubles with the following:
Whenever I delete an item from the list, the following function is called:
const handleDeleteFormation = async (id) => {
await axios({
method: "DELETE",
url: `http://localhost:1337/formations/${id}`
})
getFormations()
}
As you can clearly see, I'm calling the getFormations() function after the deletion to re-render the updated list:
async function getFormations() {
const res = await axios.get('http://localhost:1337/formations')
setFormations(res.data)
console.log(res.data)
}
This will results in a re-render of the list, which is good. But... right after the re-render, the initial prop of the motion.div will be triggered again which results in conflicting visuals.
<AnimatePresence exitBeforeEnter initial={false}>
{formations && formations.map((formation, index) =>
<motion.div
key={formation.id}
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -10 }}
transition={{ type: "tween", ease: 'easeOut', duration: 0.2 }}
>
...
My question: is it possible to prevent the initial prop from triggering right after the list-update?
You're looking for <AnimatePresence initial={false}>.
From the docs:
Suppressing initial animations
By setting initial={false} on AnimatePresence, components present when
AnimatePresence first loads will start in their animate state. Only
components that enter after this initial render will animate in.

React native no re-render after updating state array

I have the following code (full example):
import React, { useState, useEffect } from 'react';
import { SafeAreaView, View, Button, StyleSheet, Animated } from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
const App = () => {
const [blocks, setBlocks] = useState([]);
const CreateBlockHandler = () => {
let array = blocks;
array.push({
x: new Animated.Value(0),
y: new Animated.Value(0)
});
setBlocks(array);
RenderBlocks();
};
const MoveBlockHandler = (index, event) => {
Animated.spring(blocks[index].x, { toValue: event.nativeEvent.x }).start();
Animated.spring(blocks[index].y, { toValue: event.nativeEvent.y }).start();
};
const RenderBlocks = () => {
return blocks.map((item, index) => {
return (
<PanGestureHandler key={index} onGestureEvent={event => MoveBlockHandler(index,event)}>
<Animated.View style={[styles.block, {
transform: [
{ translateX: item.x },
{ translateY: item.y }
]
}]} />
</PanGestureHandler>
)
});
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.pancontainer}>
<RenderBlocks />
</View>
<Button title="Add block" onPress={CreateBlockHandler} />
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
},
pancontainer: {
width: '95%',
height:'75%',
borderWidth: 1,
borderColor: 'black'
},
block: {
width: 50,
height: 50,
backgroundColor: 'black'
}
});
export default App;
What does this code do? It's a big square, and a button below it. When I click on the button, a new black square (50x50) is made in the big square. I do this by creating a new array element (the array = blocks). This is done in the function CreateBlockHandler. This does not work correctly!
The function MoveBlockHandler makes the little squares movable. This works!
What does not work? When I create a new black square, the black square is not rendered on the screen. Only when I refresh, the square is rendered. The square is created through CreateBlockHandler, because when I do a console.log(blocks) in that function, I can see that a new array element is added.
How can I force this code to do a full re-render with all the array elements? I tried to wrap the render of the square in a separate function (RenderBlocks) and I'm calling this function every time a new square is made (last line in CreateBlockHandler). The function is called (I can check this with a console.log()) but no squares are rendered.
When you assign blocks to array the reference gete copied which mutates the state, so it doesn't re-render on setState.
const CreateBlockHandler = () => {
let array = [...blocks];
array.push({
x: new Animated.Value(0),
y: new Animated.Value(0)
});
setBlocks(array);
RenderBlocks
There are multiple issues with your code.
As kooskoos pointed out, your state remains referentially equal (it's the same array, only the elements change). This will not trigger re-render.
Also, you are manipulating state of the App component. RenderBlocks component's props and state remain unchanged which implies that they don't need to be re-rendered. Since the component is an anonymous function and is recreated during every render of App, it probably gets re-rendered anyways.
In addition, you are directly calling RenderBlocks, which looks like a component. That is unnecessary and will do nothing here, but if it had any hooks, it would cause problems.
You should probably also conform to the convention that components are PascalCase capitalised and callbacks snakeCase capitalised.

Animate conditionally React component with react-spring

I have a react component and I want to animate it when the state of the component change.
I am using useReducer const [state, dispatch] = useReducer(reducer, initialState);
Then on click I update the state.isOpen from false to true or vice versa. In the same component I have this
const wrapperStyles = useSpring({
transform: state.isOpen ? 'translate3d(0, 0, -300px)' : 'translate3d(0, 0, 0)'
});
Finally the component consumes it like this <StyledWrapper style={wrapperStyles}>
I expect the transform the be applied and changed when the state changes, but it does not.
I have no idea what I am doing wrong, I am following their simple instructions on the website.
Edit: I tried with opacity instead of transform and it works fine, so I am doing something wrong with the css transform?
I am answering my own question, so the reason why it wasn't working is that I was using different values.
from 0 to 300px. It must be the same value so changing to
transform: state.isOpen ? 'translate3d(0, 0, -300px)' : 'translate3d(0, 0, **0px)**'
where 0px makes the difference :)
Thanks for the help!
You read this page.
I search react-spring,
If you use transform, You use import {useTransition, animated} from 'react-spring'
not useSpring()
https://www.react-spring.io/docs/hooks/use-transition
const [items, set] = useState([...])
const transitions = useTransition(items, item => item.key, {
from: { transform: 'translate3d(0,-40px,0)' },
enter: { transform: 'translate3d(0,0px,0)' },
leave: { transform: 'translate3d(0,-40px,0)' },
})
return transitions.map(({ item, props, key }) =>
<animated.div key={key} style={props}>{item.text}</animated.div>
)

Modal component renders Twice on open

I'm using react-spring to animate a Modal based on #reach/dialog. The Modal can have any children. In the children I'm fetching some data based on some prop.
The problem is that the fetch call is made two times on opening the modal. I think it has probably to do with how I'm managing the state and that is causing re-renders.
I'v tried memoizing the children inside the modal and it didn't work, so I think that the problem is outside of the Modal component.
Here is something close to my code and how it is working https://codesandbox.io/s/loving-liskov-1xouh
EDIT: I already know that if I remove the react-spring animation the double rendering doesn't happen, but I want to try keeping the animation intact.
Do you think you can help me to identify where is the bug? (Also some tips on good practice with hooks are highly appreciated).
it renders three times because your return component has transitions.map since you have three item inside the
from: { opacity: 0 }
enter: { opacity: 1 }
leave: { opacity: 0 }
the {children} was called two times when the isOpen is true
you can fix the issue with just removing the from: { opacity: 0 } and leave: { opacity: 0 }
so change your modal.js => transitions
const transitions = useTransition(isOpen, null, {
enter: { opacity: 1 }
});
I checked and it is rendered twice because of animation in a Modal component when an animation is finished, modal is rendered second time when I commented out fragment responsible for animation, Modal renders only once.
const Modal = ({ children, toggle, isOpen }) => {
// const transitions = useTransition(isOpen, null, {
// from: { opacity: 0 },
// enter: { opacity: 1 },
// leave: { opacity: 0 }
// });
console.log("render");
const AnimatedDialogOverlay = animated(DialogOverlay);
// return transitions.map(
// ({ item, key, props }) =>
// item && (
return (
<AnimatedDialogOverlay isOpen={isOpen}>
<DialogContent>
<div
style={{
display: `flex`,
width: `100%`,
alignItems: `center`,
justifyContent: `space-between`
}}
>
<h2 style={{ margin: `4px 0` }}>Modal Title</h2>
<button onClick={toggle}>Close</button>
</div>
{children}
</DialogContent>
</AnimatedDialogOverlay>
);
// )
// );
};
The problem is, that at the end of the animation AnotherComponent remounts. I read similar problems about react-spring. One way could be, that you lift out the state from AnotherComponent to the index.js. This way the state will not lost at remount and you can prevent refetching the data.
const AnotherComponent = ({ url, todo, setTodo }) => {
useEffect(() => {
if (todo.length === 0) {
axios.get(url).then(res => setTodo(res.data));
}
});
....
}
Here is my version: https://codesandbox.io/s/quiet-pond-idyee

Measure height of a element in semantic react ui

I am trying to measure the height of a component, If i try to get the ref and find the offsetHeight, the ref is returned to be null always. so How to measure the height for a react-semantic-ui component.
<Container ref={rf=>this.container=rf} style={{overflowY:"auto"}}>
<Item.Group divided >
<ItemList items={items}></ItemList>
</Item.Group>
</Container>
here this.container is always null
What other ways are there to measure the height of a react-semantic-ui component ?
#bogdan-surai, can you test the following code:
componentDidMount() {
const container = this.container;
if (!container) {
return;
}
const specs = container.getBoundingClientRect();
console.log(specs);
}
#salman.zare Yep. It doesn't work when componentDidMount just have fired. I guess the cause is height really equals 0 at that moment. But we can run setInterval to get height in few time later.
This code works for me
componentDidMount() {
this.intervalId = setInterval(() => {
const container = this.container;
if (!container) {
return;
}
const specs = container.getBoundingClientRect();
console.log(specs);
console.log(this.scrollComponent.offsetHeight);
this.setState(() => ({ height: specs.height }));
}, 100);
}
Then I see it in the browser's console.
DOMRect {x: 0, y: 120, width: 693, height: 0, top: 120, …}
DOMRect {x: 0, y: 16, width: 678, height: 4068, top: 16, …}
You should use this.container.offsetHeight in componentDidMount or componentDidUpdate methods.
It is possible to wrap your components into <div ref={...}></div>. Then you can get height of the div. If your higher component doesn't have margins, height of div and your component will be the same.
Note:
When the ref attribute is used on a custom component declared as a class, the ref callback receives the mounted instance of the component as its argument.
When the ref attribute is used on an HTML element, the ref callback receives the underlying DOM element as its argument.
You may not use the ref attribute on functional components because they don’t have instances
Returned values from 1 and 2 have different properties and methods. There is a doc https://reactjs.org/docs/refs-and-the-dom.html

Resources