I'm building a React component that shows data on an Order Summary Screen to conclude the order process for my App.
I am receiving the message:
Warning: Each child in a list should have a unique "key" prop.%s%s See...
Here is the complete error for reference:
Check the render method of `SummaryOrder`., ,
in RCTView (at SummaryOrder.js:24)
in SummaryOrder (at PreOrderScreen.js:111)
in ScrollView (at PreOrderScreen.js:105)
in RCTView (at PreOrderScreen.js:99)
Location on errors are also marked with "=>".
SummaryOrder.js:
import React from "react";
import { View, StyleSheet } from "react-native";
//Number
import NumberFormat from "../../../components/UI/NumberFormat";
//PreOrderItem
import PreOrderItem from "./PreOrderItem";
//Text
import CustomText from "../../../components/UI/CustomText";
import Colors from "../../../utils/Colors";
//PropTypes check
import PropTypes from "prop-types";
export class SummaryOrder extends React.PureComponent {
render() {
const { cartItems, total } = this.props;
return (
in RCTView (at SummaryOrder.js:24) => ***<View style={styles.container}>***
<CustomText style={{ ...styles.title, marginVertical: 5 }}>
Order Summary
</CustomText>
<View style={{ backgroundColor: "#fff", paddingHorizontal: 10 }}>
{cartItems.map((item) => {
return (
in SummaryOrder (at PreOrderScreen.js:111) => ****<View key={item.item.createdAt}>****
<PreOrderItem item={item} />
</View>
);
})}
</View>
<View style={styles.total}>
<CustomText
style={{
fontSize: 15,
color: Colors.text,
fontWeight: "500",
}}
>
Total
</CustomText>
<NumberFormat price={total.toString()} />
</View>
</View>
);
}
}
SummaryOrder.propTypes = {
cartItems: PropTypes.array.isRequired,
total: PropTypes.number.isRequired,
};
PreOrderScreen.js:
import React, { useState, useEffect, useRef } from "react";
import { useIsFocused } from "#react-navigation/native";
import { View, StyleSheet, ScrollView } from "react-native";
//Address
import Address from "./components/Address";
//Redux
import { useSelector } from "react-redux";
//Steps
import Colors from "../../utils/Colors";
import { Header, SummaryOrder, TotalButton, UserForm } from "./components";
import Loader from "../../components/Loaders/Loader";
export const PreOrderScreen = (props) => {
const unmounted = useRef(false);
const isFocused = useIsFocused();
const [loading, setLoading] = useState(true);
const carts = useSelector((state) => state.cart.cartItems);
const { cartItems, total, cartId } = props.route.params;
const [error, setError] = useState("");
const [name, setName] = useState("");
const [phone, setPhone] = useState("");
const [address, setAddress] = useState("");
const [province, setProvince] = useState("");
const [town, setTown] = useState("");
useEffect(() => {
return () => {
unmounted.current = true;
};
}, []);
useEffect(() => {
if (isFocused) {
setLoading(true);
const interval = setInterval(() => {
setLoading(false);
}, 1000);
return () => clearInterval(interval);
}
return;
}, [isFocused]);
const getInfo = (province, town) => {
setProvince(province);
setTown(town);
};
const getReceiver = (name, phone, address) => {
setName(name);
setPhone(phone);
setAddress(address);
};
const checkValidation = (error) => {
setError(error);
};
let orderItems = [];
cartItems.map((item) => {
orderItems.push({ item: item.item._id, quantity: item.quantity });
});
const fullAddress = `${address}, ${town} ,${province}`;
const toPayment = async () => {
try {
if (error == undefined && province.length !== 0 && town.length !== 0) {
props.navigation.navigate("Payment", {
screen: "PaymentScreen",
params: {
fullAddress,
orderItems,
name,
phone,
total,
cartId,
carts,
},
});
} else {
alert("Please enter your full information.");
}
} catch (err) {
throw err;
}
props.navigation.navigate("Payment", {
screen: "PaymentScreen",
params: {
fullAddress,
orderItems,
name,
phone,
total,
cartId,
carts,
},
});
};
useEffect(() => {
if (carts.items.length === 0) {
props.navigation.goBack();
}
}, [carts.items]);
return (
in RCTView (at PreOrderScreen.js:99) => ***<View style={styles.container}>***
<Header navigation={props.navigation} />
{loading ? (
<Loader />
) : (
<>
<ScrollView>
<UserForm
getReceiver={getReceiver}
checkValidation={checkValidation}
/>
<Address getInfo={getInfo} />
in ScrollView (at PreOrderScreen.js:105) =>
***<SummaryOrder cartItems={cartItems} total={total} />***
</ScrollView>
<TotalButton toPayment={toPayment} />
</>
)}
</View>
);
};
Thanks for your help in advance!
The createdAt values are not unique for them. The quick fix is to use index instead, but if you plan to do any removing or sorting, you should rather use some sort of unique identifier for the items (phone, email, user ID, etc). I only skimmed your code, but you could use phone instead.
The keys are important because it hints to React when it needs to update the list. The docs are helpful https://reactjs.org/docs/lists-and-keys.html#keys for more on this.
Anyway, the quick fix is shown here. Read https://stackoverflow.com/a/43642421 as to why you shouldn't do this. I'll leave a better implementation up to you.
...
{ cartItems.map((item, index) => {
return (
<View key={index}>
<PreOrderItem item={item} />
</View> );
})
}
...
whenever you use map in react, you should define a unique key for the repeating tag or component, this is for react to keep track of changes in the array you are mapping on, and if any changes occur in array, react will know where to re-render the related tag or component and leave others alone :)
if you don't have a plan to re-order the array, you can simply use index as key but if you want to re-order the array elements, it's better if first you define a unique id for them(if it's not provided by server) and then do the map the array
Related
I'm working on a mobile phone application with Stripe and Expo Bar Code Scanner. When you start the application, if you gave the permissions for using the camera, you will can scan bar codes. Bar Codes only contains the id of the scanned item. If it exists, two buttons (+/-) will appear in order to choose the amount for the item. If it doesn't exists, nothing happens. When the amount changes, I save in SecureStore the id of the item as the key and the amount as the value.
The problem is when I move on others screens (with React Navigation) and I came back to scan and I rescan the same item, the amount resets to 0. If you don't give the permissions for the camera, it displays a list of available items when you can choose the amount (+/-) buttons and similar problem.
Here the concerned two files :
ItemListComponent.tsx
import { Button, FlatList, View, Text } from 'react-native';
import * as SecureStore from 'expo-secure-store';
import { useState } from 'react';
export const ItemComponent = (props: any) => {
const [amount, setAmount] = useState<number>(0);
const getAmount = async () => {
const amount = await SecureStore.getItemAsync(props.item.id.toString());
if (amount) {
setAmount(parseInt(amount));
}
getAmount();
}
const save = async () => {
await SecureStore.setItemAsync(props.item.id.toString(), amount.toString());
}
return (
<View>
<Text>{props.item.name}</Text>
<Button
onPress={() => {
setAmount(amount + 1);
save();
}}
title='+'
/>
{amount > 0 &&
<Button
onPress={() => {
setAmount(amount - 1);
save();
}}
title='-'
/>
}
</View>
);
};
export const ItemListComponent = (props: any) => {
return (
<FlatList
data={props.items}
renderItem={({ item }) =>
<ItemComponent key={item.id} item={item} />
}
/>
);
};
BarCodeScannerComponent.tsx
import { BarCodeScanner } from 'expo-barcode-scanner';
import { useState } from 'react';
import { StyleSheet } from 'react-native';
import { ItemComponent } from './ItemListComponent';
import Items from '../models/ItemsModel';
export const BarCodeScannerComponent = () => {
const [item, setItem] = useState<Items>();
const getItem = async ({ data }: any) => {
const response = await fetch(`http://192.168.1.81:8000/items/${data}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (response.ok) {
const json = await response.json();
setItem(json);
}
}
return (
<View style={styles.container}>
<BarCodeScanner
onBarCodeScanned={getItem}
style={StyleSheet.absoluteFillObject}
/>
{(item !== null && item !== undefined) && <ItemComponent key={item.id} item={item} />}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
},
});
Thanks for help !
It looks like you never call getAmount, and if you did call it you'd get infinite recursion.
const getAmount = async () => {
const amount = await SecureStore.getItemAsync(props.item.id.toString());
if (amount) {
setAmount(parseInt(amount));
}
getAmount();
}
should be
const getAmount = async () => {
const amount = await SecureStore.getItemAsync(props.item.id.toString());
if (amount) {
setAmount(parseInt(amount));
}
}
getAmount();
or, probably even better:
const getAmount = async () => {
const storeAmount = await SecureStore.getItemAsync(props.item.id.toString());
if (amount !== parseInt(storeAmount)) {
setAmount(parseInt(storeAmount));
}
}
useEffect(() => {
getAmount();
}, [props.item.id]);
otherwise, every time it renders you'll call setAmount which will trigger a rerender
What is the correct way so that I can save the dark mode switch even after turning off the application?
I want to use the use-state-persist library to achieve the goal.
In my example I show app.tsx , Preferences.tsx, ViewField.tsx .
So that it will be possible to understand how the logic is built
DarkModeContext
import React from 'react';
interface DarkMode {
isDarkMode: boolean;
setDarkMode: () => void;
}
export const DarkModeContext = React.createContext<DarkMode>({} as DarkMode);
this is the app.tsx
import React, { useEffect, useState } from 'react';
import { syncStorage } from 'use-state-persist';
const App: () => ReactElement = () => {
const [isDarkMode, setDarkMode] = useState(false);
const Drawer = createDrawerNavigator();
const initStorage = async () => await syncStorage.init();
const toggleDarkMode = () => {
setDarkMode(!isDarkMode);
};
return (
<DarkModeContext.Provider value={{ isDarkMode, toggleDarkMode }}>
<NavigationContainer>
<Drawer.Navigator
drawerContent={SideMenu}
screenOptions={{
drawerPosition: 'right',
headerShown: false,
drawerType: 'front',
}}
>
<Drawer.Screen name='HomeScreen' component={StackNavigator} />
</Drawer.Navigator>
</NavigationContainer>
</DarkModeContext.Provider>
);
};
export default App;
this is the Preferences.tsx
import React, { useContext, useState } from 'react';
import ViewField from './ViewField';
import { DarkModeContext } from '~/context/DarkModeContext';
const Preferences = () => {
const { isDarkMode, toggleDarkMode } = useContext(DarkModeContext);
return (
<View>
<ViewField title='dark mode' isEnabled={isDarkMode} setValue={toggleDarkMode} />
</View>
);
};
export default Preferences;
this is the ViewField.tsx
import { View, Text, Switch } from 'react-native';
import React from 'react';
import styles from './ViewFieldStyles';
import { useContext } from 'react';
import { DarkModeContext } from '~/context/DarkModeContext';
type Props = {
title: string;
isEnabled: boolean;
setValue: () => void;
};
const ViewField = ({ title, isEnabled, setValue }: Props) => {
const { isDarkMode } = useContext(DarkModeContext);
return (
<View style={isDarkMode ? styles.optionViewDark : styles.optionView}>
<View style={styles.sameRowTextView}>
<Text style={isDarkMode ? styles.optionTextDark : styles.optionText}>{title}</Text>
<View style={styles.switchView}>
<Switch
trackColor={
isDarkMode
? { false: 'rgba(255, 255, 255, 0.38)', true: 'rgba(187, 134, 252, 0.38)' }
: { false: '#767577', true: 'rgba(4, 76, 163, 0.38)' }
}
onValueChange={setValue}
value={isEnabled}
/>
</View>
</View>
</View>
);
};
export default ViewField;
Keep in mind that there seems to be some problems in use-state-persist using Boolean values. Furthermore, the latest published version of this library is from 2020.
However, the use-state-persist library just seems to be a wrapper around AsyncStorage, which is very well maintained. I would encourage you to use this library instead.
In your case, this could be implemented as follows:
Store the actual setter of the state in the context,
Create an effect that accesses the async storage on mount of the application: if there exists a value for the corresponding key, set the state of the context, if not, then do nothing.
In the Preferences component, store a new state in the async storage as well.
const App: () => ReactElement = () => {
const [isDarkMode, setDarkMode] = useState(false);
const contextValue = React.useMemo(() => ({
isDarkMode,
setDarkMode
}), [isDarkMode])
React.useEffect(() => {
const load = async () => {
const value = await AsyncStorage.getItem('isDarkMode');
if (value !== null) {
setDarkMode(JSON.parse(value));
}
}
load();
}, [])
return (
<DarkModeContext.Provider value={contextValue}>
...
};
In the Preferences component, set the state and save it to the local storage.
const Preferences = () => {
const { isDarkMode, setDarkMode } = useContext(DarkModeContext);
async function toggle() {
const newValue = JSON.stringify(!isDarkMode);
await AsyncStorage.setItem('isDarkMode', newValue);
setDarkMode(prev => !prev);
}
return (
<View>
<ViewField title='dark mode' isEnabled={isDarkMode} setValue={toggle} />
</View>
);
}
Hi I have very common problem, react native application freezes while fetching API,
It is like when you fetching you cannot click or do anythink till your fetching ends.
This is my code where I call function which fetchs APi
import React, { useEffect, useState } from "react";
import { StyleSheet, View, FlatList } from "react-native";
// Api`s
import { topCategory } from "../../api/appModels/category";
import { banners } from "../../api/appModels/slider";
import { dinamicBlocks } from "../../api/appModels/product";
// Hooks
import useApi from "../../Hooks/useApi";
// UI-Components
import Container from "../../uicomponents/General/Container";
import BannerSlider from "../../uicomponents/Slider/BannerSlider";
import Screen from "../../uicomponents/General/Screen";
import TopProductPattern from "../../uicomponents/Category/pattern/TopProductPattern";
import SliderPlaceholder from "../../uicomponents/Skeleton/Sliders/SliderPlaceholder";
import CategoryAvatarPlaceholder from "../../uicomponents/Skeleton/Category/CategoryAvatarPlaceholder";
import CategoryBlocks from "../../uicomponents/Product/blocks/CategoryBlocks";
import Header from "../../uicomponents/header/Header";
import ActivityIndicator from "../../uicomponents/Loaders/ActivityIndicator";
const HomeScreen = () => {
const {
data: topCategoryList,
loading: topCategoryLoading,
request: categoryRequest,
} = useApi(topCategory);
const {
data: bannersList,
loading: bannerLoading,
request: bannersRequest,
} = useApi(banners);
const {
loading: blocksLoading,
request: blocksRequest,
} = useApi(dinamicBlocks);
const [blocks, setBlocks] = useState([]);
const [indexing, setIndexing] = useState(1);
const [countBlocks, setCountBlocks] = useState(0);
const [loader, setLoader] = useState(false);
useEffect(() => {
// Calling Api`s
categoryRequest();
bannersRequest();
blocksRequest((item) => {
setIndexing(indexing + 1);
setBlocks(item["blocks"]);
setCountBlocks(item["count"]);
}, 1);
}, []);
const loadMore = () => {
if (!blocksLoading) {
blocksRequest((item) => {
setIndexing(indexing + 1);
setBlocks(blocks.concat(item["blocks"]));
console.log(item);
}, indexing);
setLoader(indexing != countBlocks);
}
};
return (
<Screen>
<FlatList
data={blocks}
keyExtractor={(item) => item.categories.toString()}
ListHeaderComponent={
<>
<Header />
{bannerLoading ? (
<SliderPlaceholder />
) : (
<BannerSlider data={bannersList} dots />
)}
<Container>
<View style={{ paddingTop: 10 }}>
{topCategoryLoading ? (
<CategoryAvatarPlaceholder />
) : (
<TopProductPattern data={topCategoryList} />
)}
</View>
</Container>
</>
}
ListFooterComponent={<ActivityIndicator visible={loader} />}
renderItem={({ item }) => (
<CategoryBlocks title={item.blocks_name} data={item.categoriesList} />
)}
onEndReached={loadMore}
onEndReachedThreshold={0.1}
/>
</Screen>
);
};
export default HomeScreen;
const styles = StyleSheet.create({});
Here is my code which fetches API
import { useState } from "react";
const useApi = (apiFunc) => {
const [data, setData] = useState([]);
const [error, setError] = useState("");
const [loading, setLoading] = useState(true);
const request = async (callBack = () => {}, ...args) => {
setLoading(true);
const response = await apiFunc(...args);
if (!response.ok) return setError(response.problem);
setLoading(false);
setError("");
setData(response.data);
if (response.ok) {
callBack(response.data);
}
};
return {
data,
error,
loading,
request,
setLoading,
setData,
};
};
export default useApi;
I think there is problem with RN-bridge HELP ME PLEASE !
Probably it's happened because you set onEndReachedThreshold={0.1} and when data was loaded one more time request will be send and it's make problem.
So you can increase this value for example to 0.7.
In the official react native website This is how it is explained onEndReachedThreshold:
"How far from the end (in units of visible length of the list) the bottom edge of the list must be from the end of the content to trigger the onEndReached callback. Thus a value of 0.5 will trigger onEndReached when the end of the content is within half the visible length of the list."
I want to send the parameter to the function submitLanguageSelection, which is userSelectedLanguage, to a custom hook I've written which (hopefully) saves that parameter to AsyncStorage. The user selects a language, either English or Arabic, from one of the two buttons.
This is my first time ever doing this. I've gotten very stuck.
I would like the submitLanguageSelection function to call the saveData function which is made available through the useLocalStorage hook. I would like the user's choice of language to be persisted in AsyncStorage so I can then later render the ChooseYourLanguageScreen according to whether the user has selected a language or not.
Here is the cutom hook, useLocalStorage:
import React from 'react';
import { Alert } from 'react-native';
import AsyncStorage from '#react-native-community/async-storage';
const STORAGE_KEY = '#has_stored_value';
export default () => {
const [storedValue, setStoredValue] = React.useState('');
const [errorMessage, setErrorMessage] = React.useState('');
const saveData = async () => {
try {
const localValue = await AsyncStorage.setItem(STORAGE_KEY, storedValue);
if (localValue !== null) {
setStoredValue(storedValue);
Alert.alert('Data successfully saved');
}
console.log('stored val', storedValue);
} catch (e) {
setErrorMessage('Something went wrong');
}
};
return [saveData, errorMessage];
};
Here is the ChooseYourLanguageScreen:
import React from 'react';
import { View, Text, StyleSheet, Button } from 'react-native';
import useLocalStorage from '../hooks/useLocalStorage';
const ChooseYourLanguageScreen = ({ navigation }) => {
const [saveData, errorMessage] = useLocalStorage();
const submitLanguageSelection = (userSelectedLanguage) => {
//TODO: save the data locally
//TODO: navigate to welcome screen
// at the moment, the language choice isn't making it to useLocalStorage
if (userSelectedLanguage !== null) {
console.log('user selected lang', userSelectedLanguage);
saveData(userSelectedLanguage);
}
};
return (
<View style={styles.container}>
{errorMessage ? <Text>{errorMessage}</Text> : null}
<Text style={styles.text}>This is the Choose Your Language Screen</Text>
<View style={styles.buttons}>
<View>
<Button
title={'English'}
onPress={() => submitLanguageSelection('English')}
/>
</View>
<View>
<Button
title={'Arabic'}
onPress={() => submitLanguageSelection('Arabic')}
/>
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
text: {
alignSelf: 'center',
},
buttons: {
backgroundColor: '#DDDDDD',
padding: 10,
},
});
export default ChooseYourLanguageScreen;
saveData() needs a parameter. You can provide a default value that uses storedValue that came from React.useState(), but when you call it with an explicit argument it will override that default.
export default () => {
const [storedValue, setStoredValue] = React.useState('');
const [errorMessage, setErrorMessage] = React.useState('');
const saveData = async (dataToSave = storedValue) => {
try {
const localValue = await AsyncStorage.setItem(STORAGE_KEY, dataToSave);
if (localValue !== null) {
setStoredValue(dataToSave);
Alert.alert('Data successfully saved');
}
console.log('stored val', dataToSave);
} catch (e) {
setErrorMessage('Something went wrong');
}
};
return [saveData, errorMessage];
};
Using react hooks with firebase real time database with a project Im working on for the first time, the data is being retrieved and logs in the console. BUT I am unable go move out side the useEffect function to display values on the screen! Would really appreciate some help!
videoArray needs to be put in a FlatList, as you can see in the code bellow videoArray logs values in the console in useEffect function. However, once I move out that function to add it into a FlatList it is null because the values are in a local function.
My question is, how am I able to pass value of videoArray (in useEffect) into the FlatList?
import React, { useState, useEffect } from 'react'
import { FlatList, View, TouchableOpacity, Text, StyleSheet, SafeAreaView } from 'react-native';
import { Center } from '../components/Center'
import { Video } from 'expo-av';
import firebase from '../firebase'
const videoRef = firebase.database().ref('videoCollaction');
export const FeedScreen = ({ }) => {
let [videoArray, setVideo] = useState([]);
useEffect(() => {
videoRef.on('value', (childSnapshot) => {
videoArray = [];
childSnapshot.forEach((doc) => {
videoArray.push({
key: doc.key,
video: doc.toJSON().video,
});
})
})
// able to log values only here
console.log('working:', videoArray);
});
// get video uri from firebase (testing)
// readVideo = () => {
// var collection = firebase.database().ref("videoCollactionvideo" + "/video").orderByValue();
// console.log('uri', collection);
// }
return (
<SafeAreaView>
<Text>Feed Screen</Text>
{/* null here: need values to show up here*/}
{console.log(" test",videoArray)}
<FlatList
data={videoArray}
renderItem={({ item, index }) => {
return (
<View>
<Text style={{ fontSize: 35, color: 'red' }}>Video:...</Text>
<TouchableOpacity onPress={() => console.log('pressed')}><Text style={{ color: 'blue' }}>Expand</Text></TouchableOpacity>
</View>
);
}} keyExtractor={({ item }, index) => index.toString()}>
</FlatList>
</SafeAreaView>
);
}
Try this:
useEffect(() => {
const temp = []; // temp array
videoRef.on("value", childSnapshot => {
childSnapshot.forEach(doc => {
temp.push({
key: doc.key,
video: doc.toJSON().video
});
});
setVideo(temp); // update state array
});
}, []);
It seems like you are trying to update the State Hook (videoArray), but you are doing it the wrong way (it shouldn't be modified directly). Instead, use the setVideo update method which you created with the Hook (let [videoArray, setVideo] = useState([]);):
useEffect(() => {
videoRef.on('value', (childSnapshot) => {
newVideoArray = [];
childSnapshot.forEach((doc) => {
newVideoArray.push({
key: doc.key,
video: doc.toJSON().video,
});
})
})
// able to log values only here
console.log('working:', newVideoArray);
setVideo(newVideoArray);
});
Check out Using the Effect Hook for more information on how to use this specific hook (the Optimizing Performance by Skipping Effects section might be especially of interest).
In essence, this functionality is similar to your Functional Component's stateful counterparts (React.Component or React.PureComponent), where:
Constructor is the only place where you should assign this.state directly. In all other methods, you need to use this.setState() instead.
Try this:
import React, { useState, useEffect } from 'react'
import { FlatList, View, TouchableOpacity, Text, StyleSheet, SafeAreaView } from 'react-native';
import { Center } from '../components/Center'
import { Video } from 'expo-av';
import firebase from '../firebase'
const videoRef = firebase.database().ref('videoCollaction');
export const FeedScreen = ({ }) => {
let [videoArray, setVideoArray] = useState([]);
useEffect(() => {
videoRef.on('value', (childSnapshot) => {
const newVideoArray = [];
childSnapshot.forEach((doc) => {
newVideoArray.push({
key: doc.key,
video: doc.toJSON().video,
});
})
setVideoArray(newVideoArray);
})
// able to log values only here
console.log('working:', videoArray);
}, []);
console.log('State also working :) >> ', videoArray);
return (
<SafeAreaView>
<Text>Feed Screen</Text>
{/* null here: need values to show up here*/}
{console.log(" test",videoArray)}
<FlatList
data={videoArray}
renderItem={({ item, index }) => {
return (
<View>
<Text style={{ fontSize: 35, color: 'red' }}>Video:...</Text>
<TouchableOpacity onPress={() => console.log('pressed')}><Text style={{ color: 'blue' }}>Expand</Text></TouchableOpacity>
</View>
);
}} keyExtractor={({ item }, index) => index.toString()}>
</FlatList>
</SafeAreaView>
);
}