I create a simple animation using react native reanimated but I can't access the numeric value of Reanimated Value
I using victory native pie chart, and I want to make a simple effect that pie angle goes from 0 to 360 but I've tried react-native animated API it works well with add listener but I want use reanimated for performance issue
the animation effect that I'm looking for that the chart starts from 0 to 360
run correctly with react-native Animated API:
const Chart = props => {
const { data, width, height } = props;
const endAngleAnimatedValue = new Value(0);
const [endAngle, setEndAngle] = useState(0);
const runTiming = Animated.timing(endAngleAnimatedValue, {
duration: 1000,
to: 360
});
useEffect(() => {
endAngleAnimatedValue.addListener(_endAngle => setEndAngle(_endAngle));
runTiming.start();
return () => {
endAngleAnimatedValue.removeAllListeners();
};
}, [endAngleAnimatedValue]);
return (
<VictoryPie
data={data}
width={width}
height={height}
padding={0}
startAngle={0}
endAngle={endAngle}
style={{
data: {
fill: ({ datum }) => datum.fill,
fillOpacity: 0.7
}
}}
/>
);
};
How I can achieve the desired output with reanimated?
I totally forgot about this question,
Here we can have two implementations if using react-native-reanimated v1:
1. using react-native-svg and some helpers from react-native-redash
2. using `const AnimatedPie = Animated.createAnimatedComponent(VictoryPie)` then passing `endAngleAnimatedValue` as a prop.
In reanimated v2:
You can use explicitly .value to animated sharedValues, derivedValues, but also you have to createAnimatedComponent
Related
I'm trying to do something extremely simple - have my button be transparent while not pressed, and change its background color to a different colour gradually when a user presses it. To achieve that, I'm using React Native Reanimated. Unfortunately, there's something wrong with my animation and I'm not sure what. The issue is this:
I change isPressed to true when the button is pressed and to false when the user moves his finger away from the button. Then I use that isPressed boolean to change the progress and finally use that progress to interpolate the colour from transparent to my other colour. Sadly what happens is this:
I press on the button and instead of the button changing its background color almost instantly, it takes like 5 seconds before turning to colors.primary50. Then if I unpress the button, it takes another 5 or so seconds to turn back to transparent. Also I don't really see gradual change in color, it just changes instantly.
const TertiaryButton: FunctionComponent<Props> = ({ title, wide = false, style, ...rest }) => {
const [isPressed, setIsPressed] = useState(false);
const progress = useSharedValue(0);
useEffect(() => {
progress.value = withTiming(isPressed ? 1 : 0, { easing: Easing.out(Easing.cubic), duration: 1000 });
}, [isPressed, progress]);
const rStyle = useAnimatedStyle(() => {
const backgroundColor = interpolateColor(progress.value, [0, 1], ['transparent', colors.primary50]);
return { backgroundColor };
});
return (
<Pressable onPressIn={() => setIsPressed(true)} onPressOut={() => setIsPressed(false)} {...rest}>
<Button
wide={wide}
style={[
style,
{
...rStyle,
},
]}
>
<ButtonText variant="h4">{title}</ButtonText>
</Button>
</Pressable>
);
};
Try to change the progress value instantly inside onPressIn and onPressOut.
onPressOut={() => {
console.log("onPressOut")
isPressed.value = withTiming(0)
}}
onPressIn={() => {
console.log("onPressIn")
isPressed.value = withTiming(1)
}}
The possible reason for this not working is that the Button component may not be an Animated component. You need to make sure that Button is an animated component. If it's a custom/library component, you can wrap it with Animated.createAnimatedComponent(...) to make it an Animated component:
const AnimatedButton = Animated.createAnimatedComponent(Button);
I wanna update the component and change the lines.
When I try to send new props with coordinates, old coordinates also staying.
const PolylineDecorator = withLeaflet(props => {
const { positions } = props;
const polyRef = useRef();
useEffect(() => {
const polyline = polyRef.current.leafletElement; //get native Leaflet polyline
const { map } = polyRef.current.props.leaflet; //get native Leaflet map
L.polylineDecorator(polyline, {
patterns: props.patterns,
}).addTo(map);
}, []);
return <Polyline ref={polyRef} {...props} opacity="0" />;
});
It is tricky to debug this one out without trying in the local machine. But It looks like you need to pass positions to the useEffect as follows, so it will re-render every time positions are changing.
useEffect(() => {
const polyline = polyRef.current.leafletElement; //get native Leaflet
polyline
const { map } = polyRef.current.props.leaflet; //get native Leaflet map
L.polylineDecorator(polyline, {
patterns: props.patterns,
}).addTo(map);
}, [positions]);
I am currently making a Graph component that fetches data from an API, parses the data to be used with a graph library, and then renders the graph. I have all of that working right now, but the issue I am having is with adding the ability to filter. The filtering I am currently doing is done by the parent of the Graph component, which will set the filters prop in the component which is then processed by a useEffect. But this seems causes some portions to re-render and I am trying to prevent. Below is what I have roughly speaking.
Rough example of Parent:
const Parent = (props) => {
const [filters, setFilters] = useState({});
//there are more state values than just this one also cause
//the same problem when their setState is called.
return (
<Graph filters={filters} />
<FilterComponent
onChange={(value) => setFilters(value)}
/>
)
}
export default Parent
Rough example of Child:
const Graph = (props) => {
const [nodes, setNodes] = useState({});
const [links, setLinks] = useState({});
const [velocity, setVelocity] = useState(0.08);
const createGraph = async () => {
//fetches the data, processes it and then returns it.
//not including this code as it isn't the problem
return {
nodes: nodes,
links: links,
};
}
//loads the graph data on mount
useEffect(() => {
const loadGraph = async () => {
const data = await createGraph();
setNodes(data.nodes);
setLinks(data.links);
};
loadGraph();
}, []);
//filters the graph on props change
useEffect(() => {
//this function uses setNodes/setLinks to update the graph data
filterGraph(props.filter);
}, [props.filters]);
return (
<ForceGraph2D
graphData={{
nodes: nodes,
links: links,
}}
d3VelocityDecay={velocity}
cooldownTicks={300}
onEngineStop={() => setVelocity(1)}
/>
);
}
export default Graph
My main issue is that whenever the FilterComponent updates, while I want it to update the graph data, this seems to re-render the Graph component. This causes the graph to start moving. This graph library creates a graph which kinda explodes out and then settles. The graph has a cooldown of 300, and after which it isn't supposed to move, which is where onEngineStop's function is called. But changing the filter state in Parent causes the graph to regain it's starting velocity and explode out again. I want to be able to change the filter state, update the graph data, without re-rendering it. I've looked into useMemo, but don't know if that's what I should do.
I'm fairly new to React having just started two weeks ago, so any help is greatly appreciated! Also, this is my first post on stackOverflow, so I apologize if I didn't follow some community standards.
Edit
I was asked to include the filterGraph function. The function actually was designed to handle different attributes to filter by. Each node/link has attributes attached to them like "weight" or "size". The filterComponent would then pass the attr and the value range to filter by. If a component falls outside that range it becomes transparent.
const Graph = (props) => {
...
//attr could be something like "weight"
//val could be something like [5,10]
const filterGraph = ({ attr, val }) => {
for (const [id, node] of Object.entries(nodes)) {
const value = nodes[id][attr];
if (val.length == 2) {
if (val[0] > value || val[1] < value) {
const color = nodes[id]["color"] || "#2d94adff";
nodes[id]["color"] = setOpacity(color, 0)
);
} else {
const color = nodes[id]["color"] || "#2d94adff";
nodes[id]["color"] = setOpacity(color, 1)
);
}
}
}
setNodes(Object.values(this.nodes));
}
...
}
In your example you mention that filterGraph uses setNodes/setLinks. So everytime the filter changes (props.filters) you will do 2 setState and 2 rerenders will be triggered. It can be that React will batch them so it will only be 1 rerender.
Depending on what filterGraph does exactly you could consider let it return filteredNodes en filteredLinks without putting the filterGraph in a useEffect.
Then pass the filteredNodes en filteredLinks to the graphData like graphData={{
nodes: filteredNodes,
links: filteredLinks,
}}
This way you won't trigger extra rerenders and the data will be filtered on every render. which is already triggered when the props.filters change. This is an interesting article about deriving state https://kentcdodds.com/blog/dont-sync-state-derive-it
Since you also mention that there are more state values in the parent you could make the component a pure component, which means it won't get rerendered when the parent renders but the props that are being passed don't change
https://reactjs.org/docs/react-api.html#reactmemo
Also it's better to include createGraph in the useEffect it's being used or wrap it in a useCallback so it won't be recreated every render.
const Graph = React.memo((props) => {
const [nodes, setNodes] = useState({});
const [links, setLinks] = useState({});
const [velocity, setVelocity] = useState(0.08);
//loads the graph data on mount
useEffect(() => {
const createGraph = async () => {
//fetches the data, processes it and then returns it.
//not including this code as it isn't the problem
return {
nodes: nodes,
links: links,
};
}
const loadGraph = async () => {
const data = await createGraph();
setNodes(data.nodes);
setLinks(data.links);
};
loadGraph();
}, []);
const { filteredNodes, filteredLinks } = filterGraph(props.filter)
return (
<ForceGraph2D
graphData={{
nodes: filteredNodes,
links: filteredLinks,
}}
d3VelocityDecay={velocity}
cooldownTicks={300}
onEngineStop={() => setVelocity(1)}
/>
);
})
export default Graph
For some reason, I just cannot get the most basic example of react-use-gesture to work. What I am trying to do is just have a square follow my mouse location when you drag it. I copy-pasted the example from their documentation multiple times (https://github.com/pmndrs/react-use-gesture) and still cannot get it to work. I just don't understand it anymore. I created a stackblitz to show you my code. What am I still doing wrong?
Stackblitz with code: https://stackblitz.com/edit/react-mg2u8p?file=src/Square.js
I will also include the most relevant code here:
import React from "react";
import { useSpring, animated } from "react-spring";
import { useDrag } from "react-use-gesture";
const Square = () => {
const [{ x, y }, set] = useSpring(() => ({ x: 0, y: 0 }));
const bind = useDrag(({ down, movement: [mx, my] }) => {
set({ x: down ? mx : 0, y: down ? my : 0 });
});
return (
<animated.div
{...bind()}
className="Square"
style={{ x, y, touchAction: "none" }}
/>
);
};
export default Square;
It's a version problem.
Your example uses code for react-spring version 9+, but the version of react-spring you're using in the example is 8.0.27.
The example the documentation gives for version 8 is this:
import { useSpring, animated } from 'react-spring'
import { useDrag } from 'react-use-gesture'
function PullRelease() {
const [{ xy }, set] = useSpring(() => ({ xy: [0, 0] }))
// Set the drag hook and define component movement based on gesture data
const bind = useDrag(({ down, movement }) => {
set({ xy: down ? movement : [0, 0] })
})
// Bind it to a component
return (
<animated.div
{...bind()}
style={{
transform: xy.interpolate((x, y) => `translate3d(${x}px, ${y}px, 0)`),
}}
/>
)
}
So in your case you would only need to change PullRelease to Square and add className="Square" to the animated.div like you had it in your question.
For both the documentation on the v8 and v9 implementation of this using React UseGesture see this.
If you want to use the v9 version, you currently need to install react-spring#next according to the documentation (see previous link).
I am trying to figure out how to make eslint with the rule react-hooks/exhaustive-deps happy and also use the build in Animated library in react native to make animations with useEffect.
The following code should highlight the button when clicked, by overlaying it with a colored view.
const Component = props => {
const [active, setActive] = useState(false);
const [opacity, setOpacity] = useState(new Animated.Value(0));
useEffect(() => {
if (active) {
Animated.timing(opacity, {
toValue: 1,
duration: 200,
useNativeDriver: true
}).start();
} else {
setOpacity(new Animated.Value(0))
}
}, [active, opacity]); // <- Works fine without `opacity`, but eslint wants this
return (
<View>
<Animated.View style={{backgroundColor: "blue", opacity}} />
<TouchableOpacity onPress={()=> setActive(!active)} />
</View>
)
};
Is there any way of doing this (with useCallback, useMemo, etc.) without disabling the rule?
You don't need to call setOpacity, instead you can use setValue:
opacity.setValue(0)
There is also no need to add opacity to dependencies, because it never changes. ESLint doesn't always get it right.