Setting data returned by useQuery as state - react-native

I have been using client.query to get data from my database and setting states using useState.
Here's an example:
const [videos, setVideos] = useState([]);
client.query({ query: GET_VIDEOS })
.then(response => {
setVideos(response.data.videos);
})
However, this does not load 100% of the time. Usually, it doesn't load when I load the app for the first time in a while. I typically have to reboot in these situations.
This issue makes me want to look into useQuery instead of client.query.
However, the examples in the documentation only show how we can use useQuery to make direct changes in the components.
Example:
function Dogs({ onDogSelected }) {
const { loading, error, data } = useQuery(GET_DOGS);
if (loading) return 'Loading...';
if (error) return `Error! ${error.message}`;
return (
<select name="dog" onChange={onDogSelected}>
{data.dogs.map(dog => (
<option key={dog.id} value={dog.breed}>
{dog.breed}
</option>
))}
</select>
);
}
However, I don't want to make changes to the components right now. I would much rather query the data and then set the queried data as a state, using useState.
How would I best accomplish this? One idea that I had was to create a component that returns null but queries the data. It would look something like this:
function Videos() {
const { loading, error, data } = useQuery(GET_VIDEOS);
if (loading) return 'Loading...';
if (error) return `Error! ${error.message}`;
setVideos(data);
return null;
}
I was curious to hear what are best practices for such situations.

react-apollo's useQuery has an onCompleted as one of its options, so you can use it like
const { loading, error } = useQuery(GET_VIDEOS, {
onCompleted: (data) => setVideos(data)
});

the best practise would be to use the data directly, without setting the state.
you have only showed the calling of setState, somewhere in your component the video state variable is used right?
pass the fetched data directly there. you need not call "setVideos" to trigger the change. whenever the query result is changed the UI will be changed accordingly.
There is useLazyQuery if your use case is not to fetch the data upfront.

Related

Hold back react native UI with async until data from Firebase is fully loaded

My db is loading fine from Firebase.
How do I write async function correctly to make sure nothing renders until setDATA is set with the data from the db
useEffect(() => {
const db = getDatabase();
return onValue(ref(db, "/beaches"), (querySnapShot) => {
setDATA(querySnapShot.val() || {});
});}, []);
The simplest way is going to be to wrap a conditional around the JSX that you don't want to render, and check whether the data exists.
If we say that your data is getting assigned to a variabl called data, this would look like
return (
<>
{!!data && (
<your jsx here>
)}
</>
)

Use fetch and asyncData together

I've a doubt..
In my Nuxt static project pages (Nuxt version => 2.12), I need to be fast with data recuperation.
The assumption:
With asyncData, the objective of speed is checked, because with asyncData I can get data during the component render. But this project has 3 different possible API calls, as many as the languages that I set and, when I select one language the value is saved in the Vuex store.
At the same time of this process, the language value is also saved on local storage, so, if I use only asyncData, when the page is refreshed the API call will not be the right call with the saved language (asyncData can't access the local storage).
Here, the fetch hook enters into the game, with the watch set on the state language value of the store, it can trigger the fetch and get the right data. Moreover, even when the page is refreshed, the fetch hook, being able to read the values of the local storage, can do its work great.
So why don't I use only the fetch hook? Because the fetch is slower than asyncData.
The questions:
Is the use of both fetch and asyncData an anti-pattern?
Is there a better way?
Here my code:
export default {
asyncData (context) {
const slug = (context.route.path === '/' || context.route.path === '') ? '/home' : context.route.path
return context.app.$storyapi
.get(`cdn/stories${slug}`, {
language: context.store.state.language.language
}).then((res) => {
return res.data
}).catch((res) => {
context.$errorMessage(res.response,
'Sorry but this content doesn\'t extist', `Sorry, but the content called: "${context.route.name}" has a problem or doesn't exist`
)
})
},
data () {
return {
story: {
content: {}
}
}
},
async fetch () {
const slug = (this.$route.path === '/' || this.$route.path === '') ? '/home' : this.$route.path
const { data } = await this.$storyapi.get(`cdn/stories${slug}`, {
language: this.$store.state.language.language
})
this.story = data.story
},
watch: {
'$store.state.language.language': '$fetch'
}
}
Just to complete the information I would like to further say that the code works well, the matter is concerning the best practice.
Since when fetch() is slower than asyncData()?
Also, if you really need to re-run something you can totally use await this.$nuxt.refresh().
You could probably run both of those but they can kinda make the same thing, so it's kinda a duplication and I'd recommend to choose only one.
I'm not sure how it being located in localStorage is an issue but you could probably use one of the available packages to have an universal storage of this data, like this one: https://github.com/unjs/unstorage
If not, cookies should be enough too (available both on server and client).

React-Native - useEffect causes infinite loop

I am trying to show some dynamic content in my component but somehow useEffect causes a infinite loop.
What can be the problem?
useEffect(() => {
retrieveLocalData('following').then((contacts) => {
setLocalData(JSON.parse(contacts));
});
}, [getLocalData]);
async function retrieveLocalData(key) {
try {
return await AsyncStorage.getItem(key);
} catch (error) {
console.log(error);
}
}
console.log('test'); // infinite
Code: https://codepen.io/eneskul/pen/OJWEgmw
Updated Answer
The infinite loop is a result of the useEffect hook updating the same value that is triggering the hook to run in the first place.
Here's a simple example to illustrate the problem:
const [value, setValue] = useState({ foo: 'bar' });
useEffect(() => {
Promise.resolve('{"foo":"bar"}').then((result) => {
const newValue = JSON.parse(result);
// `newValue` is a new object, even if its content is identical to `value`.
setValue(newValue);
});
}, [value]);
In this example, when value is set, it causes the useEffect hook to execute, which will asynchronously update value with a new object, which will cause the useEffect hook to execute again, and so on. Even though the contents of the objects are identical, the JSON.parse call creates a new object with a new reference.
You can prevent the infinite loop by doing a deep equality check of the two objects before updating the state. Using something like Lodash's isEqual function makes this pretty easy.
useEffect(() => {
Promise.resolve('{"foo":"bar"}').then((result) => {
setValue((prev) => {
const newValue = JSON.parse(result);
// Do a deep comparison and only update state with new object if content is different.
return isEqual(prev, newValue) ? prev : newValue;
});
});
}, [value]);
In this example, the reference to value will only change if the contents of the objects are different.
However, this only explains what the problem is. I'm not sure what the right solution is for your problem, since it's not clear why the component only needs to load data from local storage into state when the state changes, but the state is only updated when it loads from local storage. There seems to be a "chicken or the egg" problem here. It feels like there should be something else that should trigger loading data from local storage into state, other than the data that was just loaded from local storage into state.
Previous Answer
The likely culprit here is getLocalData in the dependency list of the useEffect hook. If that is not a stable reference (i.e. the reference changes on each render), then it will cause the useEffect hook to execute, which will then trigger a state update, which will trigger a render, which will cause useEffect to execute again, which starts the whole thing over again.
In the sample code, it's not clear where getLocalData comes from. Wherever it comes from, you might consider wrapping it with the useCallback hook to create a stable reference. If it's just a typo and meant to be retrieveLocalData, then that is definitely the issue. Because retrieveLocalData is declared inside the component's render function, it will create a new instance of the function (with a new reference) on each render.
I would just move it inside the useEffect hook and eliminate the dependencies.
useEffect(() => {
AsyncStorage.getItem('following')
.then((contacts) => {
setLocalData(JSON.parse(contacts));
})
.catch((error) => {
console.log(error);
});
}, []);

watchEffect with side effect but without infinite loop

what I am trying to do is:
construct an URL based on props
initially and whenever the URL changes, fetch some data
Since this is asynchronous and I also want to indicate loading, I use this construct:
const pageUrl = computed(() => `/api/${props.foo}/${props.bar}`)
const state = reactive({
page: null,
error: null,
loading: false
})
watchEffect(async () => {
state.loading = true
try {
const resp = await axios.get(pageUrl.value)
state.page = resp.data
} catch (err) {
state.error = err
console.log(err)
}
state.loading = false
})
// return page, loading, error for the component to use
The problem is that this seems to run in an infinite loop because in the body, I am not only reacting to the pageUrl, but also to state which itself is modified in the function body.
Alternatively, I can use watch(pageUrl, async pageUrl => { ... }), but this seems only to be triggered when pageUrl changes (in my case: I modify the URL because the props are updated via vue-router, but not when I initially visit the URL).
What should I do here, is my idea of signalling the loading state not appropriate here?
From a logical point of view, the page is a computed value, the only reason I use watch here is that it's asynchronous and might yield an error as well.
Thanks to Husam I got aware of the bug, and it seems like changing a piece of state twice introduces this behaviour - in my case setting loading to true and then again to false.
This behaviour is not apparent in Vue3, and a workaround (and in general maybe the much cleaner method) could be to directly use watch instead of watchEffect.
The source code shows different overloads of the funciton, and there is an options argument that is not directly documented in the Vue3 API. There, I found that my call above needs to read like watch(value, async value => { effect }, { immediate: true }).

Initializing a map in firestore

I'm trying to build an app using react native with a firestore database. I'm fairly new to the react native framework (as well as working with firestore), so it's possible I might be trying to solve this problem the wrong way.
I have a database that works well and is already populated. For each user of this app, I'd like to add a map to their entry. I want to use this map to store some data about the user which they can fill out later.
Here's some code:
componentDidMount() {
this.readProfile(this.props.uid);
}
readProfile = (uid) => {
this.props.getProfile(uid).then((profile) =>
{
if(!profile.userMap)
{
profile.userMap = generateUserMap();
}
...
}
export const generateUserMap = function () {
var map = new Map();
SomeEnum.forEach((key, value) => {
map.set(key, false);
});
AnotherEnum.forEach((key, value) => {
map.set(key, false);
});
OneMoreEnum.forEach((key, value) => {
map.set(key, false);
});
return map;
};
...
<Input
value={this.state.profile.userMap[SomeEnum.Foo]}
onChangeText={(foo) => this.updateUserMap({ foo })}
/>
What I want this code to be doing is to read in the user's profile when I load the page. That part seems to be working fine. My next concern is to properly initialize the map object. The code doesn't seem to be properly initializing the map, but I'm not sure why. Here's why I say that:
TypeError: Cannot read property 'Foo' of undefined
With the stack trace pointing to my component's Connect() method.
Any help would be greatly appreciated.
EDIT: Apologies for the oversight, here is the updateUserMap function:
updateUserMap = (property) => {
const profile = Object.assign({}, this.state.profile, property);
this.setState({ profile });
}
So, as anyone who looks over this question can probably tell, I was doing a few things pretty wrong.
The error I'm getting referred specifically to that input block in my render method - this.state.profile.userMap was undefined at that point. I can guarantee that it won't be undefined if I do my check within the render method but before I'm accessing the userMap. Because of how the lifecycle methods work in react native, ComponentDidMount wouldn't be called before my render method would.
My enum code also wouldn't work. I changed that to a simple for loop and it works like a charm.
Here's my updated code:
render() {
if(!this.state.profile.userMap)
{
this.state.profile.userMap = generateUserMap();
}