undefined is not an object(singleValue.StopTracking) on Swipe animation - reactjs

I have a Delete on Swipe function that returns TypeError: undefined is not an object(evaluating 'singleValue.StopTracking')
Im using expo to run the project
After I swipe, expo returns the error, I click dismiss and the product disappears from my cart
I don't know what any more information to give
const rowTranslateAnimatedValues = {};
Array(20)
.fill('')
.forEach((_, i) => {
rowTranslateAnimatedValues[${i}] = new Animated.Value(1);
});
const [cart, setCart] = useState([]);
const [products, setProducts] = useState([]);
const [valueInput, setValueInput] = useState(1);
const [listData, setListData] = useState(
Array(4)
.fill('')
.map((_, i) => ({ key: `${i}`, text: `item #${i}` }))
);
const onSwipeValueChange = swipeData => {
const { key, value } = swipeData;
if (
value < -Dimensions.get('window').width &&
!this.animationIsRunning
) {
this.animationIsRunning = true;
Animated.timing(rowTranslateAnimatedValues[key], {
toValue: 0,
duration: 500,
})
.start(() => {
const newData = [...listData];
const prevIndex = listData.findIndex(item => item.key === key);
newData.splice(prevIndex, 1);
setListData(newData);
this.animationIsRunning = false;
});
}
};
const renderItem = data => (
<Animated.View
>
<TouchableHighlight
onPress={() => console.log('You touched me')}
style={styles.rowFront}
underlayColor={'#AAA'}
>
<View>
<Text>I am {data.item.text} in a SwipeListView</Text>
</View>
</TouchableHighlight>
</Animated.View>
);
const renderHiddenItem = () => (
<View style={styles.rowBack}>
<View style={[styles.backRightBtn, styles.backRightBtnRight]}>
<Text style={styles.backTextWhite}>Delete</Text>
</View>
</View>
);
return (
<Block flex center style={styles.cart}>
<View style={styles.container}>
<SwipeListView
disableRightSwipe
data={cart}
renderItem={renderProduct}
showsVerticalScrollIndicator={false}
keyExtractor={(item, index) => `${index}-${item.title}`}
ListEmptyComponent={renderEmpty()}
ListHeaderComponent={renderHeader()}
ListFooterComponent={renderFooter()}
renderHiddenItem={renderHiddenItem}
rightOpenValue={-Dimensions.get('window').width}
onSwipeValueChange={onSwipeValueChange}
/>
</View>
</Block>
);
}

For anyone who faces this problem,
you can just think this.animationIsRunning of a variable that manages the start and the end of animation.
So you can just use const animationIsRunning = useRef(false) to manage the animation state.
You can use animationIsRunning.current instead of this.animationIsRunning.
Also, this.animationIsRunning = true or false can be changed into animationIsRunning.current = true or false
See code at
https://github.com/jemise111/react-native-swipe-list-view/pull/521/commits/2946e3a123f26ef5292a3884e0aec58436386e18

Related

Trying to add multiple images in an object

I have a form object that holds all my input values. I am working with the react-native camera component and what I am trying to accomplish is every time a picture is added, it is added to the object. With my current code, I am getting invalid attempts to spread non-iterable instances.
import { TicketContext } from "../store/TicketContext";
function Pictures() {
const [hasCameraPermissions, setHasCameraPermissions] = useState();
const [picture, setPicture] = useState();
const { form, setForm } = useContext(TicketContext);
//Add picture to form //
const handleAddPicture = () => {
setForm([...(form ?? []), { picture: picture.uri }]);
setPicture();
};
const cameraRef = useRef();
//Get Permission to use Camera//
const handleCameraPermissions = async () => {
const cameraPermissions = await Camera.requestCameraPermissionsAsync();
setHasCameraPermissions(cameraPermissions.status === "granted");
};
// Check for permissions on load //
useEffect(() => {
handleCameraPermissions();
}, []);
if (hasCameraPermissions === undefined) {
return <Text>Permissions Required...</Text>;
} else if (!hasCameraPermissions) {
return <Text>Camera Permission Denied. Please change in settings.</Text>;
}
//Take Picture //
const handleTakePicture = async () => {
const options = { base64: true, exif: false, quality: 1 };
const newPicture = await cameraRef.current.takePictureAsync(options);
setPicture(newPicture);
};
if (picture) {
return (
<SafeAreaView style={styles.container}>
<Image
style={styles.preview}
source={{ uri: "data:image/jpg;base64," + picture.base64 }}
/>
<View style={styles.buttonContainer}>
<Button title="Add Picture" onPress={handleAddPicture} />
</View>
</SafeAreaView>
);
}
return (
<SafeAreaView style={styles.container}>
<Camera style={styles.cameraContainer} ref={cameraRef}>
<View style={styles.buttonContainer}>
<Button title="Take Picture" onPress={handleTakePicture} />
</View>
</Camera>
</SafeAreaView>
);
}
export default Pictures;
setPicture returns the state back to undefined
my state in context
const [form, setForm] = useState({});
//[form ,setForm]
// make sure you object keys are camel case.
const handleAddPicture = () => {
setForm([...(form ?? []), { picture: picture.uri }]);
setPicture();
};

Getting "Can't perform a React state update on an unmounted component" only the first time

I am creating a ToDo app. This app has two screens: Todos and Done. I'm using BottomTabNavigator to switch between these screens. These two screens has list of todos. The todos component shows the undone todos and the Done component shows the done todos. There's a checkbox on the left and Trash icon on the right of every single todo. When a todo from Todos page is checked then it moves to the Done page. The issue is: after switching to the Done screen from Todos for the first time then after unchecking the todo there gives this warning:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
in SingleTodo (at Done.tsx:74)
After this, the app is running perfectly. As I'm not sure which component is causing this error that'w why I'm sharing the minimal version of the code.
I have set up Bottom Tab navigator component like this:
import stuff..
...
const HomeTab = () => {
return (
<Tab.Navigator
screenOptions={({route}) => ({
headerShown: false,
tabBarIcon: ({focused, color, size}) => {
let iconName = '';
size = focused ? 25 : 20;
if (route.name === 'To-Do') {
iconName = 'clipboard-list';
} else if (route.name === 'Done') {
iconName = 'clipboard-check';
}
return <FontAwesome5Icon name={iconName} size={size} color={color} />;
},
tabBarActiveTintColor: '#0080ff',
tabBarInactiveTintColor: '#777777',
tabBarLabelStyle: {fontSize: 15, fontWeight: 'bold'},
})}>
<Tab.Screen name="To-Do" component={Todos} />
<Tab.Screen name="Done" component={Done} />
</Tab.Navigator>
);
};
export default HomeTab;
As you can see, there are 2 components here. One is Todos. The code for this component is as follows:
import stuff...
...
const Todos = ({navigation}) => {
const dispatch = useAppDispatch();
const {todos}: {todos: TodoInterface[]} = useAppSelector(
state => state.todoReducer,
);
useEffect(() => {
loadTodos();
}, []);
const loadTodos = () => {
AsyncStorage.getItem('todos').then(todos => {
const parsedTodos: TodoInterface[] = JSON.parse(todos || '{}');
dispatch(setAllTodo(parsedTodos));
});
};
return (
<HideKeyboard>
<View style={styles.body}>
<FlatList
data={todos.filter(todo => todo.done !== true)}
renderItem={({item, index}) => {
const firstChild = index == 0 ? {marginTop: 5} : {};
return (
<TouchableOpacity
style={[styles.todoWrp, firstChild]}
onPress={() => todoPressHandler(item.todoId)}>
<SingleTodo // ***The code for this one is given below***
title={item.title}
subtitle={item.subTitle}
done={item?.done}
todoId={item.todoId}
/>
</TouchableOpacity>
);
}}
keyExtractor={(item, i) => item.todoId}
/>
<TouchableOpacity style={styles.addBtn} onPress={addBtnHandler}>
<FontAwesome5 name="plus" color="#fff" size={25} />
</TouchableOpacity>
</View>
</HideKeyboard>
);
}
The code for SingleTodo is as follows:
const SingleTodo = ({title, subtitle, done: doneProp, todoId}: Props) => {
const [done, setDone] = useState(doneProp);
const dispatch = useAppDispatch();
const {todos}: TodosType = useAppSelector(state => state.todoReducer);
const checkBoxHandler = (val: boolean) => {
const todoList: TodoInterface[] = [...todos];
const index = todos.findIndex(todo => todo.todoId === todoId);
todoList[index].done = val;
AsyncStorage.setItem('todos', JSON.stringify(todoList)).then(() => {
dispatch(setAllTodo(todoList));
setDone(val);
});
};
const deleteHandler = () => {
const todoList: TodoInterface[] = [...todos];
const index = todos.findIndex(todo => todo.todoId === todoId);
todoList.splice(index, 1);
AsyncStorage.setItem('todos', JSON.stringify(todoList)).then(() => {
dispatch(setAllTodo(todoList));
});
};
return (
<View style={styles.body}>
<CheckBox
value={done}
onValueChange={val => checkBoxHandler(val)}
style={styles.checkbox}
/>
<View>
<Text style={[styles.title, GlobalStyle.IndieFont]}>{title}</Text>
<Text style={[styles.subtitle, GlobalStyle.IndieFont]}>{subtitle}</Text>
</View>
<View style={styles.trashWrp}>
<TouchableOpacity onPress={deleteHandler}>
<FontAwesome5Icon
style={styles.trashIcon}
name="trash"
color="#e74c3c"
size={20}
/>
</TouchableOpacity>
</View>
</View>
);
};
export default SingleTodo;
The code for Done component is similar to Todos component. The only changes is on the data property of the component
<FlatList
data={todos.filter(todo => todo.done === true)}
...
other props...
...
/>
It's happening every time you use this, it is just shown once to not spam the console.
const checkBoxHandler = (val: boolean) => {
const todoList: TodoInterface[] = [...todos];
const index = todos.findIndex(todo => todo.todoId === todoId);
todoList[index].done = val;
AsyncStorage.setItem('todos', JSON.stringify(todoList)).then(() => {
dispatch(setAllTodo(todoList));
setDone(val);
});
};
const deleteHandler = () => {
const todoList: TodoInterface[] = [...todos];
const index = todos.findIndex(todo => todo.todoId === todoId);
todoList.splice(index, 1);
AsyncStorage.setItem('todos', JSON.stringify(todoList)).then(() => {
dispatch(setAllTodo(todoList));
});
};
Basically, you call the function, and the todo is unmounted from the state, but the function is not completed yet and you get that warning.
The solution is to lift everything related to the deleteHandler and checkBoxHandler from your children (Todo) to your parent (Todos), and pass it to Todo as props. Since parent is always mounted, deleting the todo will not unmount the parent and therefore, delete function will not be interrupted.

react_native + iframe + firebase : How could I serve the videoId of next index from realtime database?

now i use react native, expo project.
and i apply realtime database, iframe, FlatList.
and i use videoID instead of the 'scr= URL'.
i hope to autoplay by auto changing the videoId in my realtime database.
i think it can be possible by using index number.
and when i console.log selected index, it presented on terminal.
but i don't know how to apply that path for my code.
this is my realtime database.
enter image description here
and this is my codes.
import YoutubePlayer from "react-native-youtube-iframe";
const [state, setState] = useState([])
const [cardID, setCardID] = useState(["i4S5hvPG9ZY"])
const [playing, setPlaying] = useState(true);
const onStateChange = useCallback((state) => {
if (state === "ended") {
setPlaying(true)
}
}, []);
const onPress = ({ item, index }) => {
console.log({ index })
return (
setCardID(item.id.videoId)
)
}
...
useEffect(() => {
setLoading(true);
firebase_db.ref('/TGBSitems')
.once('value')
.then((snapshot) => {
console.log("TGBS에서 데이터 가져왔습니다!!")
let TGBSitems = snapshot.val()
setState(TGBSitems)
setTotalDataSource(TGBSitems);
setLoading(false);
})
.catch(err => { setLoading(false); setError(err); })
}, []);
...
return (
...
<View>
<YoutubePlayer
height={200}
play={playing}
videoId={cardID}
onChangeState={onStateChange}
// playList
/>
</View>
...
<FlatList
data={state}
// ItemSeparatorComponent={ItemSeparatorView}
keyExtractor={(index) => index.toString()}
renderItem={({ item, index }) => (
<View style={styles.cardContainer}>
<TouchableOpacity style={styles.card} onPress={() => onPress({ item, index })}>
<Image style={styles.cardImage} source={{ uri: item.snippet.thumbnails.medium.url }} />
<View style={styles.cardText}>
<Text style={styles.cardTitle} numberOfLines={1}>{item.snippet.title}</Text>
<Text style={styles.cardDesc} numberOfLines={3}>{item.snippet.description}</Text>
<Text style={styles.cardDate}>{item.snippet.publishedAt}</Text>
<Text style={styles.cardDate}>{item.id.videoId}</Text>
<Text style={styles.cardDate}>{index}</Text>
</View>
...

Unable to push data into an array - React Native

one place I seem to be stuck is on is being able to populate an array of objects, which are used for a FlatList later on.
I get this data from my FireStore – for each document, it will push the objects into ‘const DATA = []’
But when I run the ‘getUsers()’ inside of UseEffect, it only updates ‘DATA’ inside of the method, it doesn’t update the global variable.
Im new to react native, so im probably missing something important within my structure. Thanks for any assistance tho!
I need the format of DATA to look like this example:
My Code:
const MainScreen = () => {
const DATA = [];
const navigation = useNavigation()
const [selectedId, setSelectedId] = useState(null);
const usersCollectionRef = collection(db, "Posts");
const [Data, setData]= useState([]);
LogBox.ignoreLogs(['Setting a timer']);
LogBox.ignoreLogs(['AsyncStorage has been extracted']);
LogBox.ignoreAllLogs(true);
useEffect(() => {
getUsers();
console.log(DATA);
}, []);
const getUsers = async () => {
const data = await getDocs(usersCollectionRef);
data.forEach(doc =>{
const dataG = (doc.id, "=>", doc.data());
DATA.push({
id: doc.id,
title: dataG.status +" "+ dataG.Type,
status: dataG.status,
location: dataG.Location,
type: dataG.Type,
injured: dataG.Injured,
collar: dataG.Collar,
color: dataG.Colour,
username: dataG.username,
description: dataG.Description,
imagesrc: dataG.picture });
})
};
const Item = ({ item, onPress, backgroundColor, textColor }) => (
<View style={styles.ContentBox}>
<TouchableOpacity onPress={onPress} style={[styles.item, backgroundColor]}>
<Text style={[styles.title, textColor]}>{item.title}</Text>
<Text style={styles.ContentText}>By: {item.username}</Text>
</TouchableOpacity>
<Image source = {{uri: item.imagesrc}}
style = {{ width: 200, height: 200, alignSelf:'center' }}/>
<Text style={styles.ContentText}>Animal: {item.type}</Text>
<Text style={styles.ContentText}>Location: {item.location}</Text>
<Text style={styles.ContentText}>Injured: {item.injured}</Text>
<Text style={styles.ContentText}>Colour: {item.color}</Text>
<Text style={styles.ContentText}>Has a Collar: {item.collar}</Text>
<Text style={styles.ContentText}>Description: {item.description}</Text>
</View>
);
const renderItem = ({ item }) => {
const backgroundColor = item.status === "lost" ? '#b80909' : '#098231';
const color = item.id === selectedId ? 'white' : 'white';
return (
<Item
item={item}
onPress={() => setSelectedId(item.id)}
backgroundColor={{ backgroundColor }}
textColor={{ color }}
/>
);
};
const PostScreen = () =>{
navigation.navigate('PostScreen');
}
return (
<SafeAreaView style={styles.container}>
<View style={styles.MainFeed}>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
extraData={selectedId}
/>
</View>
)
instead of pushing data in a variable and then updating the state, you can do it like this directly -
setData([...Data,{
id: doc.id,
title: dataG.status +" "+ dataG.Type,
status: dataG.status,
location: dataG.Location,
type: dataG.Type,
injured: dataG.Injured,
collar: dataG.Collar,
color: dataG.Colour,
username: dataG.username,
description: dataG.Description,
imagesrc: dataG.picture
}])
The answer is that after doing DATA.push(...), I needed to setData(DATA).

React native renderitems with refs to textinput

i cant seem to get my head around this.
I want to to use a button to use .focus() on my textinput in react native.
How do i pass the ref to my function and call that method?
const [{ingredients}, dispatch] = React.useContext(AddRecipeContext);
const [edit, setEdit] = React.useState('');
const startEdit = (key: string, ref: any) => {
ref.current.focus();
setEdit((prev) => (prev === key? '':key))
}
const _renderItem = ({ item }: {item: IIngredient}) => {
var textInputRef: any = React.createRef();
return (
<View key={item.key}>
<TextInput
ref={textInputRef}
onChangeText={label => setIngredientLabel(item.key, label) }
value={item.label.toString()}/>
<Pressable onPress={() => startEdit(item.key, textInputRef)}/>
</View>
);
};
const RenderIngredients = ingredients.map((item: IIngredient) => _renderItem({item}));
return (
<View>
<ScrollView>
{RenderIngredients}
</ScrollView>
</View>
);
What is the correct way to handle refs?
Thank you in advance.
EDIT: ref.focus(); to ref.current.focus();
This should do the job
const [{ ingredients }, dispatch] = React.useContext(AddRecipeContext);
const [edit, setEdit] = React.useState("");
const _renderItem = ({ item }: { item: IIngredient }) => {
var textInputRef: any = React.createRef();
return (
<View key={item.key}>
<TextInput
ref={textInputRef}
onChangeText={(label) => setIngredientLabel(item.key, label)}
value={item.label.toString()}
/>
<Pressable
onPress={() => {
textInputRef.current.focus();
// current was missing
setEdit((prev) => (prev === item.key ? "" : item.key));
}}
/>
</View>
);
};
const RenderIngredients = ingredients.map((item: IIngredient) =>
_renderItem({ item })
);
return (
<View>
<ScrollView>{RenderIngredients}</ScrollView>
</View>
);
Your forgot the current object with the ref: ref.focus(); should be ref.current.focus();
See how to accessing refs

Resources