React await async or rerender? - reactjs

I have a component that renders data received from a server.
I could think of 2 options to use in this case.
trigger fetch function in componentDidMount() and render with initial(empty) data and let redux rerender when the state is set
await in componentDidMount() and wait for servers response, then render with received data
Option1 would load slightly faster with no data then rerender with data (total 2) and Option2 would render only once but component would show slower.
which one is a better approach?

It depends on what design/demand you have.
One normal approach is to add a loading animation (like material-ui loading) for the request. And only render when the response is reached.
async componentDidMount() {
await this.request();
}
async request() {
const req: ReqParamsType = {
...
}
this.setState({loading: true});
await this.props.getReports(req);
this.setState({loading: false});
}
render() {
const { loading } = this.state;
return (
{loading ? <LoadingAnimation /> : <MainContent />}
...

Related

Why does functional component render twice?

I got a component that fetches data from a GraphQL route using Apollo.
When I load the page, it console logs the data twice.
This is the component :
const LAUNCHES_QUERY = gql`
query LaunchesQuery {
launches {
flight_number
mission_name
launch_date_local
launch_success
}
}
`
const Launches = () => {
const { loading, error, data } = useQuery(LAUNCHES_QUERY)
const getLaunches = () => {
if (loading) return <div>Loading..</div>
if (error) console.log(error)
console.log(data)
}
return (
<div>
<h1 className='display-4 my-3'>Launches</h1>
{getLaunches()}
</div>
)
}
What am I doing wrong?
useQuery is an async operation, the first render will be while fetching the data and next render will be when data is fetched. Therefore, it is logging the twice.
One is from start of the useQuery, and another one is from once useQuery is done fetching.
You can console.log loading to see.

Lifecycle - fetch data only after loading complete

I have a component that needs to wait for the user's token to be validated first before fetching data. Right now, my component only renders when authLoading is false but I'm fetching my data in componentDidMount so sometimes the token is not validated yet and I get an unauthorized error. How should I refactor this so everything should wait for authLoading to finish?
componentDidMount() {
// if (!authLoading) { <----- Sometimes the component mounts and the fetchData function doesn't get called
this.fetchData();
// }
}
render() {
const { authLoading } = this.props;
if (!authLoading) {
return (
<Component1 />
);
}
else {
return null
}
}
You want to fetch your data only once, but also only after this.props.authLoading becomes false.
Check out componentDidUpdate(), which will allow you to update any time your props change.
Your implementation might look something like this:
componentDidUpdate(prevProps) {
if (!this.props.authLoading && prevProps.authLoading) { // tokens just loaded!
this.fetchData();
}
}
If you can convert your component to be a functional component then using hooks would me this simple.
const MyComponent = ({ authLoading }) => {
const [authLoaded, setAuthLoaded] = useState();
useEffect(
() => {
fetchData();
// process data
setAuthLoaded(/* some value based on fetched data I assume */);
},
[authLoading], // triggers effect anytime value of `authLoading` changes
);
return authLoaded ? <Component1 /> : null;
};

Component shows previous data when mount for fractions of seconds

I am developing an app named "GitHub Finder".
I am fetching the date in App component using async function and pass these function to User component as props and I call these functions in useEffect.
The problem is here, when I goto user page for second time it shows previous data which I passed in props from App component and then it shows loader and shows new data.
Here is App component code where I am fetching date from APIs and passing to User component through props.
// Get single GitHub user
const getUser = async (username) => {
setLoading(true);
const res = await axios.get(
`https://api.github.com/users/${username}?client_id=${
process.env.REACT_APP_GITHUB_CLIENT_ID}&client_secret=${
process.env.REACT_APP_GITHUB_CLIENT_SECRET}`
);
setUser(res.data);
setLoading(false);
}
// Get user repos
const getUserRepos = async (username) => {
setLoading(true);
const res = await axios.get(
`https://api.github.com/users/${username}/repos?
per_page=5&sort=created:asc&client_id=${
process.env.REACT_APP_GITHUB_CLIENT_ID}&client_secret=${
process.env.REACT_APP_GITHUB_CLIENT_SECRET}`
);
setRepos(res.data);
setLoading(false);
}`
User component code.
useEffect(() => {
getUser(match.params.login);
getUserRepos(match.params.login);
// eslint-disable-next-line
}, []);
I've recorded a video, so you guys can easily understand what I am trying to say.
Video link
Check live app
How can I solve this problem?
Thank in advance!
Here is what happens in the app :
When the App component is rendered the first time, the state is user={} and loading=false
When you click on a user, the User component is rendered with props user={} and loading=false, so no spinner is shown and no data.
After the User component is mounted, the useEffect hooks is triggered, getUser is called and set loading=true (spinner is shown) then we get the user data user=user1 and set loading=false (now the user data is rendered)
When you go back to search page, the app state is still user=user1 and loading=false
Now when you click on another user, the User component is rendered with props user=user1 and loading=false, so no spinner is shown and the data from previous user is rendered.
After the User component is mounted, the useEffect hooks is triggered, getUser is called and set loading=true (spinner is shown) then we get the user data user=user2 and set loading=false (now the new user data is rendered)
One possible way to fix this problem :
instead of using the loading boolean for the User component, inverse it and use loaded
When the User component is unmounted clear the user data and the loaded boolean.
App component:
const [userLoaded, setUserLoaded] = useState(false);
const getUser = async username => {
await setUserLoaded(false);
const res = await axios.get(
`https://api.github.com/users/${username}?client_id=${
process.env.REACT_APP_GITHUB_CLIENT_ID
}&client_secret=${process.env.REACT_APP_GITHUB_CLIENT_SECRET}`
);
await setUser(res.data);
setUserLoaded(true);
};
const clearUser = () => {
setUserLoaded(false);
setUser({});
};
<User
{...props}
getUser={getUser}
getUserRepos={getUserRepos}
repos={repos}
user={user}
loaded={userLoaded}
clearUser={clearUser}
/>
User component:
useEffect(() => {
getUser(match.params.login);
getUserRepos(match.params.login);
// eslint-disable-next-line
return () => clearUser();
}, []);
if (!loaded) return <Spinner />;
You can find the complete code here
Please make your setUser([]) empty at the start of getUser like this:
const getUser = async (username) => {
setLoading(true);
setUser([]);
const res = await axios.get(
`https://api.github.com/users/${username}?client_id=${
process.env.REACT_APP_GITHUB_CLIENT_ID}&client_secret=${
process.env.REACT_APP_GITHUB_CLIENT_SECRET}`
);
setUser(res.data);
setLoading(false);
}

React component is rendering but not updating when state is updating

React component is showing data when state is null but, when its getting data then its not updating the content of the view.
constructor(props){
super(props);
this.state = {
post: null
}
this.getTotalDownloadSize = this.getTotalDownloadSize.bind(this);
}
componentWillMount(){
const {match} = this.props;
const postId = _.get(match, 'params.id');
getDownloadInfo(postId).then((response) => {
this.setState({
post: _.get(response, 'data')
});
}).catch((err) => {
console.log("an error while fetching data", err);
})
}
inside my render i am getting null value for render(){
render(){
const {post} = this.state;
console.log{post};
const files = _.get(post, 'files', []);
)
initially its showing the null value but after it has value but its not updating the content of the view.
can anyone help me with this.
thanks in advance.
componentDidMount is place where you can place request logic.
componentDidMount() {
const {match} = this.props;
const postId = _.get(match, 'params.id');
getDownloadInfo(postId).then((response) => {
this.setState((state) => ({ post: _.get(response, 'data')}));
}).catch((err) => {
console.log("an error while fetching data", err);
})
}
If your data came from an asynchronous request you should use componentDidMount
Invoked once, only on the client (not on the server), immediately
after the initial rendering occurs. At this point in the lifecycle,
you can access any refs to your children (e.g., to access the
underlying DOM representation). The componentDidMount() method of
child components is invoked before that of parent components.
If you want to integrate with other JavaScript frameworks, set timers
using setTimeout or setInterval, or send AJAX requests, perform those
operations in this method.

Ajax request won't display in react render function

I don't know why the result of my axios promise doesn't show up in the render function. I'm using the create-react-app tools by the way.
_getPrice() {
const url = 'https://api.coinbase.com/v2/prices/BTC-USD/spot';
axios.get(url)
.then(function (response) {
//console.log(response.data.data.amount);
let prices = response.data.data.amount;
return prices;
})
}
render() {
return(<div><h3> {this._getPrice()} </h3></div>);
}
React only re-renders components when either the state or props of the component change. If data changes during the render cycle, but doesn't interact with those variables, then the changes will not show up.
You can save the result of your promise to state as follows:
getInitialState() {
return {prices: undefined}
}
componentDidMount() {
const url = 'https://api.coinbase.com/v2/prices/BTC-USD/spot';
axios.get(url)
.then(function (response) {
//console.log(response.data.data.amount);
let prices = response.data.data.amount;
this.setState({prices: prices});
}.bind(this))
}
render() {
return(<div><h3> {this.state.prices} </h3></div>);
}
first you cant call a function in return in render function and if you want update your view you must update state or props...
When requesting data to the server, the request is async, this means it will take time for the server to respond and the browser will continue the execution, than been said, in your current implementation you are returning a promise in your _getPrice function and then when the server responds you are not doing anything with the data.
The second problem is that react will only re-render the component when there are changes on the state or on the props, and in your current implementation you are not changing any of that.
Here's a sample of how you need to do it in order to make it work.
class YourComponent extends Component {
state = {
prices: 0,
};
componentDidMount() {
const url = 'https://api.coinbase.com/v2/prices/BTC-USD/spot';
axios.get(url)
.then((response) => {
let prices = response.data.data.amount;
this.setState({ prices });
});
}
render() {
const { prices } = this.state;
return(
<div>
<h3> {prices} </h3>
</div>
);
}
}
Good luck!

Resources