fetch returns first empty array, after that it returns response - react-native

I'm making my final degree project and now I'm involved in the part to show towns on a map using data from a backend URL.
The problem is that fetch returns at first an empty array, and I need it to stay loading until the variable is a valid JSON.
const [isLoading, setLoading] = useState(true);
const [markers, setMarkers] = useState([]);
const getTowns = async () => {
try {
const response = await fetch(`${baseUrl}/towns`);
const json = await response.json();
setMarkers(json.data);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
useEffect(() => {
getTowns();
}, []);
Another question is: Why when I put a console.log('whatever') it appears more than one time on the console. Don't understand why
What I need is fetch to setLoading(false) only when response is a JSON, not an empty array

What you can do is add these 2 hooks to your code:
A state for the returned response (with initial value an empty array)
A useEffect which will set to false isLoading once the response state change value
const [response, setResponse] = useState([]);
const getTowns = async () => {
try {
setResponse(() => await fetch(`${baseUrl}/towns`));
const json = await response.json();
setMarkers(json.data);
} catch (error) {
console.error(error);
}
};
useEffect(() => {
setLoading(() => false);
}, [response]);

your code is fine you juste don't need the isLoading useState Hook. you can test with the value of markers
Why when I put a console.log('whatever') it appears more than one time on the console. Don't understand why
when the component first render getTowns runs in useEffect and since it updates the state the component renders again. learn more here about react component lifecycle
what I suggest is when you are returning your jsx you check if markers is still empty
const [markers, setMarkers] = useState('');
if(markers === ''){
return (<div>Loading...<div/>)
} else {
return(<>
.... what you need to return in you component
</>)
}

Related

React native with axios fetch first post delayed

I am using react native, and axios.
I have two parts.
The exercice list that is rendered with useEffect in a div. Inside, there is a Input form which once pressed the Add set button, the set is added to the database and the exercices are fetched again with the passed function.
The main problem is that when I first add an exercice, the exercice s not rendering. I must go back and come again in the page to render the first one. after doing this process I can add as many exercices... And with delete is same. I can delete any exercice but when deleting the last one, it persist and I must leave the page to see the changes...
THIS IS THE FUNCTION THAT ADD THE exercices. It executes once the alert button is pressed
const NewExercice = ({dayID, getAllEx}) => {
// States and ontext change functions
const [exName, setexName] = useState('');
const [comment, setcomment] = useState('');
const handleExname = text => setexName(text);
const handleComments = text => setcomment(text);
// Add new exercices
const handleNewExercice = async () => {
try
{
const status = await data.post('/api/create-exercice', {dayID, exName, comments: comment});
Alert.alert(
'Exercice created',
'Please add new sets to existing exercices',
[
{
text: 'Ok!',
// Fetch again for all the exercices
onPress: getAllEx
}
]
)
}
catch (error)
{
console.log(error);
}
}
Bellow is the component that adds map over the array state
<View>
{error ? (<Text>No exercices created yet.</Text>) :
exArray.map(obj => (
<ExerciceWrapper getAllEx={getAllExercices} navigation={navigation} key={obj.exID} object={obj} />
))}
</View>
Bellow is the function that fetch the data from the DB and set the state to be able to be rendered in the component above
const getAllExercices = async () => {
try
{
const response = await data.get('/api/get-all-ex/' + dayID);
setExArray(response.data);
}
catch (error)
{
if (error.response.status === 404) return setError(true);
else return console.log(error);
}
}
useEffect(() => {
getAllExercices();
}, []);
You need to toggle the error value when you have successful fetch as well
update code to this
const getAllExercices = async () => {
try
{
const response = await data.get('/api/get-all-ex/' + dayID);
setExArray(response.data);
setError(response.data.length < 1)
}
catch (error)
{
if (error.response.status === 404) return setError(true);
else return console.log(error);
}
}

wait for onSnapshot fetching data

I'm currently learning React Native (Expo).
I want to use redux and react-native-firebase.
When I subscribe to firebase (onSnapshot) at startup of my app, it returns the data from firebase. But since onSnapchot doesn't return a promise, I can't use it for my app-loading component.
Therefore, I also need to fetch the data from firebase to prevent the app from flicker.
The result is that at startup of my app I fetch the data twice.
So my question is:
How can I wait for onSnapshot loading my data from firebase?
Thanks
const Manager = (props) => {
//STATE
const [init, setInit] = useState(false);
//HOOKS
const fetchData = useFetchData();
useInitFirebaseSubscriptions();
//FUNCTIONS
async function onInit() {
console.log('[MANAGER]: loading app...');
await Promise.all([fetchData()]);
}
function onFinishedInit() {
console.log('[MANAGER]: ...app loading successfull!');
setInit(true);
}
//RETURN
if (!init) {
return <AppLoading startAsync={onInit} onFinish={onFinishedInit} onError={console.warn} />;
} else {
return props.children;
}
};
export default Manager;
//INITIAL FETCH BEFORE RENDERING
export function useFetchData() {
const dispatch = useDispatch();
return async function () {
try {
await firestore()
.collection('users')
.get()
.then((querySnapshot) => dispatch(actions.fetch(querySnapshot)));
} catch (err) {
console.log(err.message);
}
};
}
//INIT SUBSCRIPTIONS TO FIREBASE
export function useInitFirebaseSubscriptions() {
const dispatch = useDispatch();
useEffect(() => {
console.log('[CONTROLLER]: subscribed to Firebase');
const unsubscribe = firestore()
.collection('users')
.onSnapshot(
(querySnapshot) => dispatch(action.fetch(querySnapshot)),
(error) => console.log(error)
);
return () => {
unsubscribe();
console.log('[CONTROLLER]: unsubscribed from Firebase');
};
}, []);
}
[MANAGER]: loading app...
[MANAGER]: subscribed to Firebase
[USER_REDUCER]: fetched data
[USER_REDUCER]: fetched data
[MANAGER]: ...app loading successfull!
I think you can accomplish your goal by adding some "loading" state in redux for when you are actively fetching data from firebase. Add the state and reducer cases specific to this data fetching/loading.
Example code:
export function useInitFirebaseSubscriptions() {
const dispatch = useDispatch();
useEffect(() => {
console.log('[CONTROLLER]: subscribed to Firebase');
dispatch(action.startFetch()); // <-- dispatch starting data fetch
const unsubscribe = firestore()
.collection('users')
.onSnapshot(
(querySnapshot) => {
dispatch(action.fetch(querySnapshot));
dispatch(action.completedFetch()); // <-- done fetching
},
(error) => {
console.log(error);
dispatch(action.completedFetch()); // <-- done fetching
},
);
return () => {
unsubscribe();
console.log('[CONTROLLER]: unsubscribed from Firebase');
};
}, []);
};
Select the loading state from the redux store and conditionally render the loading UI, otherwise render the passed children.
const Manager = (props) => {
const isFetchingData = useSelector(state => state.isFetchingData);
if (isFetchingData) {
return <AppLoadingIndicator />;
}
return props.children; // *
};
* Generally you may use some additional conditional rendering here depending on if data was actually fetched/returned and is just empty, or if there was an error, etc... basically provide a bit of a result status.

problem with storing data from cache into useState in react native

const [loggedInData, setLoggedInData] = useState([]);
const getLoginFromCache = async() => {
try{
let logData = await AsyncStorage.getItem('#MyDish:loginData');
let parsed = await JSON.parse(logData);
console.log(parsed);
setLoggedInData(parsed);
}
catch(error){
console.log(error);
}
}
getLoginFromCache()
console.log(loggedInData);
the result console.log(loggedInData) is always empty. whereas when i console.log(parsed) doesn't return empty
Use redux persist for store data on cache and to state.
https://www.npmjs.com/package/redux-persist
React set state is async, so console.log(loggedInData); will always print the initializtion state of loggedInData, in this case this state is an empty array
Try to wrap your getLoginFromCache function inside an useEffect like this:
const [loggedInData, setLoggedInData] = useState([]);
useEffect(() => {
const getLoginFromCache = async() => {
try{
let logData = await AsyncStorage.getItem('#MyDish:loginData');
let parsed = await JSON.parse(logData);
console.log(parsed);
setLoggedInData(parsed);
}
catch(error){
console.log(error);
}
}
getLoginFromCache()
}, [])
and if you want tracking your loggedInData state, you can use another useEffect to log that one:
useEffect(() => console.log(loggedInData),[loggedInData])

how to handle the return values from reducer, inside a functional component

This may be a basic question, but I'm new to React Native and stuck here.
My code pasted below. reducer and functional component. I want to capture the response returned from reducer.
reducer.js
export const ActivationCenterReducer = (
state = INIT_KIT_STATE,
{ type, payload = {} }
) => {
switch (type) {
case 'KIT_ACTIVATION_SUCCESS_DATA': {
const { message, response_code, apiLoading, apiError } = payload;
return {
...state,
apiLoading: apiLoading,
apiError: apiError,
message: message,
response_code: response_code
};
}
// ...
}
// ...
};
Functional Component class:
const kitActivationCenter = ({ route, navigation }) => {
const response_code = useSelector(
store => store.kitActivationCenter.response_code
);
const handleKitActivation = () => {
/*This will call the validation() inside action.js and that follows the reducer.js file. where reducer.js file returning the values on success response. but I am not able to access that response_code returned from reducer.
How to save the response_code from the below dispatch function.*/
dispatch(Validation(locator, pin));
if (response_code === 200) {
// should navigate to the next screen
}
};
};
My question is how to capture the returned response_code from reducer.
I'm able to navigate to the next screen on clicking the submit button couple of times.I notice that first time when the dispatch function is called, the state of the response_code is not updating , hence the response_code != 200.
I want a way to capture the response and assign to variable.
Thanks in advance.
You are probably looking at the old value of response_code in your handleKitActivation.
const kitActivationCenter = ({ route, navigation }) => {
const response_code = useSelector(
store => store.kitActivationCenter.response_code
);
const handleKitActivation = () => {
dispatch(Validation(locator, pin));
// HERE the response_code does not have result value
// of your calling dispatch(Validation(locator, pin)) above yet
if (response_code === 200) {
// should navigate to the next screen
}
};
};
I suggest to move your response_code hanfling to the useEffect:
const kitActivationCenter = ({ route, navigation }) => {
const response_code = useSelector(
store => store.kitActivationCenter.response_code
);
// this effect will run whenever your response_code changes
useEffect(() => {
if (response_code === 200) {
// should navigate to the next screen
}
}, [response_code]);
const handleKitActivation = () => {
dispatch(Validation(locator, pin));
};
};

React Natie, When I use useState data on componentWillUnmount, just getting first data

When I use useState data inside useEffect return function (componentWillUnmount), I am just getting first data,
I am changing note values on textview and when i go back another screen, useEffect return function working well but note values is "". Why this happining and how can i solve it?
const [note, setNote] = useState("");
useEffect(() => {
getContent();
return () =>{
saveToStorage({note:note}, "componentWillUnmount");
};
}, []);
const saveToStorage = (data) =>{
console.log(data); // note:""
}
useEffect will get the note reference only once since you have added [] as dependencies instead give notes as dependencies so it will be called evertime so it can get updated note reference
code:
const [note, setNote] = useState("");
React.useEffect(() => {
if (condition to check wether its first time) {
// getNotes()
}
return () => {
saveToStorage({ note: note }, 'componentWillUnmount');
}
}, [note]);
const saveToStorage = (data) =>{
console.log(data); // note:""
}