React Native Hooks initializer not taking the correct value - react-native

What I am trying to do is sync a list of attendees from an online database, and if the current user is in the list, then disable a button, else enable the button.
I am using react native hook (I am not sure if I am using the term correctly as I am fairly new to react), in order to set the value of disabling the button.
The issue that I am facing is that the value is getting initialized to false, even tho it should clearly get initialized to true.
After adding some logging I made sure that the function is executing correctly and reaching the code where it sets the value to true.
const [buttonDisabled, changeButtonState] = useState( () => {
var database = firebase.database();
var userId = firebase.auth().currentUser.uid;
const dbRef = firebase.database().ref();
var Attendees = [];
var disable = false;
dbRef.child("gameAttendees").child(gameinfo.gameID).get().then((snapshot) => {
if (snapshot.exists()) {
Attendees = snapshot.val().Attendees;
for(var i=0;i<Attendees.length;i++){
if(Attendees[i]==userId){
return true;
}
}
} else {
console.log("no value");
return false;
}
}).catch((error) => {
console.error(error);
});
});

Adding an example of an async mount effect:
const Comp = () => {
const [s, setS] = useState(); // State will be undefined for first n renders
useEffect(() => {
// Call the async function and set the component state some time in the future
someAsyncFunction().then(result => setS(result));
}, []); // An effect with no dependencies will run only once on mount
return </>;
};

Related

Async Storage / Secure Store help for React Native

I'm doing a test app to learn react native, and I'm trying to use secure store (a bit like async storage) to store my individual goals and save them. So far it's working, however when I refresh the app only the last goal I entered gets loaded.
Where am I going wrong here? In my console log the full array is shown with both the old and the new ones I add, then I refresh and I only have one left.
const [goals, setGoals] = useState([])
const addGoal = async (goal) => {
try{
const goalJson = JSON.stringify({text: goal, id:`${Math.random()}`, todos:[], date: Date.now(), percentage:0})
await SecureStore.setItemAsync("Goal", goalJson)
load()
}
catch (err) {alert(err)}
}
const load = async() => {
try {
const goalValue = await SecureStore.getItemAsync("Goal")
const parsed = JSON.parse(goalValue)
if(goals !== null) {
setGoals([...goals, parsed])
console.log(goals)
}
}catch (err) {alert(err)}
}
useEffect(()=> {
load()
},[])
SecureStore is like a key-value database, so currently you're always writing to the same key Goal and your addGoal function is erasing the previous value with goalJson content.
Instead, load once the goals from storage, then update the goals state when a new goal is added, and write them all to on storage each time goals value is updated.
This how effects works, by "reacting" to a change of value. This is just a little bit more complicated because of SecureStorage async functions.
Here is my (untested) improved code. I renamed the storage key from Goal to Goals.
const [goals, setGoals] = useState([])
const [loaded, setLoaded] = useState(false)
useEffect(()=> {
async function load() {
try {
const goalsValue = await SecureStore.getItemAsync("Goals")
const goalsParsed = JSON.parse(goalsValue)
if (goalsParsed !== null) {
setGoals(goalsParsed)
}
setLoaded(true)
} catch (err) { alert(err) }
}
load()
}, []) // load only when component mount
const addGoal = (text) => {
const goal = { text, id:`${Math.random()}`, todos:[],
date: Date.now(), percentage:0 }
setGoals([...goals, goal])
})
useEffect(() => {
async function saveGoals() {
try {
// save all goals to storage
const goalsJson = JSON.stringify(goals)
await SecureStore.setItemAsync("Goals", goalsJson)
}
catch (err) {alert(err)}
}
if (loaded) { // don't save before saved goals were loaded
saveGoals();
}
}, [goals, loaded]) // run the effect each time goals is changed

How to make pagination work? Async await function in vue.js 3 setup

I was trying to make an app which lists a user's repositories from github using github API, however I'm having a big problem with fetching data from all pages (so far I can only get repos from one page). I tried to fix it by using an async/await function (instead of Promise), but it's also my first time using vue3 and I have no idea how to have a function inside of the setup() method.
The current code is here:
https://github.com/agzpie/user_repos
My try at using async/await, which didn't work:
import ListElement from "./components/ListElement";
import { ref, reactive, toRefs, watchEffect, computed } from "vue";
export default {
name: "App",
components: {
ListElement,
},
setup() {
const name = ref(null);
const userName = ref(null);
const state = reactive({ data: [] });
let success = ref(null);
const userNameValidator = /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i;
const split1 = reactive({ spl1: [] });
const split2 = reactive({ spl2: [] });
async function myFetch() {};
/*
* Check for input in the form and then fetch data
*/
watchEffect(() => {
if (!userName.value) return;
if (!userNameValidator.test(userName.value)) {
console.log("Username has invalid characters");
return;
}
let hasNext = false;
state.data = [];
do {
async function myFetch() {
let url = `https://api.github.com/users/${userName.value}/repos?per_page=5`;
let response = await fetch(url);
if (!response.ok) {
success.value = false;
throw new Error(`HTTP error! status: ${response.status}`);
}
success.value = true;
// check response.headers for Link to get next page url
split1.spl1 = response.headers.get("Link").split(",");
let j = 0;
while (j < split1.spl1.length) {
split2.spl2[j] = split1.spl1[j].split(";");
console.log(split2.spl2[j][0]);
console.log(split2.spl2[j][1]);
if (split2.spl2[j][1].includes("next")) {
let urlNext = split2.spl2[j][0].replace(/[<>(\s)*]/g, "");
console.log(urlNext);
url = urlNext;
hasNext = true;
break;
} else {
hasNext = false;
}
j++;
}
// second .then
let myData = await response.json();
state.data.push(...myData);
console.log("data", myData);
name.value = "";
}
myFetch().catch((err) => {
if (err.status == 404) {
console.log("User not found");
} else {
console.log(err.message);
console.log("oh no (internet probably)!");
}
});
} while (hasNext);
});
// Sort list by star count
const orderedList = computed(() => {
if (state.data == 0) {
return [];
}
return [...state.data].sort((a, b) => {
return a.stargazers_count < b.stargazers_count ? 1 : -1;
});
});
return {
myFetch,
success,
isActive: true,
name,
userName,
ListElement,
...toRefs(state),
orderedList,
};
},
};
Any help would be highly appreciated
The call to myFetch() near the end is a call to an async function without an await, so it is effectively going to loop (if hasNext was initialized to true, but it isn't) without waiting for it to complete.
You should probably change that line to await myFetch() and wrap it all with a try/catch block.
I also don't really care for the way you're directly updating state inside the async myFetch call (it could also be doing several of those if it looped) and perhaps it should be returning the data from myFetch instead, and then you can use let result = await myFetch() and then make use of that when it returns.
Also, instead of awaiting myFetch() result, you could not await it but push it onto a requests array and then use await Promise.all(requests) outside the loop and it is one operation to await, all requests running in parallel. In fact, it should probably be await Promise.allSettled(requests) in case one of them fails. See allSettled for more.
But also I wonder why you're reading it paged if the goal is to fetch them all anyway? To reduce load on the server? If that is true, issuing them paged but in parallel would probably increase the load since it will still read and return all the data but require multiple calls.

React Native useEffect() re-renders too much

i have a useEffect function where a redux action is called and data is written to prop. My Problem is that useEffect loop many times and flooded the server with requests.
const { loescherData, navigation } = props;
useEffect(() => {
AsyncStorage.getItem('userdata').then((userdata) => {
if (userdata) {
console.log(new Date());
console.log(userdata);
var user = JSON.parse(userdata);
props.fetchLoescherDetails(user.standort);
setData(props.loescherData);
}
});
}, [loescherData]);
if i leave it blank the rendering is finished before receiving data and the content would not updated.
is there another way to work with this function?
loescherData won't be available right after calling your redux-action fetchLoescherDetails ... and changing component by setData will cause an infinite rendering cause your current useEffect has a dependency on loescherData
So I'd suggest you exec your redux-action onComponentDidMount by passing an empty-deps [] to your effect ... and then consume the output of you action in a different effect
useEffect(() => {
AsyncStorage.getItem('userdata').then((userdata) => {
if (userdata) {
console.log(new Date());
console.log(userdata);
var user = JSON.parse(userdata);
props.fetchLoescherDetails(user.standort);
// setData(props.loescherData);
}
});
}, []);
useEffect(() => {
if (loescherData) {
// do some with loescherData like setState
}
}, [loescherData]);

React Native hooks - correct use of useEffect()?

I'm new to hooks and ran across this setup on SO and wanted to confirm that this is the correct pattern. I was getting the RN "unmounted component" leak warning message before and this seemed to solve it. I'm trying to mimic in some way compnentDidMount. This is part of a phone number verify sign up flow and onMount I want to just check for navigation and then fire off a side effect, set mounted true and then unmount correctly.
const SMSVerifyEnterPinScreen = ({ route, navigation }) => {
const [didMount, setDidMount] = useState(false)
const { phoneNumber } = route.params
useEffect(() => {
if(navigation) {
signInWithPhoneNumber(phoneNumber)
setDidMount(true)
}
return () => setDidMount(false)
}, [])
if (!didMount) { return null }
async function signInWithPhoneNumber(phoneNumber) {
const confirmation = await auth().signInWithPhoneNumber('+1'+phoneNumber)
...
}
return (
...
)
}
RN 0.62.2 with react-nav 5 - thanks!
Since signInWithPhoneNumber is a async function and will setState you will see warning it the component is unmounted before the response is available
In order to handle such scenarios you can keep a variable to keep track whether its mounted or not and then only set state is the mounted variable is true
However you do not need to return null if component has unmounted since that doesn't accomplish anything. The component is removed from view and will anyways not render anything.
Also you do not need to maintain this value in state, instead use a ref
const SMSVerifyEnterPinScreen = ({ route, navigation }) => {
const isMounted = useRef(true)
const { phoneNumber } = route.params
useEffect(() => {
if(navigation) {
signInWithPhoneNumber(phoneNumber)
}
return () => {isMounted.current = false;}
}, [])
async function signInWithPhoneNumber(phoneNumber) {
const confirmation = await auth().signInWithPhoneNumber('+1'+phoneNumber)
...
}
return (
...
)
}

how to get multiple fields from expo SecureStore

I am new to ES6 and react-native, trying to get multiple values from the SecureStore.
I think I am misunderstanding promises here ... global.userData is empty in the Promise.all(promises).then function. The relevant values do exist in the secure store
My code is:-
getUserData(fields) {
var promises = [];
var that = this;
global.userData = {};
function getField(field) {
return SecureStore.getItemAsync(field)
.then(res => {
console.log(field+"="+res); // this appears after the log below
global.userData[field] = res;
})
.catch(error => {
global.userData[field] = null;
});
}
fields.map(field => {
promises.push[getField(field)];
});
Promise.all(promises).then(function(v) {
console.log(global.userData); // this is empty
that.setState({ isReady: true }); // allow page to render
});
}
getUserData(["userId", "userName","etc"]);
My bad ... inadvertantly used
promises.push[getField(field)];
should have been:
promises.push(getField(field));
Suprised it wasn't detected as a syntax error ...