In my header component, I have conditional rendering set to display different links depending on if the user is logged in or not. The conditional rendering works fine EXCEPT the fact that it blinks one set of links before displaying the proper set of links.
It dawned on me this is because of the axios.get call I have that retrieves the user's status. The state variable is initially set to false. If the user is logged in, the get call will indicate that and the state variable will get set to true. The short delay makes it so the "not logged in" links are shown momentarily before the "is logged in" links get displayed.
I've tried quite a few recommended approaches in both componentDidMount and componentWillMount - nothing seems to be working:
componentDidMount() {
axios.get('http://localhost:8000/api/login', {
withCredentials: true
}).then(res => {
if (res.data.loggedIn) {
this.setState({isLogged: true});
} else {
this.setState({isLogged: false});
}
}).catch(error => {
this.setState({isLogged: false});
});
}
Next approach:
async componentDidMount() {
const res = await axios.get('http://localhost:8000/api/login', {
withCredentials: true
});
const logged = await res.data.loggedIn;
if (logged) {
this.setState({isLogged: true});
} else {
this.setState({isLogged: false});
}
}
I've tried call back functions with the same results.
The conditional rendering is the typical:
{this.state.isLogged && <LoggedIn />}
I'm clearly failing to see something basic in how the async functions work - I'm just not sure what that is!
Thanks in advance!!
The best that you can do in this case is to show nothing or a spinner until the request has finished. It doesn't matter what you do (async/await, promise, callback) that request is going to take some time.
Here’s a method I’ve found useful. Add a loading variable to state:
async componentDidMount() {
this.setState({ loading: true });
const res = await axios.get('http://localhost:8000/api/login', {
withCredentials: true
});
const logged = await res.data.loggedIn;
if (logged) {
this.setState({isLogged: true, loading: false });
} else {
this.setState({isLogged: false, loading: false });
}
}
Then, you can add:
{!this.state.loading && this.state.isLogged && ...}
You can always set loading to true in your component’s constructor, if you still see a flash, but I would try to keep loading in the same method as the process that’s actually loading.
Related
I am trying to understand some socket and props behavior I'm seeing in my react-native app. I am using a redux store in this app. When the user manually signs out, I call the following:
if (this.props.authenticated) {
this.props.deauthenticateUser().then((res) => {
console.log('res: ', res); // {"payload": {"authenticated": false, "error": ""}, "type": "DEAUTHENTICATE_USER"}
console.log('this.props.authenticated 26: ', this.props.authenticated); // is true here, should be false
if (!this.props.authenticated) this.props.navigation.navigate('Login'); // Never happens because authenticated is still true
});
}
}
By the way, the redux section of that page/screen looks like this:
// Redux Mapping
const mapStateToProps = (state) => {
return { ...state.User };
};
const mapDispatchToProps = (dispatch) => ({
deauthenticateUser: async (user) => await dispatch(deauthenticateUser(user)),
});
export default connect(mapStateToProps, mapDispatchToProps)(LogoutScreen);
This is what the deauthenticateUser function looks like:
export const deauthenticateUser = () => {
console.log('deauthenticateUser() firing...'); // I see this
return async (dispatch) => {
await SocketDriver.Deauthenticate(); // This is successful
await LocalDataStore.Invalidate(); // This is successful
// Clear unauthorized application state
dispatch(clearOverview());
dispatch(clearSignature());
dispatch(clearUser());
console.log('finished clearing data...'); // I see this
return {
type: DEAUTHENTICATE_USER,
payload: { authenticated: false, error: '' },
};
};
};
This calls my User reducer, which looks like this:
case 'DEAUTHENTICATE_USER':
return { ...state, ...payload };
Even when I try a setTimeout to give plenty of time for deauthenticateUser() to complete, I find that authenticated never gets to a false state.
What am I potentially missing here? The logic seems to look solid to my eyes. Nothing bombs out, and when I console.log the data out at various points, it all looks as it should, EXCEPT for the props.authenticated property, which remains set to true.
Is it possible that my Logout screen doesn't get the final props update? And, if so, why would that be? Even when I try to call the deauthenticateUser() function multiple times -- just to test it -- I never see this.props.authenticated ever console.log as false.
I have a task to call an API, save the API data into local storage, and retrieve that same data when a page is reloaded.
However, the API gets called on every F5 and the data it contains gets randomized, (because API returns a bunch of random facts). So i have the Mount() and Update(), the data is in local storage, but i can't get it to stay and retrieve from there.
I've tried some if statements inside Mount(), for example to check length, and the like, but none worked. Help, I don't know what I'm doing wrong. This is the code:
import React from "react"
class LogicContainer extends React.Component {
constructor() {
super()
this.state = {
isLoading: true,
allFacts: []
}
)
}
//Call to API, get facts, put them into local storage.
componentDidMount() {
fetch("https://catfact.ninja/facts?limit=1000")
.then(response => response.json())
.then(response => {
const allFactsApi = response.data
this.setState({
isLoading: false,
allFacts: allFactsApi
})
localStorage.setItem("allFacts", JSON.stringify(allFactsApi))
})
console.log(this.state.allFacts.length)
}
/*check if facts have changed on reload and save them in tempFacts*/
componentDidUpdate(prevProps, prevState) {
if(prevState.allFacts !== this.state.allFacts) {
const tempFacts = JSON.stringify(this.state.allFacts)
localStorage.setItem("allFacts", tempFacts)
}
}
render() {
return(
<div></div>
)
}
}
export default LogicContainer;
You are setting the data into the localStorage properly. But the problem is you are never actually retrieving it. You are just placing it in the state but the state will reset when the lifecycle of your component comes to an end (when F5 comes along for example). This is the code for retrieving data in localStorage:
localStorage.getItem("allFacts")
Bare in mind: When a user refreshes the website, the "componentDidUpdate" is not going to run, given that the page is actually reloading from scratch. What you should actually be doing is adding the snippet of code mentioned above on your "componentDidMount()" to check wether previous data exists in localStorage or if it should be retrieved from the API. If the data exists, then set the state to that. Otherwise, fetch the data from the API as you are doing now:
componentDidMount() {
// Check if localStorage has an assigned value for item "allFacts"
if(localStorage.getItem("allFacts")!== null){
// Set the state to that
this.setState({
isLoading: false,
allFacts: localStorage.getItem("allFacts")
})
// Otherwise, retrieve it from the API and set the state as well as the localStorage
}else{
fetch("https://catfact.ninja/facts?limit=1000")
.then(response => response.json())
.then(response => {
const allFactsApi = response.data
this.setState({
isLoading: false,
allFacts: allFactsApi
})
localStorage.setItem("allFacts", JSON.stringify(allFactsApi))
})
}
console.log(this.state.allFacts.length)
}
I'm new to React and got some questions now.
I use componentDidUpdate to fetch data when the button is clicked. I manage the results data with redux. It's seem like componentDidUpdate is async function, because "console.log" always execute first before the fetch function is done. So I always got [] in the console the first time I clicked the button.
Suppose I have 2 actions I want to do when I clicked the button. First fetching the data, after that use that data and pass to another actions. How can I wait until the fetch data done after that the console.log is execute? Please help me.
state = {
searchClick: false,
keyword: "pizza",
};
componentDidUpdate(prevProps, prevState) {
if (
this.state.searchClick &&
prevState.searchClick !== this.state.searchClick
) {
// send get request to fetch data
this.props.searchResults(this.state.keyword);
//Update searchClick state
this.setState({ ...this.state, searchClick: false });
// console log the result data
console.log(this.props.results);
}
}
The console log always executes first because searchResults is async. What you need to do is returning a promise inside searchResults:
const searchResults = (keywords) => {
// fetch with the keywords and return the promise
// or any promise that resolves after your search is completed
return fetch(...);
};
componentDidUpdate(prevProps, prevState) {
if (
this.state.searchClick &&
prevState.searchClick !== this.state.searchClick
) {
// send get request to fetch data
this.props.searchResults(this.state.keyword).then(() => {
//Update searchClick state
this.setState({ ...this.state, searchClick: false });
// console log the result data
console.log(this.props.results);
});
}
}
I have a button that changes the state of sort to either true or false. This is being passed to the App component from a child using callbacks. The first console log in the refinedApiRequest gives the value of true and then false and then true and so on as the button is click. However, the if statement keeps resulting in the else result regardless of the state of sort (true or false). What is causing this?
I want to assign a variable a value depending on the state of sort so that I can feed this value into the params of a async api get request. Thanks in advance.
class App extends React.Component {
state = { pictures: '', sort: '' };
onSortSelect = sort => {
this.setState({ sort: sort }, () => this.refinedApiRequest(sort));
}
async refinedApiRequest(sort) {
console.log(sort);
if(sort == 'true') {
console.log('its true');
} else {
console.log('its false');
}
const res = await axios.get('url', {
params: {
order: {a variable}
}
});
this.setState({ pictures: res.data });
console.log(this.state.pictures);
}
While the optional callback parameter to this.setState is guaranteed to only be executed when the component is re-rendered, in your case it still closes over the sort value of the previous render (ref: How do JavaScript closures work?).
I'd suggest following reacts advice:
The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead.
from: https://reactjs.org/docs/react-component.html#setstate
(emphasis by me)
I have a React App and I want to show a spinner when processing will take some time to complete, for example an API request, or even lengthy setting of state. I have tried a callback such as:
this.setState({
showSpinner: true,
}, () =>
this.APIgiveMeTheWorld().then(
this.setState({
showSpinner: false,
})
));
and async function with:
await this.setState({
showSpinner: true,
});
this.APIgiveMeTheWorld().then(
await this.setState({
showSpinner: false,
})
);
But at no discernible point does state show showSpinner as being true (therefore my spinner never appears). What is the best way to implement this kind of functionality.
What you can do is to show the Spinner as long the showSpinner state is false and hide it when It became false
{ !showSpinner? <SpinnerComponent /> : <ResultShower /> }
And in your component you can load your data from an API in the componentWillMount lifeCycle and when the loading completed and you have data back from the API's Call you can update the showSpinner state to false, which will automatically hide the SpinnerComponent and show whatever else you want to show.
componentWillMount() {
loadData().then((data) => {
// Here I consider get data from from you API request
this.setState({
data,
showSpinner: false
});
})
}
I see you have passed result of setState in call back then. Did you try:
this.setState({
showSpinner: true,
}, () =>
//should pass a call back function to call right after api return.
this.APIgiveMeTheWorld().then(r => {
this.setState({
showSpinner: false,
})
}));
The problem is with my API function resolving instantaneously.