Why is the data in a Context not updating/completing/getting passed down - react-native

I use the following code to fetch the data for my app.
export const DataContext = () => {
const [posts, setPosts] = useState<Array<Post>>([]);
const [profiles, setProfiles] = useState<Array<User>>([]);
const [page, setPage] = useState<number>(1);
const [perPage, setPerPage] = useState<number>(10);
useEffect(() => {
async function fetchData() {
const { data: postsData } = await supabase
.from("posts")
.select("*")
.range((page - 1) * perPage, page * perPage - 1);
setPosts(postsData!.map((post) => new Post(post)));
const postUids = postsData!.map((post) => post.uid);
const { data: profilesData } = await supabase
.from("profiles")
.select("*")
.in("uid", postUids);
const profiles = profilesData!.map((userData: any) => {
const userPosts = posts.filter((post) => post.uid === userData.uid);
const user = new User({ ...userData, posts: userPosts });
return user;
});
setProfiles((prevUsers) => [...prevUsers, ...profiles]);
}
fetchData();
}, [page, perPage]);
const nextPage = () => {
setPage(page + 1);
};
const prevPage = () => {
setPage(page - 1);
};
return React.createContext({ posts, profiles, nextPage, prevPage });
};
Then, in a TabBarView I use that context to share the data between two screens.
const { posts, profiles } = useContext(DataContext());
const Home = (props: any) => (
<View style={Theme().screen}>
<HomeScreen posts={posts} users={users} />
</View>
);
const Discover = (props: any) => (
<View style={Theme().screen}>
<ExploreScreen posts={posts} users={profiles} />
</View>
);
My first issue was that the posts array in each user was always empty in the context. I fixed this temporarily by repeating the logic to add those posts in the TabBarView.
const { posts, profiles } = useContext(DataContext());
const users: Array<User> = profiles.map((user) => { ///Added
const userPosts = posts.filter((post) => post.uid === user.uid); ///Added
user.posts.push(...userPosts); ///Added
return user; ///Added
}); ///Added
const Home = (props: any) => (
<View style={Theme().screen}>
<HomeScreen posts={posts} users={users} />
</View>
);
const Discover = (props: any) => (
<View style={Theme().screen}>
<ExploreScreen posts={posts} users={profiles} />
</View>
);
Even though that data is now logging what I would expect, the data going to HomeScreen and DiscoverScreen through props is different. The posts is fine but users is just logging as [] which means the users are not added there. Why is this happening and how can I fix this?
Example data from DataContext:
Posts: [{
"caption":"Caption",
"date":"1669244422569",
"imageUrls":[
"https://cdn.pixabay.com/photo/2020/05/04/16/05/mckenzie-river-5129717__480.jpg"
],
"location":{
"latitude":150,
"locationInfo":"City, State",
"longitude":-150
},
"postId":"1669244407166",
"uid":"daf6b8be-7cd0-4341-89d7-07879b207087"
}]
Users: [{
"blockedUsers":[],
"displayName":"name",
"photoURL":"https://cdn.pixabay.com/photo/2020/05/04/16/05/mckenzie-river-5129717__480.jpg",
"uid":"daf6b8be-7cd0-4341-89d7-07879b207087",
"verified":false
}]
Example log from TabBarView:
Posts: [Post, Post, Post]
Users: [User, User, User]
What would log in HomeScreen:
Posts: [Post, Post, Post]
Users: []

Related

How to select one item of the array, when the user selects the contact?

What im trying to do is to select one or more phone numbers from the user contact list.
function ContactList() {
const [checked, setChecked] = useState(false);
const [contacts, setContacts] = useState([]);
const [filter, setFilter] = useState([]);
const [search, setSearch] = useState('');
const [data, setData] = useState(contacts)
useEffect(() => {
(async () => {
const { status } = await Contacts.requestPermissionsAsync();
if (status === 'granted') {
const { data } = await Contacts.getContactsAsync({
fields: [Contacts.Fields.PhoneNumbers],
});
if (data.length > 0) {
setContacts(data);
setFilter(data);
}
}
})();
}, []);
const searchFilter = (text) => {
if (text) {
const newData = contacts.filter((item) => {
const itemData = item.name ? item.name.toUpperCase() : ''.toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
});
setFilter(newData);
setSearch(text);
} else {
setFilter(contacts);
setSearch(text);
}
};
const onChangeValue = () => {
setChecked(!checked)
};
useEffect(() => {
checked &&
setData((previous) => [...previous, {phone: contacts}])
}, [checked]
return(
<View>
<CheckBox
style={{ width: 15, height: 15 }}
right={true}
checked={checked}
onPress={onChangeValue}
/>
</View>
);
export default ContactList;
So far, when the user selects one phone number, it will select all phone numbers on his contact list.
I think I should get with the index only one contact from the list but I don't know how to get there.
How can I solve this error?
You can maintain a Map for checkboxes.
const [checked, setChecked] = useState(new Map());
on change use index to set values in Map.
const onChangeValue = (index) => {
checked.set(index, true)
};
in JSX use like this
<CheckBox
style={{ width: 15, height: 15 }}
right={true}
checked={checked.get(index)}
onPress={()=>onChangeValue(index)}
/>

Flatlist is very slow in using big data in react native

i have a big data list of products thats paginate, in every page it load 10 item, but when i add new items to itemlist,flatlist gets very slow,As the number of pages increases, so does the loading time of new products,The function of the choose button is also slowed down.
How to speed up loading I tried all the best methods but it still did not work. Did not React Native really solve this problem?
export default function Products(props) {
const toast = useToast();
const [isLoading, setSetIsLoading] = useState(true);
const [items, setItems] = useState([]);
const [fetchStatus, setFetchStatus] = useState(false);
const [page, setPage] = useState(1);
const [sending, setSending] = useState(false);
async function getProducts() {
let token = await AsyncStorage.getItem('#token');
let data = {
token: token,
page: page,
};
await get_products(data)
.then(res => {
setItems([...items, ...res.data.data.docs]);
setPage(res.data.data.nextPage);
})
.catch(err => {
console.log(err);
});
}
async function getNextPage() {
let token = await AsyncStorage.getItem('#token');
let data = {
token: token,
page: page,
};
await get_products(data)
.then(res => {
setItems([...items, ...res.data.data.docs]);
setPage(res.data.data.nextPage);
})
.catch(err => {
console.log(err);
});
}
async function selectProduct(id) {
setSending(true);
console.log({id});
let token = await AsyncStorage.getItem('#token');
let data = {
product_id: id
};
await select_products(data,token).then(res => {
toast.show({
description:res.data.message
})
setSending(false);
}).catch(rej => {
console.log({rej})
toast.show({
description:rej?.response?.data.message,
})
setSending(false);
})
}
useFocusEffect(
React.useCallback(() => {
getProducts();
return () => {
setItems([]);
setPage();
};
}, []),
);
renderItem =({item}) => (
<Card
selectProduct={id => selectProduct(id)}
sending={sending}
obj={item}
/>
)
return (
<View mb={20}>
<FlatList
data={items}
extraData={items}
removeClippedSubviews={true}
renderItem={renderItem}
keyExtractor={(item) => `${item._id}-item`}
onEndReached={getNextPage}
maxToRenderPerBatch="13"
ListFooterComponent={() => {
return <ActivityIndicator color="orange" size="large" />;
}}></FlatList>
</View>
);
}
Did you use **map method **?
It can help you for more easily loading data

if action.payload.number exist increase state.points with action.payload.points

In React-Native I´ve two TextInputs and an add-button
My add function works i.e it creates a list. But each time I fill out the form, it adds a new listpost. I want to check if action.payload.number exist, and if true, increase state.points with action.payload.points.
as yet everything I tried with have failed i.e it didn't recognize state.number === action.payload.number and adds a new row in the list
Please help me solving this issue
Thanks in advance
Pierre
const InputScreen = () => {
const [number, setNumber] = useState("");
const [points, setPoints] = useState("");
const {addScorer} = useContext(Context);
const onPress = () => {
addScorer(number, points);
setnumber("");
setPoints("");
};
return (
<View>
<Text style={styles.label}>enter Scorer Number:</Text>
<TextInput style={styles.input} value={number} onChangeText={setNumber} />
<Text style={styles.label}>enter Points Scored:</Text>
<TextInput style={styles.input} value={points} onChangeText={setPoints} />
<TouchableOpacity onPress={onPress}>
<FontAwesome5 name="plus-circle" size={44} color="coral" />
</TouchableOpacity>
</View>
);
};
export default InputScreen;
const scorerReducer = (state, action) => {
const id = Date.now().toString().slice(-4);
switch (action.type) {
case "add_scorer":
// if (state.number === action.payload.nummer) {
if (state.find((scorer) => scorer.id === action.payload.id)) {
return [
...state,
{
points: state.points + +action.payload.points,
},
];
} else {
return [
...state,
{
id: id,
number: action.payload.number,
points: +action.payload.points,
},
];
}
default:
return state;
}
};
const addScorer = (dispatch) => {
return (number, points, id) => {
dispatch({type: "add_scorer", payload: {number, points, id}});
};
};
export const {Context, Provider} = createDataContext(
scorerReducer,
{addScorer},
[]
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I solved it without using Reducer:)
export const ScoredProvider = ({children}) => {
const [playerList, setPlayerList] = useState([]);
const [number, setNumber] = useState("");
const [points, setPoints] = useState("");
const addScorer = () => {
const players = [...playerList];
if (number.trim().length === 0) {
return;
}
const posit = players.map((player) => player.number).indexOf(number);
if (posit !== -1) {
setPlayerList((playerList) =>
playerList.map((scorer, index) =>
index === posit
? {
...scorer,
points: scorer.points + +points,
}
: scorer
)
);
} else {
const newScorer = {
id: Date.now(),
number: number,
points: +points,
};
setPlayerList([...playerList, newScorer]);
setPoints(points);
}
};
return (
<ScoredContext.Provider
value={{number, setNumber, points, setPoints, playerList, addScorer}}
>
{children}
</ScoredContext.Provider>
);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

React Apollo fetch once

Im working on simply COVID-19 tracker and i have a problem.
Is there any option in Apollo for React to fetch graphql data once per button press?
Now i have TextInput and Button but when i fetch data once i can't type another country in input because i have immediately error.
const Tile = () => {
const [country, setCountry] = useState('Poland');
const [cases, setCases] = useState(0);
const MY_QUERY = gql`
query getCountryStats($country: String!) {
country(name: $country) {
todayCases
}
}
`;
const [getCountryStats, {data, loading, error}] = useLazyQuery(MY_QUERY, {
variables: {
country: country,
},
onCompleted: (data) => {
setCases(data.country.todayCases);
},
});
if (loading) return <Text>LOADING...</Text>;
if (error) return <Text>Error!</Text>;
return (
<View>
<CasesNumber>{cases}</CasesNumber>
<FinderWrapper>
<FinderInput
onChangeText={(text) => {
setCountry(text);
}}
/>
<FinderButton
onPress={() => {
getCountryStats();
}}>
<Text>FIND</Text>
</FinderButton>
</FinderWrapper>
</View>
);
};
export default Tile;
Try using this
const Tile = () => {
const [country, setCountry] = useState('Poland');
const [cases, setCases] = useState(0);
let inputValue = ‘’;
const MY_QUERY = gql`
query getCountryStats($country: String!) {
country(name: $country) {
todayCases
}
}
`;
const [getCountryStats, {data, loading, error}] = useLazyQuery(MY_QUERY, {
variables: {
country: country,
},
onCompleted: (data) => {
setCases(data.country.todayCases);
},
});
const onGetCountryStats = () => {
setCountry(inputValue);
getCountryStats();
}
if (loading) return <Text>LOADING...</Text>;
if (error) return <Text>Error!</Text>;
return (
<View>
<CasesNumber>{cases}</CasesNumber>
<FinderWrapper>
<FinderInput
onChangeText={(text) => {
inputValue = text;
}}
/>
<FinderButton
onPress={() => {
onGetCountryStats();
}}>
<Text>FIND</Text>
</FinderButton>
</FinderWrapper>
</View>
);
};
export default Tile;

react-native re-render negates filter

To learn myself react-native I am building an app that contains a FlatList filled with tickets with help of redux. When I try to filter through the tickets by typing in a number, the list gets filtered but only for 1 second. After that it gives a list of all tickets again. I have trouble finding the the logical error behind my beginner code. Any help would be appreciated.
I pasted the list below:
const AllTicketList = ({ navigation, ticket: { allTickets }, getTickets }) => {
useEffect(() => {
getTickets();
}, []);
const [enteredValue, setEnteredValue] = useState();
const [selectedNumber, setSelectedNumber] = useState(false);
const [displayedTickets, setDisplayedTickets] = useState();
const [confirmed, setConfirmed] = useState(false);
useEffect(() => {
setDisplayedTickets(allTickets);
});
const confirmInputHandler = () => {
const chosenNumber = parseInt(enteredValue);
if (isNaN(chosenNumber) || chosenNumber <= 0) {
Alert.alert(
'Invalid number',
'The number of upvotes has to be greater than 0.',
[{ text: 'Ok', style: 'destructive', onPress: resetInputHandler }]
);
return;
}
setConfirmed(true);
setSelectedNumber(chosenNumber);
Keyboard.dismiss();
};
const resetInputHandler = () => {
setEnteredValue('');
setConfirmed(false);
};
const numberInputHandler = inputText => {
setEnteredValue(inputText.replace(/[^0-9]/g, ''));
};
if (confirmed) {
const foundTickets = displayedTickets.filter(t => t.numberOfVotes >= selectedNumber);
setDisplayedTickets(foundTickets);
setConfirmed(false);
}
return (
<View>
<SearchBarUpvotes
numberInputHandler={numberInputHandler}
confirmInputHandler={confirmInputHandler}
enteredValue={enteredValue}
/>
<FlatList
removeClippedSubviews={false}
data={displayedTickets}
renderItem={({ item }) => (
<TicketItem ticket={item} navigation={navigation} />
)}
keyExtractor={item => item.id}
/>
</View>
);
};
const mapStateToProps = state => ({
ticket: state.ticket
});
export default connect(mapStateToProps, {
getTickets
})(AllTicketList);
The problem is in your second useEffect hook:
useEffect(() => {
setDisplayedTickets(allTickets);
});
This effect, will set the displayedTickets to allTickets on every re-render.
So here's what happens:
1. When you filter the tickets, you're changing the state, and you're setting the displatedTickets to be the filtered tickets: setDisplayedTickets(foundTickets);.
2. The displayedTickets is updated, the component is re-rendered, you see the new tickets for a second, and as soon as it is re-rendered, that effect is executing again and it sets the displayedTickets to allTickets again: setDisplayedTickets(allTickets);.
So here's my advice:
1. Remove the second useEffect - that will prevent the displayedTickets to be set again to allTickets on every re-render .
2. In your flatlist change the data to displayedTickets || allTickets. In this way, when the tickets will be unfiltered - the list will display the allTickets and as soon as you filter them, the list will display the displayedTickets.
So here's how your final code should look like:
const AllTicketList = ({ navigation, ticket: { allTickets }, getTickets }) => {
useEffect(() => {
getTickets();
}, []);
const [enteredValue, setEnteredValue] = useState();
const [selectedNumber, setSelectedNumber] = useState(false);
const [displayedTickets, setDisplayedTickets] = useState();
const [confirmed, setConfirmed] = useState(false);
// Remove this effect
//useEffect(() => {
// setDisplayedTickets(allTickets);
//});
const confirmInputHandler = () => {
const chosenNumber = parseInt(enteredValue);
if (isNaN(chosenNumber) || chosenNumber <= 0) {
Alert.alert(
'Invalid number',
'The number of upvotes has to be greater than 0.',
[{ text: 'Ok', style: 'destructive', onPress: resetInputHandler }]
);
return;
}
setConfirmed(true);
setSelectedNumber(chosenNumber);
Keyboard.dismiss();
};
const resetInputHandler = () => {
setEnteredValue('');
setConfirmed(false);
};
const numberInputHandler = inputText => {
setEnteredValue(inputText.replace(/[^0-9]/g, ''));
};
if (confirmed) {
const foundTickets = displayedTickets.filter(t => t.numberOfVotes >= selectedNumber);
setDisplayedTickets(foundTickets);
setConfirmed(false);
}
return (
<View>
<SearchBarUpvotes
numberInputHandler={numberInputHandler}
confirmInputHandler={confirmInputHandler}
enteredValue={enteredValue}
/>
<FlatList
removeClippedSubviews={false}
data={displayedTickets || allTickets} /* <-- displayedTickets || allTickets instead of displayedTickets */
renderItem={({ item }) => (
<TicketItem ticket={item} navigation={navigation} />
)}
keyExtractor={item => item.id}
/>
</View>
);
};
const mapStateToProps = state => ({
ticket: state.ticket
});
export default connect(mapStateToProps, {
getTickets
})(AllTicketList);