React Naitve shows only one View - javascript

in my app on my homescreen I am rendering a ListList with my items. Now I wanted to add a little info text above it. But it only shows my text and skips(?) my FlatList. Could anyone help me and explain me this behaviour?
If I have my text component in my file it only shows the text. If I only use FlatList it shows correctly my list with my data. But if I try to combine both, it only shows one component. Same thing when I use only FlatList and wrap it inside of View then I get only a white blank screen.
const JobsScreen = (props) => {
const dispatch = useDispatch();
const [isLoading, setIsLoading] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const allJobs = useSelector((state) => state.job.availableJobs);
const loadJobs = useCallback(async () => {
setIsRefreshing(true);
try {
await dispatch(jobActions.fetchAllJobs());
} catch (err) {}
setIsRefreshing(false);
}, [dispatch]);
useEffect(() => {
setIsLoading(true);
loadJobs().then(() => {
setIsLoading(false);
});
}, [dispatch, loadJobs]);
useEffect(() => {
const willFocusSub = props.navigation.addListener("willFocus", loadJobs);
return () => {
willFocusSub.remove();
};
}, [dispatch, loadJobs]);
if (isLoading) {
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#2f3640",
}}
>
<ActivityIndicator size="large" color="#fff" animating />
</View>
);
}
return (
<View>
<FlatList
data={allJobs}
onRefresh={loadJobs}
refreshing={isRefreshing}
keyExtractor={(item) => item.id}
style={{ flex: 1, backgroundColor: "#1a191e" }}
renderItem={(itemData) => (
<JobItem
description={itemData.item.description}
titel={itemData.item.titel}
fname={itemData.item.fname}
cover={itemData.item.cover}
genre={itemData.item.genre}
year={itemData.item.year}
id={itemData.item.id}
// friend={itemData.item.friend}
/>
)}
/>
</View>
);
};

Got it by myself.
<View style={{ height: "100%" }}>
solved it.

Try this.
<View style={{ flex: 1 }}>

Related

How to display products in Flatlist properly?

I am trying to display some fetched products in a FlatList, also to display more products when I reach the end of this FlatList, but I am getting this error: Invariant Violation: ScrollView child layout (["alignItems"]) must be applied through the contentContainerStyle prop.
When I change alignItems from style to contentContainerStyle the app closes without showing an error or alert.
If I remove the alignItems from container the app closes too
export default function NewProducts() {
const [products, setProducts] = useState([]);
const [isLoading, setisLoading] = useState(false);
const [startLimit, setStartLimit] = useState({ start: 0, limit: 10 });
const navigation = useNavigation();
useEffect(() => {
setisLoading(true);
getProducts();
}, [startLimit]);
const gotoProduct = (id) => {
navigation.push("product", { idProduct: id });
};
const getProducts = async () => {
const response = await getLastProductsApi(
startLimit.start,
startLimit.limit
);
console.log(response);
setProducts(response);
};
const renderItem = ({product}) => {
return (
<TouchableWithoutFeedback
key={product.id}
onPress={() => gotoProduct(product.id)}
>
<View style={styles.containerProduct}>
<View style={styles.product}>
<Image
style={styles.image}
source={{
uri: `${product.attributes.images.data[0].attributes.formats.small.url}`,
}}
/>
<Text style={styles.name} numberOfLines={1} ellipsizeMode="tail">
{product.attributes.title}
</Text>
</View>
</View>
</TouchableWithoutFeedback>
);
};
const renderFooter = () => {
return isLoading && <Loading text="Loading more products" size="medium" />;
};
const handleLoadMore = () => {
setStartLimit({
start: startLimit.start + 10,
limit: startLimit.limit + 10,
});
setisLoading(true);
};
return (
<FlatList
style={styles.container}
numColumns={2}
data={products}
renderItem={renderItem}
keyExtractor={(item, index) => index.toString()}
ListFooterComponent={renderFooter}
onEndReached={handleLoadMore}
onEndReachedThreshold={0}
/>
);
}
const styles = StyleSheet.create({
container: {
alignItems: "flex-start",
marginTop: 1,
},
containerProduct: {
padding: 3,
},
product: {
padding: 10,
backgroundColor: "#f0f0f0",
borderRadius: 20,
},
image: {
height: 150,
resizeMode: "contain",
borderRadius: 20,
},
name: {
marginTop: 15,
fontSize: 15,
},
});
Here is where I call this component
export default function Home() {
return (
<>
<StatusBarCustom
backgroundColor={colors.primary}
barStyle="light-content"
/>
<Search />
<View>
<Banner />
<NewProducts />
</View>
</>
);
}
When you use renderItem from FlatList you have to use item and not product: https://reactnative.dev/docs/flatlist#required-renderitem
You can replace
const renderItem = ({product}) => {
By
const renderItem = ({item}) => {

useState is not working properly in React Native

I'm working on an application and using WordPress API for showing posts. I've created 2 buttons to navigate the list of posts. As you know there is an argument "page=" to get posts on a specific page, I've initialized a state to maintain page number. The main problem is that it's not incrementing correctly.
Post Screen Code -
import React, { useState, useEffect } from "react";
import { View, FlatList, TouchableOpacity } from "react-native";
import { Colors } from "../constant/colors";
import globalStyles from "../constant/globalStyle";
import axios from "axios";
import PostCard from "../components/PostCard";
import CustomButton from "../components/Button";
const Updates = () => {
const [data, setData] = useState([]);
const [page, setPage] = useState(1);
const [loaded, setLoaded] = useState(false);
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
const response = await axios.get(
`https://bachrasouthpanchayat.in/wp-json/wp/v2/posts?embed=true&page=${page}`
);
setData(response.data);
setLoaded(true);
};
const previousHandler = () => {
setLoaded(false);
let newPage = page - 1;
setPage(newPage);
fetchData();
};
const nextHandler = () => {
setLoaded(false);
let newPage = page + 1;
setPage(newPage);
fetchData();
};
return (
<View
style={{
...globalStyles.container,
backgroundColor: Colors.BACKGROUND_SCREEN,
}}
>
{loaded ? (
<>
<FlatList
style={{ flex: 1, margin: 10 }}
data={data}
keyExtractor={(item) => item.id}
renderItem={({ item }) => {
return (
<TouchableOpacity activeOpacity={0.7}>
<PostCard
title={item.title.rendered}
imageUrl={item.jetpack_featured_media_url}
/>
</TouchableOpacity>
);
}}
/>
<View
style={{
flexDirection: "row",
alignItems: "center",
alignContent: "stretch",
justifyContent: "center",
}}
>
{page == 1 ? (
<TouchableOpacity
activeOpacity={0.7}
style={{ width: "100%" }}
onPress={nextHandler}
>
<CustomButton>Next</CustomButton>
</TouchableOpacity>
) : (
<>
<TouchableOpacity
activeOpacity={0.7}
style={{ marginRight: 2, width: "50%" }}
onPress={previousHandler}
>
<CustomButton>Previous</CustomButton>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.7}
style={{ width: "50%" }}
onPress={nextHandler}
>
<CustomButton>Next</CustomButton>
</TouchableOpacity>
</>
)}
</View>
</>
) : null}
</View>
);
};
export default Updates;
I had logged state in every state and found that was not incrementing from 1 to 2 on pressing the button the first time. I think state updated after API call because both buttons had started showing even I've used condition to show both buttons only if the state is not 1
Please let me know if i've made any silly mistake 😂😂
useEffect(() => {
fetchData();
}, []);
The last argument to useEffect is an array of dependencies so that React will only re-run the effect when the dependencies have changed. You are passing an empty array, which tells React that there are no dependencies and the effect should only be run once, when the component is first mounted.
Now you actually want the effect to re-run when the page changes, so you should put page in the depenency array:
useEffect(() => {
fetchData();
}, [page]);
And (credit: #Keith) you should remove the extra fetchData() calls in the nextHandler and previousHandler
You have to implement it like this:
useEffect(() => {
const fetchData = async () => {
setLoaded(true);
const response = await axios.get(
`https://bachrasouthpanchayat.in/wp-json/wp/v2/posts?embed=true&page=${page}`
);
setData(response.data);
setLoaded(false);
};
fetchData();
}, [page]);
const previousHandler = () => {
setPage(prevPage => prevPage - 1);
};
This way whenever the user changes the page, it will automatically call the function in useEffect, since it is in the dependency array.

ReactNative rerenders component when the user opens the page

I have two components the first where user can add a place to the favorites and the second is favorites component where user may see all his favorite places. When the user for the first time opens the favorites component everything works as expected: all the favorite places that user has already added to the favorites rendered. But if user go to the first component and add one more place and then go to the second component new place will not appear because component has already rendered and the state didn't changed because useEffect not triggered. Help me please what should I use in my FavouritePlaces component instead of useEffect to rerender this component every time when user open FavouritePlaces?
Component where user can add to favorites:
const ModalWindow = ({navigateToPlace, sendDataToParent, visible, marker}: HomeNavigationProps<"ModalWindow">) => {
const regex = /(<([^>]+)>)|( )|(&nbps)/ig;
const result = marker.description.replace(regex, '');
const [favKeys, setFavKeys] = useState([]);
const onDismiss = () => {
sendDataToParent(false)
}
const onNavigationTap = () => {
onDismiss();
navigateToPlace(true, marker.coordinates);
}
const getFavourites = async () => {
let keys = []
keys = await AsyncStorage.getAllKeys()
setFavKeys(keys);
}
const onHeartPress = async () => {
const jsonValue = JSON.stringify(marker)
try {
if (favKeys.includes(marker.id.toString())){
await AsyncStorage.removeItem(marker.id.toString())
await getFavourites();
} else {
await AsyncStorage.setItem(marker.id.toString(), jsonValue)
await getFavourites();
}
} catch (e) {
console.log('error in onHeartPress', e)
}
console.log('Done.')
//remove after test
try {
await AsyncStorage.removeItem('__react_native_storage_test')
} catch(e) {
// remove error
}
console.log('Done.')
}
return (
<Modal visible={visible} onDismiss={onDismiss} contentContainerStyle={styles.container}>
<IconButton
style={
styles.iconButton
}
icon="close"
color={Colors.black}
size={30}
onPress={() => onDismiss()}
/>
<Text
style={{fontStyle: "italic", fontSize: 20, alignSelf: "center", maxWidth: '75%'}}>{marker.title}
</Text>
<CustomCarousel {...{marker}} />
<ScrollView showsVerticalScrollIndicator={false} style={{marginTop: '3%', marginLeft: '3%', marginRight: '3%'}}>
<Text>{result}</Text>
</ScrollView>
<View style={{flexDirection: "row", justifyContent: "space-around", marginLeft: "3%", marginRight: "3%", marginBottom: "15%"}}>
<TouchableOpacity onPress={() => onNavigationTap()}>
<View style={{flexDirection: "row", alignItems: "center"}}>
<Ionicons size={height/20} name={'navigate-circle-outline'} />
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => onHeartPress()}>
{marker.id ?
<View style={{flexDirection: "row", alignItems: "center"}}>
{favKeys.includes(marker.id.toString()) ? <Ionicons size={height/20} name={'heart-dislike'} /> : <Ionicons size={height/20} name={'heart'} />}
</View> : undefined}
</TouchableOpacity>
</View>
</Modal>
);
}
export default ModalWindow;
My Favorite Places component:
const FavouritePlaces = ({navigation}: HomeNavigationProps<"FavouritePlaces">) => {
const [markers, setMarkers] = useState([]);
useEffect(() => {
const getFavourites = async () => {
let keys = []
try {
keys = await AsyncStorage.getAllKeys()
} catch (e) {
// read key error
}
let values
try {
let forDeletion = ['__react_native_storage_test', 'NAVIGATION_STATE_KEY-40.0.0'];
keys = keys.filter(item => !forDeletion.includes(item))
values = await AsyncStorage.multiGet(keys)
setMarkers(values)
} catch (e) {
// read error
}
}
getFavourites();
}, [])
const transition = (
<Transition.Together>
<Transition.Out type='fade'/>
<Transition.In type='fade'/>
</Transition.Together>
);
const list = useRef<TransitioningView>(null);
const theme = useTheme()
const width = (wWidth - theme.spacing.m * 3) / 2;
const [footerHeight, setFooterHeight] = useState(0);
return (
<Box flex={1} backgroundColor="background">
<StatusBar style="black" />
<Header
title="Избранные места"
left={{icon: 'menu', onPress: () => navigation.openDrawer()}}
right={{icon: 'shopping-bag', onPress: () => true}}
/>
<Box flex={1}>
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={{
paddingBottom: footerHeight,
}}>
<Transitioning.View ref={list} transition={transition} style={{}}>
{markers ?
<Box flexDirection='row' style={{justifyContent: "space-around"}}>
<Box>
{markers
.filter((_, i) => i % 2 === 0).map((currentMarker) => <Picture
key={currentMarker}
place={currentMarker}
width={width}
height={height}
/>)}
</Box>
<Box>
{markers
.filter((_, i) => i % 2 !== 0).map((currentMarker) => <Picture
key={currentMarker}
place={currentMarker}
width={width}
height={height}/>)}
</Box>
</Box> : undefined}
</Transitioning.View>
</ScrollView>
{/*<TopCurve footerHeight={footerHeight}/>*/}
<Box position='absolute' bottom={0} left={0} right={0} onLayout={({
nativeEvent: {
layout: {height},
}
}) => setFooterHeight(height)}>
</Box>
</Box>
</Box>
)
}
export default FavouritePlaces
Try this
useEffect(() => {
// ... Your code goes here
}, [navigation]);
this will render whenever update in navigate
I've found the solution. React navigation has hook useIsFocused, so what can we do is:
import { useIsFocused } from "#react-navigation/native";
const isFocused = useIsFocused();
useEffect(() => {
// ... Your code goes here
}, [isFocused]);
You can use React Context API to share the state across the screens.
Check out this Expo Snack I created.
import {
CompositeNavigationProp,
NavigationContainer,
NavigatorScreenParams,
} from '#react-navigation/native';
import {
createStackNavigator,
StackNavigationProp,
} from '#react-navigation/stack';
import * as React from 'react';
import {
Button,
FlatList,
ListRenderItem,
Text,
TextInput,
View,
} from 'react-native';
type MainStackParamsList = {
FavoritePlacesScreen: undefined;
};
type ModalStackParamsList = {
MainStack: NavigatorScreenParams<MainStackParamsList>;
AddFavoritePlacesModal: undefined;
};
type FavoritePlace = {
id: number;
name: string;
};
type FavoritePlacesContextValue = {
favoritePlaces: FavoritePlace[];
addNewFavoritePlace: (favoritePlace: FavoritePlace) => void;
removeFavoritePlace: (id: number) => void;
};
const FavoritePlacesContext = React.createContext<FavoritePlacesContextValue>({
favoritePlaces: [],
addNewFavoritePlace: () => {},
removeFavoritePlace: () => {},
});
const MainStack = createStackNavigator<MainStackParamsList>();
type FavoritePlacesScreenProps = {
navigation: CompositeNavigationProp<
StackNavigationProp<MainStackParamsList, 'FavoritePlacesScreen'>,
StackNavigationProp<ModalStackParamsList>
>;
};
const FavoritePlacesScreen = ({navigation}: FavoritePlacesScreenProps) => {
const {favoritePlaces, removeFavoritePlace} = React.useContext(
FavoritePlacesContext,
);
const renderItem = React.useCallback<ListRenderItem<FavoritePlace>>(
({item}) => {
return (
<View style={{height: 50, padding: 10, flexDirection: 'row'}}>
<Text style={{fontSize: 16}}>{item.name}</Text>
<Button onPress={() => removeFavoritePlace(item.id)} title="Remove" />
</View>
);
},
[removeFavoritePlace],
);
return (
<View style={{flex: 1}}>
<FlatList
data={favoritePlaces}
keyExtractor={(item) => String(item.id)}
renderItem={renderItem}
/>
<Button
onPress={() => {
navigation.navigate('AddFavoritePlacesModal');
}}
title="Add new favorite"
/>
</View>
);
};
const MainStackNavigator = () => {
return (
<MainStack.Navigator>
<MainStack.Screen
component={FavoritePlacesScreen}
name="FavoritePlacesScreen"
/>
</MainStack.Navigator>
);
};
type AddFavoritePlacesModalProps = {
navigation: StackNavigationProp<
ModalStackParamsList,
'AddFavoritePlacesModal'
>;
};
const AddFavoritePlacesModal = ({navigation}: AddFavoritePlacesModalProps) => {
const {addNewFavoritePlace} = React.useContext(FavoritePlacesContext);
const [favoritePlaceName, setFavoritePlaceName] = React.useState('');
const handleOnSave = React.useCallback(() => {
addNewFavoritePlace({
id: Date.now(),
name: favoritePlaceName,
});
navigation.goBack();
}, [addNewFavoritePlace, favoritePlaceName, navigation]);
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<View style={{borderRadius: 6, borderWidth: 1, borderColor: '#333'}}>
<TextInput
onChangeText={setFavoritePlaceName}
placeholder="Name your favorite place"
/>
</View>
<Button onPress={handleOnSave} title="Save" />
</View>
);
};
// Put the favorite places list screen and the add favorite place modal here.
// Then use FavoritePlacesContext.Provider to wrap ModalStack.Navigator in order
// for the context to be available on MainStack
const ModalStack = createStackNavigator<ModalStackParamsList>();
const ModalNavigator = () => {
const [favoritePlaces, setFavoritePlaces] = React.useState<FavoritePlace[]>(
[],
);
const addNewFavoritePlace = React.useCallback(
(favoritePlace: FavoritePlace) => {
setFavoritePlaces((prev) => [...prev, favoritePlace]);
},
[],
);
const removeFavoritePlace = React.useCallback((id: number) => {
setFavoritePlaces((prev) =>
prev.filter((favoritePlace) => favoritePlace.id !== id),
);
}, []);
return (
<FavoritePlacesContext.Provider
value={{
favoritePlaces,
addNewFavoritePlace,
removeFavoritePlace,
}}
>
<ModalStack.Navigator headerMode="none">
<ModalStack.Screen component={MainStackNavigator} name="MainStack" />
<ModalStack.Screen
component={AddFavoritePlacesModal}
name="AddFavoritePlacesModal"
options={{headerShown: false}}
/>
</ModalStack.Navigator>
</FavoritePlacesContext.Provider>
);
};
const App = () => {
return (
<NavigationContainer>
<ModalNavigator />
</NavigationContainer>
);
};
export default App;

React Native Navigation Listener. How to stop page refresh when coming back from certain pages

I have logic that makes my home page refresh every time the component is mounted. I do want it to refresh every time I go from the home screen to the profile screen of my app for example, but I do not want it to reload when I open the search modal page and just dismiss it.. or click on an item in the home page and dismiss it.. its a bit excessive. My question is how would I be able to do this in an elegant way where I can pick and choose when I want the page to reload based on which page I'm going back from.
const HomeScreen = (props) => {
let TouchableCmp = TouchableOpacity
if (Platform.OS === 'android' && Platform.Version >= 21) {
TouchableCmp = TouchableNativeFeedback
}
const [isLoading, setIsLoading] = useState(false)
const [httpError, setHttpError] = useState(false)
const homeTickets = useSelector((state) => state.tickets.homeTickets)
const dispatch = useDispatch()
const loadTickets = useCallback(async () => {
setHttpError(false)
setIsLoading(true)
try {
await dispatch(setTickets())
setIsLoading(false)
} catch (err) {
setHttpError(err.message)
setIsLoading(false)
}
}, [dispatch])
useEffect(() => {
const willFocusListener = props.navigation.addListener(
'willFocus',
loadTickets
)
return () => {
willFocusListener.remove()
}
}, [loadTickets])
useEffect(() => {
setIsLoading(true)
loadTickets().then(() => {
setIsLoading(false)
})
}, [dispatch, loadTickets])
if (isLoading)
return (
<View style={styles.center}>
<LottieView
source={require('../assets/srSPININGcompatibility.json')}
style={{ height: 60, width: 60 }}
autoPlay
loop
></LottieView>
</View>
)
if (httpError)
return (
<View style={styles.center}>
<Text style={styles.errorStyle}>{`${httpError}`}</Text>
<View style={styles.refreshCont}>
<TouchableCmp onPress={loadTickets} activeOpacity={0.5}>
<Text
style={{
fontSize: 17,
color: 'white',
fontWeight: 'bold',
}}
>
Refresh
</Text>
</TouchableCmp>
</View>
</View>
)
return (
<View style={styles.screen}>
<View style={styles.screen}>
<FlatList
showsVerticalScrollIndicator={false}
onRefresh={loadTickets}
refreshing={isLoading}
style={styles.flatList}
data={homeTickets}
keyExtractor={(item) => item.ticketID}
renderItem={(itemData) => (
<TicketCell
companyName={itemData.item.company}
requestor={itemData.item.requestedBy}
dateStamp={itemData.item.workLogDate}
issue={itemData.item.issue}
companyPhone={itemData.item.businessPhone}
navigation={props.navigation}
ticketID={itemData.item.ticketID}
goToDetails={() => {
props.navigation.navigate({
routeName: 'HomeDetail',
params: { id: itemData.item.ticketID },
})
}}
/>
)}
/>
</View>
</View>
)
}

FlatList does not show the API result

I'm just training how to consume apis in react-native with useEffects and in console.log () returns the results, but in the view it doesn't show, I think it's the keyExtractor or not ...
const api = 'https://randomuser.me/api/?&results=2';
const Detalhes = () => {
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
setIsLoading(true);
fetch(api)
.then((response) => response.json())
.then((results) => {
setData(results);
setIsLoading(false);
console.log(results);
})
.catch((err) => {
setIsLoading(false);
setError(err);
});
}, []);
if (isLoading) {
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<ActivityIndicator size="large" color="#5500dc" />
</View>
);
}
if (error) {
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 18}}>
Error...
</Text>
</View>
);
}
and this is the code with FlatList that should be returned in View and as said, I can see the result in console.log (react native debug)
<SafeAreaView>
<FlatList
data={data}
keyExtractor={item => item.first}
renderItem={({item}) => (
<View style={styles.metaInfo}>
<Text style={styles.text}>
{`${item.name.first} ${item.name.last}`}
</Text>
</View>
)}
/>
</SafeAreaView>
Try to see again console.log(results). Api response is an object { results, info }, not an array. The results property of results is array.
Replace setData(results); by setData(results.results); to resolve your problem.
Sorry for my bad English.
You are giving name in your keyExtractor which could be same repeat so It won't work. Try changing keyExtractor like below to provide unique index for each item
keyExtractor={(item,index)=>index.toString()}

Categories

Resources