Populate UI once data finishes loading - react-native

I am fetching data from a server, and I would like to do this every time the user enters the screen. The problem is, I start getting the data when the screen function is called, and apparently there isn't enough time to fetch the data before the screen is populated. What I am trying now is something like:
function MyScreen() {
model.refreshData();
const data = model.getData();
return (<View><Text>{data}</Text></View>);
}
This does not work, because, again, data has not been refreshed by the time that we do the "getData". I can't use a .then, because then I won't be able to return the (<View><Text>{data}</Text></View>) part. Is there a way that I can force the screen to update once model.refreshData() has finished?

Use reactive state to change the return value of MyScreen when the data is done loading. The following is pseudocode:
function MyScreen() {
const [screenData, setScreenData] = useState(null);
model.refreshData();
setScreenData(model.getData());
// Component will render nothing until data is finished loading
if (!screenData) {
return null;
}
// Once data is loaded your full component will render
return (<View><Text>{screenData}</Text></View>);
}
If getData() is a promise then instead of
setScreenData(model.getData());
do
model.getData().then((data) => setScreenData(data));

Related

React navigation params passing too slow

I am working on a screen which takes data through route.params.
const {
prompt,
isUpscaling,
imageToBeUpscaled,
useDefaultPrompts = true,
isChallenge = false,
challengeData = {},
isCommunityPage,
} = route.params;
When navigating to this page, the params first being passed are actually the previous values. However, upon saving the file(thereby updating the state) the expected params load in.
I am using the params' data in a focusEffect function:
useFocusEffect(
useCallback(() => {
setIsLoading(true);
setSafeAreaBackgroundColor("#1a1a1a");
generateImage();
}, [])
);
However, the original data being passed to this function are the old values. In short, the values being used are the previous route.params value from an old screen and not the new ones being passed in. The new params are being passed in but (I'm assuming) are not loading before the useFocusEffect is called. This results in the function using old data rather than new data.
How can I have the route.params data be updated when used in the useFocusEffect function?

data only fetched after re-render however useEffect code not run due to only run once when screen mounted

In my react-native project, I have one screen needs to query data from backend, then, there are some code using the backend returned data should only be run once when the screen mounted. This is what I did (I am using [react-query][1] for data fetching from backend):
const MyScreen = ()=> {
// fetch data from backend or cache, just think this code gets data from backend if you don't know react-query
const {status, data, error} = useQuery(['get-my-data'], httpClient.fetchData);
// these code only need to run once when screen mounted, that's why I use useEffect hook.
useEffect(() => {
// check data
console.log(`data: ${JSON.stringify(data)}`);
// a function to process data
const processedData = processeData(data);
return () => {
console.log('Screen did unmount');
};
}, []);
return (<View>
{/* I need to show processed data here, but the processedData is scoped in useEffect hook & I need to have the process data function in useEffect since only need it to be run once */}
</View>)
}
My issue:
The code in useEffect only run once when the screen is mounted, however at that point the query above returns data with value undefined until re-render happens, the data is then fetched however code inside useEffect will never run again though the data is fetched when re-rendered . How can I get rid of this issue?
without knowing what exactly is httpClient.fetchData, I can only assume it will return a res.json. you should be able to do something like this.
const {status, data, error} = useQuery(['get-my-data'], httpClient.fetchData).then((res)=>{processData(res)});
With this, data should now become the processedData in your example.

get relevant value from useSelector

I have two hooks:
const dispatch = useDispatch();
const response = useSelector(state => state.responseData);
And submit function:
const submit = () => {
dispatch(connectToServer(`${BASE_URL}user/signIn`, {
email: state.email,
password: state.password
}, 'post'))
if (response.data.token) <--- this data is 1 step late
//do smth
I see relevant data only in JSX elements when they are rendered, but there is no way to make a function based on this data, this data is 1 step late.
There's three issues here:
The connectToServer action is presumably doing some async work, so there's no way a response can have been updated by the time the next line runs
Even if you do await dispatch(connectToServer()), React may not have re-rendered yet
The callback can only reference the variable values that existed in scope at the time the callback was defined, ie, the render before the user clicked the "Submit" button.
You'll need to either:
Move the response handling into the thunk itself
Have the thunk retrieve the updated data from the store and return it / use it somehow
Move the response token handling into a useEffect and wait for the next re-render that has the right data

Setting data returned by useQuery as state

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.

React Native Multiselect

I am new to React Native
I am trying to create a multiselect view where user can select and deselect the items and then the selected items should pass back to the previous container and when user comes back to the next view the selected items should be checked.
I am trying to implement but getting the issue it is not updating data accurately. It shows only 1 selected item when I came back again to the screen.
Can anyone tell me the best way to do that or if there is any tutorial.
Should I do it with Redux or using react native?
Any help would be appreciated!!
Thanks!!
I believe the issue you describe is due to the following:
In componentDidMount you are calling updateItemWithSelected in a loop. This updateItemWithSelected call is both overwriting the checked attributes for all of the arrayHolder values on each call and also not using the updater function version of setState, so the later call of the loop may overwrite the earlier calls since setState is async and batched. If you are not using updateItemWithSelected elsewhere you should simplify componentDidMount to:
componentDidMount() {
const selectedTitles = {};
const { state } = this.props.navigation
const params = state.params || {};
if (params.data.length){
params.data.forEach((element) => {
// create a map of selected titles
selectedTitles[element.title] = true;
})
}
const arrayHolder = this.array.map(item => {
// map over `this.array` and set `checked` if title is in `selectedTitles`
return {...item, checked: !!selectedTitles[item.title]};
});
this.setState({ arrayHolder });
}
and delete updateItemWithSelected.