React Native callback after SocketIO response - javascript

I have a React Native App which on componentWillMount() calls a function to save everything in that list.
var list = []
const getMatchList = (logKey) => {
global.socket.emit("getMatches", logKey, (data) => {
//adding to list logic
})
console.log("Matches Loaded");
}
class MatchesScreen extends React.Component {
async componentWillMount() {
logKey = await getPreferences("logKey");
await getMatchList(logKey)
}
componentDidMount() {
console.log(list);
}
}
When I access that screen, it shows the empty list first, then I get the message "Matches Loaded". How can I first load matches?
Thank you.

whats wrong
I looks like you expect that await will turn your async function to a sync one, but this is not the case. Your componentWillMount will be called before rendering, but since it loads data asynchronously and since react will not wait for the promise to resolve, the page will be rendered with no data and will be rerendered later whenever data is available.
how to solve
this need more context to have a good answer, but you have at least following options:
Use the same logic, but add a loading spinner/progress bar for that view.
Switch to the view when data is loaded - but it will be perceived sluggish by users.
If possible preload and cache the data while in previous view.

Related

React Redux - quick flash of previous state before dispatch issue

I'm building a React + Redux app with a Node.js backend, and one of the features is that a user can view profiles of other users. To do this, I have a section in the Redux state called users that looks like:
{
...,
users: {
user: {},
isLoading: true
}
}
Every time the /users/:id route is rendered, a getUser(id) action is dispatched and fills the state with the received information.
The main issue is when a user views user1's profile page (therefore redux state is now filled with user1's profile) and then views user2's profile page, the dispatch getUser(2) action is called right after the page is rendered. Since user1's info is still in the state, it will flash their info for a very short time, and then show the loading spinner until the new user is loaded.
I read about dispatching a resetUser(id) action on every unmount of the page, but I'm not sure if this is the right way to go. Also, one of the features is if a user is viewing their own page, they have an edit button which redirects them to /edit-profile. If I reset the state on every unmount, I'll have to fetch their profile again (in the edit page), even though I just did that when they viewed their page.. And that doesn't seem like it makes sense.
Any ideas how to solve this? Thanks!
The render phase runs after mounting. And you stated that previous data is being shown before new data. It seems that you have asynchronous mounting:
async componentDidMount() {}
It will continue rendering even before mounting phase is completed. To avoid issue, you may use synchronous nature of mounting:
componentDidMount(){}
Where you'll call api data.
Now, when you reach to rendering phase it will have new data available before rendering and won't flash you old data.
You now may be wondering how to call api asynchronously. You can create a asynchronous function and call that function inside the synchronous componentDidMount.
componentDidMount() {
yourAsyncFunc()
}
async yourAsyncFunc() {} // api call
How can I do this with React hooks?
While using useEffect, don't implement async:
useEffect(async () =>
Implement it simply:
useEffect(() => {
// you can still use asynchronous function here
async function callApi() {}
callApi()
}, []) // [] to run in similar to componentDidMount
If you miss second parameter to useEffect then you are not ensuring it to run on mounting phase. It will run before, after, and in the rendering phase depending on case.
Implementing something like resetUser(id) seems to be the right way here. I use this approach in my current project and this does solve the problem. The other approach of removing async keyword from useEffect callback as mentioned in another answer didn't work for me (I use hooks, redux-toolkit, Typescript).
After dispatching this action, your state should look something like
{
...,
users: {
user: null,
isLoading: false,
}
}
If you are using hooks, you can dispatch the action this way:
useEffect(() => {
const ac = new AbortController();
...
return () => {
dispatch(resetUser(null));
ac.abort();
};
}, []);
Action could look something like this:
resetListingDetails(state, action) {
// Immer-like mutable update
state.users = {
...state.users,
user: null,
};
}

Call api before first render in functional component in React.js

If I want to call API after the first rendering of component, I know we have useEffect hook to call the API method. (I am talking about functional components only. No class component).
Is there any way, I can call the API before my component renders the first time.
The reason for this question is, If some UI part is dependent on API, I do not want to show any incomplete information to the user on the first render also, which will be changed once I get the data from API.
This seems to be a bad experience with UI.
Edit: I got a couple of advice to use useLayoutEffect or any consumable flag to check if it is rendered or not. I have checked useLayoutEffect does not work, and by using the consumable flag, we are increasing the complexity only.
Do we have any better way for this?
I think useLayoutEffect can be used for something like this, by passing in an empty array as second argument. useLayoutEffect(() => {...}, []);
Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.
Although you can always fetch the data in the parent component and pass it as props. Or - if you don't mind it being an experimental feature for now - React Suspense is trying to solve this exact problem.
There are no correct ways to make API call before component rendered from the same component.
You may preferred make API call in parent component and render presentation component when and only when have consumable data.
Another workaround for such case is keep consumable flag inside component, make request inside useEffect, render nothing or some kind loader and render something only when request completed with success.
on calling api it is not responding exact on its first render but giving exact response when it's being hit second time
You can have a spinner or loading component be rendered first conditionally (isLoading for example):
if(isLoading) return <Spinner />
and have the api call set (isLoading) to false on status 200 for example.
Just came across something, which may help someone in future. So we can use some library but the specific one I would mention here is React Query
React query does exactly what we are trying to achieve, the hooks like useQuery fetch data as soon as rendering starts so you don’t have to wait until react loads the entire component as follows
// with react query
const { status, data, error, isFetching } = useQuery(
['data'],
async () => {
const data = await (
await fetch(`${API_BASE_URL}/data`)
).json()
return data
}
)
// without react query
useEffect(() => {
try {
setLoading(true)(async () => {
const data = await (await fetch(`${API_BASE_URL}/data`)).json();
setData(data);
})();
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
}, []);
Here is the article link if you want to read

Issues with asynchronous nature of redux in React?

I'm pulling data into one of my parent components and then using various filter statements which are based on user choices from select boxes. I'm then calling an action which simply stores that filtered data based on the users search into global state so that my child components can access them.
One of my child components is supposed to render the results but what is happening is the results being rendered are lagging one action behind. I've encountered similar issues when using set state and my solution then was to use a callback but I'm not exactly sure how to go about dealing with this issue in this situation with redux.
The wordpress.get is just named import of axios config.
componentDidMount = async () => {
const response = await wordpress.get(
"*********************/api/wp/v2/variants?per_page=100"
);
this.props.fetchData(response);
const data = []
response.data.forEach(ele => {
data.push(ele)
})
this.props.sendFilteredView(data);
};
handleChange = () => {
this.preBuiltFiltering();
};
I've left out pre-built filtering because its long and excessive, all it does is run the filter based on the users choices and then dispatches the this.props.sendFilteredView action with the filtered data set as the argument. The action just returns the payload.
I then am rendering the results of the filter in a child component by accessing the global state (I also tried just passing it directly through props, same issue).
It’s an async function, you’re using a callback after the forEach with data.
So you need to wait forEach been completed.
Try to use await before forEach.
componentDidMount = async () => {
const response = await wordpress.get(
"*********************/api/wp/v2/variants?per_page=100"
);
this.props.fetchData(response);
const data = []
await response.data.forEach(ele => {
data.push(ele)
})
this.props.sendFilteredView(data);
};
handleChange = () => {
this.preBuiltFiltering();
};

Abort previous running async componentDidMount

We have a component, connected to the redux store, that has this componentDidMount definition:
async componentDidMount() {
const page = await fetchPage(this.props.page);
const section = await fetchSection(this.props.section);
this.props.saveDataToStore({ page, section });
}
So, every time the component is mounted, it'll run this code.
Now let's imagine that we go to another section of our app, so the component gets unmounted, and then we come back before the previous componentDidMount has finished its execution, mounting the component again and starting fetching new data. Every time the data is fetched, we save it to the redux store.
I think that there is no way to abort previous componentDidMount execution to prevent setting old data from previous calls. So, my question is, what's the best approach to follow to prevent old data being set?
I've been able to solve it having a function that does all of these calls (like fetchData) that handles an AbortController object, but I found it a bit verbose, so do you know what's the recommended approach to follow in this situation?
Using the AbortController or a library like axios that supports cancellable requests is a good solution, but you could also keep an instance variable that you change to false when the component unmounts and check against his before you use setState after the requests have finished.
Example
class App extends React.Component {
_isMounted = true;
async componentDidMount() {
const page = await fetchPage(this.props.page);
const section = await fetchSection(this.props.section);
if (this._isMounted) {
this.setState({ page, section });
}
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
// ...
}
}

React/Flux - Best way to monitor api and update store on new data?

I'm trying to figure out how to update store when api returns changed data.
One of my Components is supposed to render "live" data when other users write data to api. What's the best approach? Poll async data on interval?
I'm using ReactJS/AltJS and right now i'm using jQuery for making async api calls in my Actions.
Thanks!
BookActions.js:
getBook(bookId) {
$.ajax({ url: '/api/books/' + bookId })
.done((data) => {
this.actions.getBookSuccess(data);
})
.fail((jqXhr) => {
this.actions.getBookFail(jqXhr);
});
}
BookStore.js
import alt from '../alt';
import BookActions from '../actions/BookActions';
class BookStore {
constructor() {
this.bindActions(BookActions);
this.books = [];
}
onGetBookSuccess(data) {
this.books = data;
}
onGetBookFail(errorMessage) {
toastr.error(errorMessage);
}
}
export default alt.createStore(BookStore);
First of all, you have to define what's 'live' data. After a user write some data to the server, how long can you wait until you know there's new data? If you want to be notified within 1 second, you also need to design your backend system carefully.
In your question, I assume that several seconds delay are tolerable. One of the simple solutions is polling. According to the react doc, you can create a timer in componentDidMount to invoke the API periodically, and clean up everything in componentWillUnmount.
Don't put the timer logic into the actions which may be shared by man components.

Categories

Resources