Problem with useState, UseEffect when saving data when getting data - javascript

I am new to react native and I want on the welcome screen of my application to click on a request to an api using axios and the data is saved in a variable using useState and then use this data in another class (AllProductCategory .js) without having to make the request back to the api.
I am using React native 0.62 hooks react navigation 5 and axios.
I have the following in the Navigation.js file. A context that has a useMemo as its value, which contains a return so that it returns an array with information that it brings from an application using axios. the class looks like this:
In this class skip certain lines of code that have nothing to do with the problem I am currently having.
export default function Navigation() {
const [allproducts, setAllproducts] = useState([]);
useEffect(() => {
const _loadAllCategories = async () => {
await axiosClient
.get("/service/product_available")
.then(function (response) {
console.log("Data antes de pasarlo al useState ", response.data);
setAllproducts(response.data);
console.log("Los productos son: ", allproducts);
})
.catch(function (error) {
console.log("Error obteniendo el token", error);
});
};
_loadAllCategories();
}, []);
const authContext = useMemo(
() => ({
getAllProducts: () => {
return allproducts;
},
}),
[]
);
return (
<AuthContext.Provider value={authContext}>
{state.isLoading ? (
<SplashStackScreen />
) : state.userToken == null ? (
<PrincipalStackScreen />
) : (
<MyDrawer />
)}
</AuthContext.Provider>
);
}
With this file what I want is for the data that brings all the products to be loaded when the splash screen is loading and so when I want to use this data on another screen, just call the context variable and return the data without having to make another request to the api.
Then in the class I implement the call of this data using the context
const { getAllProducts } = React.useContext(AuthContext);
const allProducts = getAllProducts();
The complete class is like this:
import React, { useState, useEffect } from "react";
import { View, Text, FlatList, StyleSheet, TouchableOpacity, Dimensions, Image } from "react-native";
import { AuthContext } from "../../context";
var { height, width } = Dimensions.get("window");
export default function AllProductCategoryScreen() {
const { getAllProducts } = React.useContext(AuthContext);
const allProducts = getAllProducts();
function Product_Category({ name, image }) {
console.log("name e image", name);
return (
<View>
<TouchableOpacity>
<Image style={styles.imageCategory} source={{ uri: image }} />
<Text>{name}</Text>
</TouchableOpacity>
</View>
);
}
return (
<View>
<Text>Todas las categorias</Text>
<View style={{ alignItems: "center" }}>
<FlatList
scrollEnabled={true}
numColumns={2}
data={allProducts}
renderItem={({ item }) => (
<Product_Category name={item.name} image={item.imagePath} />
)}
keyExtractor={(item, index) => index.toString()}
/>
</View>
</View>
);
}
My app.js is as follows:
import React from 'react';
import Navigation from "./src/components/Navigation/Navigation"
export default function App() {
return <Navigation />
}
The problem that I currently have is that when I start my app, I show that the request is made with axios and it brings the data correctly, however the useState is not filled with the data that the axios responds to me (it prints []). However if I save changes being in the navigation.js class in visual code the variable allproducts of the navigation class is filled with the data correctly and therefore in the other class where I want to display the data, it paints the data correctly.
I need that when my app loads, the data that the api brings is saved and that when using it in the other class, these data remain so that they can be used and illustrate this data on the screen.

When the function passed to useMemo is created, it captures the first value of allproducts and will always have that value. It will always return [].
In order for useMemo to run again and capture a new value, add that value to the second argument, the array. React will call the memo function whenever a value in that array changes, and then getAllProducts will be created again and capture a new value of allproducts.
const authContext = useMemo(
() => ({
getAllProducts: () => {
return allproducts;
},
}),
[allproducts]
);

Related

Unable to re-render FlatList using extraData prop and State Variable

What I am trying to do
I am trying to get a set of songs play one after another. My app leverages Expo-Av API for playing the songs.
The app is consisted of two components. App.js and a ChildComponent.
App.js renders three songs using a flatlist. It also keeps track of which song has played using a state variable. The sequence of events as I intend them to happen are as follows:
Expected Steps
nextSongToPlay index is set to 0 when the app loads
Flatlist renders 3 ChildComponents using the Data array
Each ChildComponent is passed the index of renderItem as well as the nextSongToPlay
Within the first instance of ChildComponent, once the audio has been loaded using the loadAsync() function which returns a promise. If the promise is resolved AND the index (zero 0) and nextSongToPlay (zero 0 at first run) props are equal, the button within the ChildComponent is pressed by calling its reference (ref.current.props.onPress() )
Once the song finished playing the function passed to onPlayBackStatusUpdate method in ChildComponent is run (looking at the playBackObjectStatus.didJustFinish) and if the didJustFinish property is true, the NextSongToPlay method in App.js is called which has been passed to ChildComponent as a prop.
Once the NextSongToPlay method is run, the netSongToPlay state variable in App.js is incremented by 1.
This causes the re-render of App.js along with the FlatList. In
order to force the FlatList to re-render (since FlatList is a pure
component), the nextSongToPlay variable is passed to the extraData
prop within FlatList
The renderItem is ran again and this time the second ChildComponent will receive index (1) and nextSongToPlay (1). This will cause the loadAsync() method in second ChildComponent to call the ref.current.props.onPress() and play the song.
The process should continue until the last song in the Data array.
Here is what my App.js looks like:
import { View, Text, FlatList } from "react-native";
import React, { useState, useRef, useEffect } from "react";
import ChildComponent from "./ChildComponent";
const Data = [
{
key: "1",
song: "https://www2.cs.uic.edu/~i101/SoundFiles/CantinaBand3.wav",
},
{
key: "2",
song: "https://www2.cs.uic.edu/~i101/SoundFiles/CantinaBand3.wav",
},
{
key: "3",
song: "https://www2.cs.uic.edu/~i101/SoundFiles/CantinaBand3.wav",
},
];
export default function App() {
const [nextSongToPlay, setNextSongToPlay] = useState(0);
const shouldPlayOnItsOwn = useRef(false);
useEffect(() => {
return () => (shouldPlayOnItsOwn.current = false);
});
const NextSongToPlay = () => {
setNextSongToPlay(nextSongToPlay + 1);
};
const setShouldPlayOnItsOwn = () => {
shouldPlayOnItsOwn.current = true;
};
const renderItem = ({ item, index }) => {
return (
<View style={{ marginTop: 10 }}>
<ChildComponent
path={item.path}
NextSongToPlay={() => NextSongToPlay()}
nextSongToPlay={nextSongToPlay}
index={index}
songURL={item.song}
setShouldPlayOnItsOwn={setShouldPlayOnItsOwn}
shouldPlayOnItsOwn={shouldPlayOnItsOwn.current}
/>
</View>
);
};
return (
<View
style={{ justifyContent: "center", alignItems: "center", marginTop: 200 }}
>
<FlatList
data={Data}
renderItem={renderItem}
extraData={nextSongToPlay}
/>
<Text style={{ marginTop: 30 }}>
{" "}
Number of Songs Played: {nextSongToPlay}{" "}
</Text>
</View>
);
}
And this is what my ChildComponent looks like:
import { View, Button } from "react-native";
import React, { useRef, useEffect } from "react";
import { Audio } from "expo-av";
export default function ChildComponent(props) {
const sound = useRef(new Audio.Sound());
const PlayBackStatus = useRef();
const ref = useRef();
const alreadyPlayed = useRef(false);
useEffect(() => {
LoadAudio();
return () => sound.current.unloadAsync();
}, []);
const LoadAudio = async () => {
PlayBackStatus.current = sound.current
.loadAsync({ uri: props.songURL })
.then((res) => {
console.log(`load result : ${res}`);
if (props.index === props.nextSongToPlay && props.shouldPlayOnItsOwn) {
ref.current.props.onPress();
}
})
.catch((err) => console.log(err));
};
const PlayAuido = async () => {
alreadyPlayed
? sound.current.replayAsync()
: (PlayBackStatus.current = sound.current
.playAsync()
.then(() =>
console.log(`result of playing: ${PlayBackStatus.current}`)
)
.catch((err) => console.log(`PlayAsync Failed ${err}`)));
};
sound.current.setOnPlaybackStatusUpdate((playBackObjectStatus) => {
console.log(
`Audio Finished Playing: ${playBackObjectStatus.didJustFinish}`
);
if (playBackObjectStatus.didJustFinish) {
console.log(
`Inside the If Condition, Did the Audio Finished Playing?: ${playBackObjectStatus.didJustFinish}`
);
alreadyPlayed.current = true;
props.NextSongToPlay();
}
});
const onPressHandler = () => {
PlayAuido();
props.setShouldPlayOnItsOwn();
};
return (
<View>
<Button title="Play Sound" onPress={onPressHandler} ref={ref} />
</View>
);
}
What is the Problem
Everything seems to work fine until step 7 in the expected steps section above. Even though the nextSongToPlay state variable does increment after the first song is played, the Flatlist doesnot seem to be getting rendered.
Here is the snack to reproduce this.
Any help in determining the issue is greatly appreciated!
Thanks in advance!

React Native - Unable to set state with arrays

I'm trying to call external api during the first time when the component is rendered and get the list of all countries with async/await function. However I got empty state back after I called the setState function. I'm quite new to RN and been stuck in this problem for days now. Appreciated if anyone can help. Here's what I've been trying to do...
Call countryapi and get the country name and iso2 (only country name and iso2 are available in the response)
After that call I wanna get more details about that specific country, such as iso3 and country flag, so I passed the parameter iso2.
After I destructured for country, iso3, and country flag after the second api call, I called setState each time I destructured for that specific country. However state is not getting updated.
import React, { useState, useEffect } from "react";
import {
Platform,
Text,
View,
TextInput,
StyleSheet,
FlatList,
} from "react-native";
import { FontAwesome } from "react-native-vector-icons";
import countryapi from "../src/countryapi";
const CountryScreen = () => {
const [countryDetails, setCountryDetails] = useState([]);
// Fetch country details { country name, iso3, iso2, country flag }
const countrySearchAPI = async () => {
try {
const countriesData = await countryapi.get();
const countries = countriesData.data;
for (const country in countries) {
const { iso2 } = countries[country];
let countriesDetailsData = await countryapi.get(`/${iso2}`);
let { name, iso3, emoji } = countriesDetailsData.data;
setCountryDetails([
...countryDetails,
{
countryName: name,
iso3,
iso2,
countryFlag: emoji,
},
]);
console.log(countryDetails);
}
} catch (e) {
console.log(e);
}
console.log(countryDetails);
};
useEffect(() => {
countrySearchAPI();
}, []);
return (
<View>
<View style={styles.searchSection}>
<FontAwesome name="search" size={30} />
<TextInput placeholder="Search for Country" style={styles.inputBox} />
</View>
{/* <FlatList
data={countryDetails}
renderItem={({ item }) => {
return (
<Text>
{item.countryName}
{item.countryFlag}
</Text>
);
}}
keyExtractor={(item) => item.id.toString()}
/> */}
</View>
);
};

How do i get data that return from another component react js

I need to fetch an api and get the these data from one file in React native, I am able to get the data in the file that fetch the api by console.log()
API.js
export const merchastListFetch = [
axios.get('http://myapi.com/API/fetch_all')
.then(function (response) {
// console.log(response.data);
//able to get the data here
return response.data;
})
]
Merchant_list.js
import React from 'react'
import { StyleSheet, Text, View } from 'react-native'
import MerchantItem from './MerchantItem'
import { FlatList, TouchableOpacity } from 'react-native-gesture-handler'
import { merchant } from '../../models/models'
import SectionHeader from './SectionHeader'
import { H4, P } from '../typography'
import { merchastListFetch } from '../../API/api'
//get the api from the file
export default function MerchantList({navigation}) {
const renderItem = ({item})=> <MerchantItem {...item}/>
console.log({merchastListFetch});
//cannot access the data here
//get the data like this {"merchastListFetch": [{"_U": 0, "_V": 1, "_W": [Object], "_X": null}]}
return (
<View style={styles.sectionContainer}>
{/* <View style={styles.headerContainer}>
<H4 style={styles.textTitle}>Nearby Merchants</H4>
<View style={styles.flexContainer}>
<P style={styles.textDescription}>Pick from here get the fastest delivery</P>
<TouchableOpacity onPress={() => navigation.navigate('MerchantCategoryScreen')}>
<P style={styles.textLink}>See All</P>
</TouchableOpacity>
</View>
</View> */}
<SectionHeader title="Nearby Merchants" description="Pick from here get the fastest delivery" onLinkPress={() => navigation.navigate('MerchantCategoryScreen')}/>
<FlatList
keyExtractor={(item)=>item.merchant_id}
data={merchant}
renderItem={renderItem}
// itemDimension={80}
/>
</View>
)
}
What i expected from the Merchant_list.js
is the data like how i get in the API.js
which is format like this
{"status":true,"data":[{"shop_id":"1","merchant_id":"1","area_id":"0","agent_id":"1","business_type_id":"1","business_category_id":"1","bill_type_id":"1","currency_id":"0","status_id":"0","register_name":"Dummy Name","register_no":"123456789","retail_name":"Test Only","description":"TESTING USE","commission":"20.00","gst_no":"12345","coming_soon":"0","is_halal":"0","is_active":"1","delivery_charge":"3.50","remarks":"TESTING USE AGAIN","approved_date":"0000-00-00 00:00:00","deleted":"0","created_date":"2020-10-06 15:02:20","created_by":"1","modified_date":"2020-10-08 09:37:53","modified_by":"1","merchant":"Merchant","shop_image":[{"shop_image_id":"3","shop_id":"1","file":"\/images\/shop\/5af16e1c6554160a79bea005.png","file_size":"65124","file_type":"image\/png","is_default":"1","is_active":"1","deleted":"0","created_date":"2020-10-09 13:21:23","created_by":"0","modified_date":"0000-00-00 00:00:00","modified_by":"0"}]},
I did some reseach online and found out it possibly will be the aysnc and await issue , but i don know how to modified the code .
Any help will be appreciated !
You could save the response in an Array State and then call and modify it where you need it I guess.
Something like this:
async CallApiFunc(){
await fetch("http://myapi.com/API/fetch_all")
.then(res => res.json())
.then(res => {
console.log(JSON.stringify(res))
this.setState({
dataArray: res
})
})
.catch(err => console.log(err))
}
this requires a dataArray:[] as state.
After that you can show the entrys in a FlatList for example and also modify them like you do with state.
Modify your function as following.
export const merchastListFetch = ()=>{
return axios.get('http://myapi.com/API/fetch_all')
.then(function (response) {
return response.data;
}).catch((error)=>{
console.log(error)
})
}

How to Re-render Component Only Once after the data is changed?

I am new to React JS. I am making CRUD Operation in React. Everything is fine but when I delete the item from the list I have to refresh the browser tho update the List. How can I solve this?
import React, { useState, useEffect } from 'react'
import axios from 'axios';
import { Segment, Item, Container, Card, Icon, Button } from 'semantic-ui-react';
import { IEmployee } from '../../src/Model/activity'
import { Link, RouteComponentProps } from 'react-router-dom';
interface DetailParams {
id: string;
}
const EmployeeList : React.FC<RouteComponentProps<DetailParams>> = ({ match, history }) => {
const [employees, setEmployees] = useState<IEmployee[]>([])
useEffect(() => {
axios.get('https://localhost:44353/Employee/GetEmployeeList')
.then((response) => {
setEmployees(response.data)
})
}, [])
const deleteEmployee =(id: string) => {
axios.get(`https://localhost:44353/Employee/DeleteEmployee/${id}`)
.then((response) => {
history.push('/employeeList')
})
}
return (
<Container style={{ marginTop: '7em' }}>
<Segment>
{
employees.map(employee => (
<Card key={employee.id}>
{/* <Image src='/images/avatar/large/daniel.jpg' wrapped ui={false} /> */}
<Card.Content>
<Card.Header>{employee.firstName}</Card.Header>
<Card.Meta>{employee.address}</Card.Meta>
<Card.Description>
{employee.organization}
</Card.Description>
</Card.Content>
<Card.Content>
<Button
onClick={() => deleteEmployee(employee.id)}
floated="right"
content="Delete"
color="red" />
<Button
as={Link} to={`/edit/${employee.id}`}
floated="right"
content="View"
color="blue" />
</Card.Content>
</Card>
))
}
</Segment>
</Container>
)
}
export default EmployeeList
The above code is of EmployeeList Component which is routed by ** /employeeList ** . Here is the UI of the code
when I delete the item from the list I need to reload the browser to update the List. I tried using employee dependent in useEffect
useEffect(() => {
axios.get('https://localhost:44353/Employee/GetEmployeeList')
.then((response) => {
setEmployees(response.data)
})
}, [employees])
this worked fine but the API method is executing infinitely. How do I solve this?
Two things can be done
if your delete api returns the updated data you can just call setEmployess and set the updated value .
or you can filter the deleted value from the state employees
const deleteEmployee =(id: string) => {
//add this in axios call success
let updatedEmployee = [...employees];
updatedEmployee.filter(eachEmployee=>eachEmployee.id !== id);
setEmployees(updatedEmployee);
}
Instead of refreshing the page you should just make another request after the delete request to get an updated employees list.
const deleteEmployee = async (id: string) => {
// Delete employee
await axios.get(`https://localhost:44353/Employee/DeleteEmployee/${id}`)
// Get a fresh list
const employees = (await axios.get('https://localhost:44353/Employee/GetEmployeeList')).data
setEmployees(employees)
// Navigate
history.push('/employeeList')
}

React Native Hooks: unable to display values in return() from useEffect function

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>
);
}

Categories

Resources