Custom Drawer using Reanimated 2 useSharedValue and useAnimatedStyle - react-native

I am trying to implement drawing navigation like in the image above using react native reanimated 2.
import React, { useState } from 'react'
import { Text } from 'react-native'
import { createDrawerNavigator } from '#react-navigation/drawer'
import Animated, { useSharedValue, interpolate, useAnimatedStyle } from 'react-native-reanimated'
import Screen1 from '../screens/screen1'
import Screen2 from '../screens/screen2'
import Screen3 from '../screens/screen3'
const Drawer = createDrawerNavigator()
const DrawerNavigator = () => {
const [translateX, setTranslateX] = useState(new Animated.Value(0))
//const translateX = useSharedValue(0)
const scale = Animated.interpolateNode(translateX, {
inputRange: [0, 1],
outputRange: [1, 0.8],
});
// const animatedStyle = useAnimatedStyle(() => {
// const scale = interpolate(
// translateX.value,
// [0, 1],
// [1, 0.8]
// );
// return{ transform: [{ scale }] }
// })
const animatedStyle = { transform: [{ scale }] };
return (
<Drawer.Navigator drawerContent={ (props) => {
setTranslateX(props.progress)
// translateX.value = props.progress
return <Text>Hello madan</Text>
}} >
<Drawer.Screen name="screen1">
{() => <Screen1 style={ animatedStyle } />}
</Drawer.Screen>
<Drawer.Screen name="screen2" component={ Screen2 } />
<Drawer.Screen name="screen3" component={ Screen3 } />
</Drawer.Navigator>
)
}
I can achieved it by using code above. But, i am using Animated.Value. I want to use useSharedValue() and useAnimatedStyle hook from reanimated 2. I tried to implement that which can be seen in the code above which are commented. But, its not working. App crashes and give error in JSCRuntime file.

Related

Props.progress issue after updating react reanimated to 2.3.0

I was working with react native reanimated 1.13.1, but I decided to upgrade because of the new content and new features. I have been trying to fix an animation that I had on my Custom Drawer the animation consists on scaling the screen differently when the user accesses the drawer. It was working perfectly on 1.13.1 but on 2.3.0 I can't seem to get the scaling right with props.progress.
This is the code for the drawer.
import React, { useEffect } from 'react';
import { Container } from './styles';
import { createDrawerNavigator } from '#react-navigation/drawer';
import { Register } from '../../screens/Register';
import { CustomDrawerContent } from '../CustomDrawerContent';
import { Dashboard } from '../../screens/Dashboard';
import { Summary } from '../../screens/Summary';
//REDUX
import { connect } from 'react-redux';
import { setSelectedTab } from '../../stores/tab/tabActions';
import { Settings } from '../../screens/Settings';
import { useSharedValue, interpolate, useAnimatedStyle, Extrapolate, withTiming } from 'react-native-reanimated';
const Drawer = createDrawerNavigator();
function CustomDrawer({ selectedTab, setSelectedTab }: any){
function setDefaultOnlyOnce() {
setSelectedTab('Dashboard');
}
let progress = useSharedValue(0);
const scale: any = useAnimatedStyle(() => {
return {
transform: [{
scale: interpolate(
progress.value,
[0, 1],
[1, 0.8],
Extrapolate.CLAMP
)
}]
};
})
const borderRadius: any = useAnimatedStyle(() => {
return {
borderRadius: interpolate(
progress.value,
[0, 1],
[0, 26],
Extrapolate.CLAMP
)
};
})
const animatedStyle = [[ borderRadius, scale ]];
useEffect(() => {
setDefaultOnlyOnce();
}, [])
return (
<Container>
<Drawer.Navigator
drawerType='slide'
overlayColor='transparent'
drawerStyle={{
flex: 1,
width: '70%',
paddingRight: 20,
backgroundColor: 'transparent'
}}
initialRouteName="Dashboard"
drawerContent={props => {
setTimeout(() => {
progress.value = 1;
/*
PREVIOUS VERSION:
setProgress(props.progress as any);
*/
}, 0);
return <CustomDrawerContent
navigation={props.navigation}
selectedTab={selectedTab}
setSelectedTab={setSelectedTab}
/>
}}
>
<Drawer.Screen name="Dashboard">
{props => <Dashboard drawerAnimationStyle={animatedStyle} />}
</Drawer.Screen>
<Drawer.Screen name="Register">
{props => <Register drawerAnimationStyle={animatedStyle} setSelectedTab={setSelectedTab}/>}
</Drawer.Screen>
<Drawer.Screen name="Summary">
{props => <Summary drawerAnimationStyle={animatedStyle}/>}
</Drawer.Screen>
<Drawer.Screen name="Settings">
{props => <Settings drawerAnimationStyle={animatedStyle}/>}
</Drawer.Screen>
</Drawer.Navigator>
</Container>
);
}
function mapStateToProps(state: any) {
return {
selectedTab: state.tabReducer.selectedTab
}
}
function mapDispatchToProps(dispatch: any) {
return {
setSelectedTab: (selectedTab: any) => { return dispatch(setSelectedTab(selectedTab)) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CustomDrawer);
I think that the problem might be on setting progress.value where previously it setted the progress with props.progress but that doesn't seem to work for the new version.
When the progress.value is 0 the screen is completely filled (Drawer is closed) and when the progress.value is 1 the screen is resized and the drawer is opened. This, altered in the previous version with the props.progress being set on drawerContent

React-Native Animated Accordion/ Drawer/ Drop-down/ Collapsible-card

I want to implement an animated accordion list/ drawer / drop-down menu / collapsible card.
The animation should be performant and look like this:
After a lot of searching, I could find many libraries. But I wanted to implement it without any library. Also, some tutorials showed how to build one, but they were not performant.
Finally, this is how I implemented it. The complete snack code is here: https://snack.expo.dev/#vipulchandra04/a85348
I am storing isOpen (whether the menu is open or closed) in a state. Then changing that state on button press. I am using the LayoutAnimation API in React-Native to animate the opening and closing of the list. LayoutAnimation runs the animation natively, thus it is performant.
const Accordion = ({ title, children }) => {
const [isOpen, setIsOpen] = useState(false);
const toggleOpen = () => {
setIsOpen(value => !value);
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
}
return (
<>
<TouchableOpacity onPress={toggleOpen} activeOpacity={0.6}>
{title}
</TouchableOpacity>
<View style={[styles.list, !isOpen ? styles.hidden : undefined]}>
{children}
</View>
</>
);
};
const styles = StyleSheet.create({
hidden: {
height: 0,
},
list: {
overflow: 'hidden'
},
});
With this, it will fix the Vipul's demo's error: if click accordion so fast, children's opacity descending to 0. and add animation for icon
import {
Animated,
LayoutAnimation,
Platform,
StyleProp,
StyleSheet,
UIManager,
View,
ViewStyle,
} from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons;
if (
Platform.OS === 'android' &&
UIManager.setLayoutAnimationEnabledExperimental
) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
const toggleAnimation = duration => {
return {
duration: duration,
update: {
property: LayoutAnimation.Properties.scaleXY,
type: LayoutAnimation.Types.easeInEaseOut,
},
delete: {
property: LayoutAnimation.Properties.opacity,
type: LayoutAnimation.Types.easeInEaseOut,
},
};
};
interface IAccordion {
title?: JSX.Element | JSX.Element[];
children?: JSX.Element | JSX.Element[];
style?: StyleProp<ViewStyle> | undefined;
}
const Accordion = ({title, children, style}: IAccordion) => {
const [isOpen, setIsOpen] = useState(false);
const animationController = useRef(new Animated.Value(0)).current;
const arrowTransform = animationController.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '90deg'],
});
const onToggle = () => {
setIsOpen(prevState => !prevState);
const duration = 300;
const config = {
duration: duration,
toValue: isOpen ? 0 : 1,
useNativeDriver: true,
};
Animated.timing(animationController, config).start();
LayoutAnimation.configureNext(toggleAnimation(duration));
};
return (
<View style={style ? style : styles.accordion}>
<TouchableOpacity onPress={onToggle} style={styles.heading}>
{title}
<Animated.View style={{transform: [{rotateZ: arrowTransform}]}}>
<Ionicons name={'chevron-forward-outline'} size={18} />
</Animated.View>
</TouchableOpacity>
<View style={[styles.list, !isOpen ? styles.hidden : undefined]}>
{children}
</View>
</View>
);
};
export default Accordion;
I had difficulty using the native API, so I go to third parties. The only thing I couldn't do was make the accordion size automatic.
import { useEffect } from 'react';
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
Easing,
} from 'react-native-reanimated';
import styled from 'styled-components';
const Accordion = ({ children, open, height }) => {
const heightAnimation = useSharedValue(0);
useEffect(() => {
if (open === true) heightAnimation.value = height;
if (open === false) heightAnimation.value = 0;
}, [open]);
const animatedStyle = useAnimatedStyle(() => {
return {
height: withTiming(heightAnimation.value, {
duration: 500,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
}),
};
});
return (
<>
<Main style={animatedStyle}>{children}</Main>
</>
);
};
const Main = styled(Animated.View)`
width: 100%;
align-items: center;
justify-content: center;
overflow: hidden;
`;
export default Accordion;
Using:
<Accordion height={height} open={open}>
{children}
</Accordion>
As asked here for an example of what I managed to do with it, I tried to get as much out of it as possible.
You can see a deploy here: https://snack.expo.dev/#francisco.ossian/accordion
Libs used, react-native-reanimated

react-native-reanimated resize button

I'm trying to create a button that resizes (gets a little bit smaller when it' pressed). I use TouchableWithoutFeedback from react-native-gesture-handlerand I use react-native-reanimated.
This is my code so far:
import React, { useState } from 'react';
import { View } from 'react-native';
import Animated, { Easing, Extrapolate } from 'react-native-reanimated';
import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
const { interpolate, sub } = Animated;
const TouchableResize = (props) => {
const { onPress, children } = props;
const [scale, setScale] = useState(0);
const scaling = interpolate(scale, {
inputRange: [0, 1],
outputRange: [1, 0.90],
extrapolate: Extrapolate.CLAMP
});
return (
<TouchableWithoutFeedback onPressIn={() => setScale(1)} onPressOut={() => setScale(0)}>
<Animated.View style={{ transform: [{ scaleX: scaling }, { scaleY: scaling }] }}>
{children}
</Animated.View>
</TouchableWithoutFeedback>
);
};
export { TouchableResize };
This code works partly. The button resizes to 0.90 when it's pressed, but the animation is not smooth. It snaps directly to 0.90, and when it's released, the button directly snaps back.
How can I update my code so the animation runs smoothly? Please note I'm a complete beginner in react-native-reanimated.
You have to use timing function to change your Animated.Value over time. Here example in docs. Also, I created expo snack example. Here updated component code
import React, { useState, useMemo } from 'react';
import { View } from 'react-native';
import Animated, { Easing, Extrapolate } from 'react-native-reanimated';
import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
const {
Clock,
Value,
set,
cond,
startClock,
clockRunning,
timing,
debug,
stopClock,
block,
interpolate,
useCode,
} = Animated;
function runTiming(clock, from, to) {
const state = {
finished: new Value(0),
position: new Value(from),
time: new Value(0),
frameTime: new Value(0),
};
const config = {
duration: 100,
toValue: new Value(to),
easing: Easing.inOut(Easing.ease),
};
return block([
cond(
clockRunning(clock),
[],
startClock(clock),
),
// we run the step here that is going to update position
timing(clock, state, config),
// if the animation is over we stop the clock
cond(state.finished, debug('stop clock', stopClock(clock))),
// we made the block return the updated position
state.position,
]);
}
const TouchableResize = (props) => {
const { onPress, children } = props;
const [pressed, setPressed] = useState(false);
const {clock, scale} = useMemo(() => ({
clock: new Clock(),
scale: new Value(1),
}), [])
useCode(
() => block([
pressed ? set(scale, runTiming(clock, 0, 1)) : set(scale, runTiming(clock, 1, 0))
]), [pressed]
);
const scaling = interpolate(scale, {
inputRange: [0, 1],
outputRange: [1, 0.90],
extrapolate: Extrapolate.CLAMP
});
return (
<TouchableWithoutFeedback onPressIn={() => setPressed(true)} onPressOut={() => setPressed(false)}>
<Animated.View style={{ transform: [{ scaleX: scaling }, { scaleY: scaling }] }}>
{children}
</Animated.View>
</TouchableWithoutFeedback>
);
};
export { TouchableResize };

undefined is not an object (evaluating 'Context._context') - React Native

I am trying to wrap one of my navigators with User Context that I created. I have achieved this before in other projects but I am encountering an issue. I Tried following this solution but it doesn't seem to be the same issue I am encountering. I can't exactly tell what is wrong here.
App.js Code :
import React, { useContext, useEffect } from "react";
import { View, Text, AsyncStorage, Button } from "react-native";
import { createStackNavigator } from "#react-navigation/stack";
import HomeScreen from "./src/screens/HomeScreen";
import LoginScreen from "./src/screens/login";
import CalendarScreen from "./src/screens/Calendar";
import SignUpScreen from "./src/screens/signUp";
import { scale, vs } from "react-native-size-matters";
import { createDrawerNavigator } from "#react-navigation/drawer";
import { createMaterialBottomTabNavigator } from "#react-navigation/material-bottom-tabs";
import { Icon } from "react-native-elements";
import UserContext, { UserProvider } from "./src/screens/Context/UserContext";
import { NavigationContainer } from "#react-navigation/native";
const Tab = createMaterialBottomTabNavigator();
const Stack = createStackNavigator();
const Drawer = createDrawerNavigator();
const signedIn = true; //this is for testing
const drawer_style = {
backgroundColor: "#202B35",
activeTintColor: "#000",
width: 200,
};
const drawer_item_style = {
activeTintColor: "orange",
inactiveTintColor: "#fff",
itemStyle: { marginVertical: vs(10) },
};
const non_user_stack = () => {
<Stack.Navigator>
<Stack.Screen
name="Sign in - Cal "
component={LoginScreen}
options={({ navigation }) => ({
headerShown: true,
headerTintColor: "orange",
headerStyle: {
backgroundColor: "#202B35",
},
})}
/>
<Stack.Screen
name="Sign up - Cal "
component={SignUpScreen}
options={({ navigation }) => ({
headerShown: true,
headerTintColor: "orange",
headerStyle: {
backgroundColor: "#202B35",
},
})}
/>
</Stack.Navigator>;
};
const UserMenu = () => {
return (
<NavigationContainer>
<Drawer.Navigator
initialRouteName="Home"
drawerStyle={drawer_style}
drawerContentOptions={drawer_item_style}
drawerType="front"
>
<Drawer.Screen name="Home" component={MyStack} />
</Drawer.Navigator>
</NavigationContainer>
);
};
const MyStack = () => {
return(
<Stack.Navigator>
<Stack.Screen
name="Cal"
component={BottomNav}
options={({ navigation }) => ({
headerShown: true,
headerTintColor: "orange",
headerStyle: {
backgroundColor: "#202B35",
},
headerLeft: (props) => (
<Icon
size={25}
name={"ios-menu"}
iconStyle={{
fontSize: scale(30),
color: "orange",
margin: 5,
marginLeft: 10,
}}
type="ionicon"
color="orange"
onPress={() => {
navigation.toggleDrawer();
}}
/>
),
})}
/>
</Stack.Navigator>
)
};
export default App = () => {
const { isSignedIn, check_and_set_signin_status } = useContext(UserContext); //<-- causes crash
return (
isSignedIn === "false" ? (
<UserProvider>
<UserMenu />
</UserProvider>
) : (
<non_user_stack></non_user_stack>
);
);
};
UserContext.js :
import React, { useState, useEffect } from "react";
import * as Permissions from "expo-permissions";
import axios from "axios";
import { AsyncStorage } from "react-native";
//import registerForPushNotificationsAsync from "../Hooks/registerForPushNotifications";
import Constants from "expo-constants";
const UserContext = React.createContext();
const IS_SIGNEDIN = "is_signed_in";
export const UserProvider = ({ children }) => {
const [isSignedIn, setSignIn] = useState(null);
const [didAuthenticate, setAuthenticated] = useState(null);
//Check if this user already signed in before and didnt log out since their last session
//used for conditional rendering
const check_and_set_signin_status = async () => {
const signed_in = await AsyncStorage.getItem(IS_SIGNEDIN);
if (signed_in == null || signed_in == "false") {
await AsyncStorage.setItem(IS_SIGNEDIN, "false");
setSignIn("false");
} else {
setSignIn("true");
}
};
return (
<UserContext.Provider
value={{
isSignedIn, // well use this for conditional rendering
check_and_set_signin_status,
}}
>
{children}
</UserContext.Provider>
);
};
The Error :
there is some mistake in your code
you are not exporting UserContext but you are importing UserContext
in App.js file
you are trying to use useContext and provider in same file but you
have to useContext inside of Provider child component
you are non_user_stack with first letter capital but you have to
make first letter capital
UserContext.js : you have to export UserContext in this file
import React, { useState, useEffect } from "react";
import { Text } from 'react-native'
import * as Permissions from "expo-permissions";
import axios from "axios";
import { AsyncStorage } from "react-native";
//import registerForPushNotificationsAsync from "../Hooks/registerForPushNotifications";
import Constants from "expo-constants";
const UserContext = React.createContext();
export default UserContext;
const IS_SIGNEDIN = "is_signed_in";
export const UserProvider = ({ children }) => {
const [isSignedIn, setSignIn] = useState(null);
const [didAuthenticate, setAuthenticated] = useState(null);
const check_and_set_signin_status = async () => {
const signed_in = await AsyncStorage.getItem(IS_SIGNEDIN);
if (signed_in == null || signed_in == "false") {
await AsyncStorage.setItem(IS_SIGNEDIN, "false");
setSignIn("false");
} else {
setSignIn("true");
}
};
return (
<UserContext.Provider
value={{
isSignedIn, // well use this for conditional rendering
check_and_set_signin_status,
}}
>
{children}
</UserContext.Provider>
);
};
App.js Code :
const App = () => {
const { isSignedIn, check_and_set_signin_status } = useContext(UserContext); //<-- causes crash
console.log( isSignedIn, check_and_set_signin_status ,"useContext")
return isSignedIn === "false" ? (
<UserMenu />
) : (
<Non_user_stack></Non_user_stack>
);
};
const jsx = () => (
<UserProvider>
<App />
</UserProvider>
);
export default jsx;
In my case I imported badly
import ThemeContext from '../contexts/theme-context';
Instead
import { ThemeContext } from '../contexts/theme-context';
You should always check what you're exporting from your context folder
In my case I import { LocalContext } from ".././services/location/location.context";
instead of import { LocationContext } from ".././services/location/location.context";
Mine was:
I mistakenly imported the file (languageContext.js) instead of its function {LanguageContext} so now it goes like this where I called my context.
import {LanguageContext} from "../../Context/languageContext";
const { language } = useContext(languageContext);
i18n.locale = language;

Function inside reanimated call() is not getting updated after props update

in my react-native project, I'm trying to use a callback when the State of LongPressGestureHandler becomes END | FAILED | CANCELLED. The function for this callback uses a global variable reactions that I receive as props. Even after this prop gets updated. The function somehow is using the old reactions. I'll be really thankful if you could explain what I'm doing wrong.
`
import React, { useState, useEffect } from 'react';
import { useNavigation } from '#react-navigation/native';
import { LongPressGestureHandler, State } from 'react-native-gesture-handler';
import { Vibration, View, Text } from 'react-native';
import Animated from 'react-native-reanimated';
import { useGestureHandler } from 'react-native-redash';
import useReaction from '../../../../../../services/dashboard/feed/components/reaction/api';
import assets from '../../../../../../assets';
import styles from './styles';
const {
Value, useCode, block, eq, call, onChange, cond, and, or, set
} = Animated;
export default ({
reactions,
emoji,
componentRefetch,
loggedInUsersId,
itemId,
counts,
}) => {
const { navigate } = useNavigation();
const [giveReaction, giveUnReaction] = useReaction(componentRefetch);
const reacted = reactions.find(
(reaction) => (reaction.user ? reaction.user.id === loggedInUsersId : false)
&& reaction.emoji === emoji
);
const [popAnim] = useState(new Value(1));
const onEmojiPress = async () => {
console.log('Emoji Pressed');
console.log(reactions.length);
if (reacted) {
await giveUnReaction(itemId, emoji, reacted.id);
} else {
await giveReaction(itemId, emoji);
}
};
const onEmojiLongPress = () => {
console.log('Emoji long Pressed');
Vibration.vibrate(1);
navigate(assets.strings.dashboard.feeds.reactantsPopup.NAME, {
reactions,
emoji,
});
};
const longPressState = new Value(State.UNDETERMINED);
const longPressGestureHandler = useGestureHandler({ state: longPressState });
const shouldScale = new Value(-1);
useCode(
() => block([
cond(
eq(longPressState, State.BEGAN),
set(shouldScale, 1)
),
cond(
eq(longPressState, State.FAILED),
call([], onEmojiPress)
),
cond(
or(
eq(longPressState, State.CANCELLED),
eq(longPressState, State.END),
),
call([], onEmojiLongPress)
),
]),
[]
);
return (
<LongPressGestureHandler {...longPressGestureHandler} minDurationMs={100}>
<Animated.View
style={{
...styles.icon,
borderColor: reacted
? assets.colors.appDefaults.primaryColor
: '#eeeeee',
backgroundColor: reacted
? assets.colors.appDefaults.primaryColorLight
: '#eeeeee',
}}
>
<Animated.View
style={{
transform: [
{ scale: popAnim },
{
translateY: popAnim.interpolate({
inputRange: [1, 1.3],
outputRange: [0, -2],
}),
},
],
}}
>
<Text
style={{
color: reacted ? assets.colors.appDefaults.primaryColor : 'black',
}}
>
{`${emoji ?? '👍'} `}
</Text>
</Animated.View>
<Text
style={{
color: reacted ? assets.colors.appDefaults.primaryColor : 'black',
}}
>
{`${counts[emoji]}`}
</Text>
</Animated.View>
</LongPressGestureHandler>
);
};
`
I found out that(by experimenting) useCode sends a set of instructions to native side, in order to change the set of instructions to latest prop update, we need to fire the useCode on prop update.
useCode(, [dependencyThatGetsUpdated])