Component shows previous data when mount for fractions of seconds - reactjs

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);
}

Related

how to execute a component before another one in next.js?

I've been struggling with this problem for a while. I have an Auth component inside which I try to access to local storage to see if there is a token in there and send it to server to validate that token.
if token is valid the user gets logged-in automatically.
./components/Auth.tsx
const Auth: React.FC<Props> = ({ children }) => {
const dispatch = useDispatch(); // I'm using redux-toolkit to mange the app-wide state
useEffect(() => {
if (typeof window !== "undefined") {
const token = localStorage.getItem("token");
const userId = localStorage.getItem("userId");
if (userId) {
axios
.post("/api/get-user-data", { userId, token })
.then((res) => {
dispatch(userActions.login(res.data.user)); // the user gets logged-in
})
.catch((error) => {
localStorage.clear();
console.log(error);
});
}
}
}, [dispatch]);
return <Fragment>{children}</Fragment>;
};
export default Auth;
then I wrap every page components with Auth.tsx in _app.tsx file in order to manage the authentication state globally.
./pages/_app.tsx
<Provider store={store}>
<Auth>
<Component {...pageProps} />
</Auth>
</Provider>
I have a user-profile page in which user can see all his/her information.
in this page first of all I check if the user is authenticated to access this page or not.
if not I redirect him to login page
./pages/user-profile.tsx
useEffect(() => {
if (isAuthenticated) {
// some code
} else {
router.push("/sign-in");
}
}, [isAuthenticated]);
The problem is when the user is in user-profile page and reloads . then the user always gets redirected to login-page even if the user is authenticated.
It's because the code in user-profile useEffect gets executed before the code in Auth component.
(user-profile page is a child to Auth component)
How should i run the code in Auth component before the code in user-profile page ?
I wanna get the user redirected only when he's not authenticated and run all the authentication-related codes before any other code.
Are you sure that the problem is that user-profile's useEffect is executed before Auth's useEffect? I would assume that the outermost useEffect is fired first.
What most probably happens in your case is that the code that you run in the Auth useEffect is asynchronous. You send a request to your API with Axios, then the useEffect method continues to run without waiting for the result. Normally, this is a good situation, but in your profile, you assume that you already have the result of this call.
You would probably have to implement an async function and await the result of both the axios.post method and dispatch method. You would need something like this:
useEffect(() => {
async () => {
if (typeof window !== 'undefined') {
const token = localStorage.getItem("token")
const userId = localStorage.getItem("userId")
if (userId) {
try {
const resp = await axios.post("/api/get-user-data", {userId, token})
await dispatch(userActions.login(res.data.user)) // the user gets logged-in
} catch(error) {
localStorage.clear()
console.log(error)
}
}
}
}()
}, [dispatch])
I think this should work, but it would cause your components to wait for the response before anything is rendered.

react navigate and save history

I am building a React Application with multi-router
Home router call an API in use Effect but when I navigate to another Route and go back to home the request is recall and the component which contain response is reload
is there a way to save history so when I come back the route not calling the API and if it call it, at least not reload the section using response
here my Use-effect
useEffect(() => {
(async () => {
try{
const response = await axios.get("user")
dispatch(setAuth(response.data))
}
catch(e){}
try{
const response = await axios.get("get_all_posts")
setpostsInfo(response.data)
}
catch(e){}
})()
}, []);
Thanks for help
add this isRun
const [ isRun ,setIsRun ] =useState(true)
useEffect( () => {
if(isRun){
(async () => {
try{
const response = await axios.get("user")
dispatch(setAuth(response.data))
}
catch(e){}
try{
const response = await axios.get("get_all_posts")
setpostsInfo(response.data)
setIsRun(false)
}
catch(e){}
})()
}
}, []);
when you change the route you component unmount so its state is lost.
when you go back to the home route the component mount again it's a new instance so you can't hold the information in the component you should hold the information of the number of visiting the page for example or if it's the first time mounting the component in a higher place than the component (the localstorage for example) you can store a key or value to indicate that it's the first time visiting this page and when the compoenent unmount the information stills there. when the component mount again check the existance and validity of the key in the localstorage and you decide whether you send the request or not in the useEffect

React does not wait for async function before mounting components

I am currently using a mock json-server to hold user information in my React app. I am working on storing settings and preferences for users. I have a setting page implemented through a Route component. I am displaying the settings configurations on this page. I am fetching the user settings in App.tsx :
const fetchUser = async (id:number) => {
const res = await fetch(`http://localhost:5001/users/${id}`)
const user = await res.json()
return user
}
const getSettings = async () => {
const user = await fetchUser(0)
setSettings(user.settings);
}
Then I am passing down the state variable for settings through useContext.
const [settings, setSettings] = useContext(userContext);
This works fine when I start on the root page and then go to the settings. However, if the user goes directly to the settings page, the setting state is initially null and I cannot access its values. I tried to fetch the settings again in the setting page component with useEffect but React does not wait for async functions to complete before mounting the components.
const getSettings = async (id:number) => {
const res = await fetch(`http://localhost:5001/users/${id}`)
const user = await res.json()
const settings = user.settings
setSettings(settings);
}
useEffect(() => {
if (!settings) getSettings(0);
}, [])
Is there a way to get around this? I would like to access the settings state throughout the app but the user should not have to start with the root component.
Note: It does work if I check that the value is null before use like this :
settings?.test.difficulty
Please try the below change for useEffect:
useEffect(async () => {
if (!settings) await getSettings(0);
}, [])

How to make setting state with useEffect() to run on page refresh?

My code is not long or complicated at all. It's simple. so please read!
(Im using react + next.js)
In the root file, app.js, I have useEffect to fetch photo data. This data array will be used in a page component so I pass it down from app.js via <Component.../>
function MyApp({ Component, pageProps }) {
const [photoData, setPhotoData] = useState([]);
const [user, setUser] = useState([]);
useEffect(() => {
const getPhotos = async () => {
try {
const photoData = await axios.get(
"https://jsonplaceholder.typicode.com/albums"
);
setPhotoData(photoData.data);
} catch (error) {
console.log(error);
}
};
getPhotos();
}, []);
useEffect(() => {
//code for finding user. no external api used.
setUser(user);
}
}
}, []);
const passedProps = {
...pageProps,
photoData,
user
};
return (
...
<Component {...passedProps} />
)
Then I pass the data (photodata) from a Home component to a (app.js 's) grandchild component, an Photo component
export default function Home({ photoData, user }) {
return(
<Photo photoData={photoData} user={user} />
)
In Photo component, I am receiving photoData and trying to set a state for photoArr with the default state of photoData.
When the entire app is first loaded, the photoData is passed down to the Photo component successfully that it sets the state without any issue.
But the main problem is that when I am in the Photo page (photos are loaded) and refresh the page, then it does not set the state for photoArr with photoData. Even though I can console log photoData received from app.js, it does not set state, photoArr, with the default state, photoData.
export default function Photo({ photoData, user }) {
const [photoArr, setPhotoArr] = useState(photoData);
//I have this as state because I change this array
//later on in this component (doing CRUD on the photo array).
console.log(photoData); // this returns the array received from app.js
console.log(photoArr); // []. returns an empty array
console.log(user); // returns the user object received from app.js.
return (
<div>
{photoArr.length > 0 ?
.... code mapping photoArr
: "Data is empty" //It returns this comment when I refresh the page
}
</div>
)
As you can see above, when I refresh the page, I get "Data is empty" meaning photoArr was not set even with the given default state. If I keep refreshing the page multiple times, it still shows a blank page.
From my research, it's due to setting state being asynchronous? So then how can I fix this problem?
Try this:
(In your Photo page)
const [photoArr, setPhotoArr] = useState(null);
useEffect(() => {
if(photoData.length) setPhotoArr(photoData) // If not empty, set the Arr
},[photoData]} // We listen to photoData's change
On page load, there aren't any data in your photoData, and as it pass down to Photo component, react remembers that state.
But with useEffect listen to photoData's change, we can setPhotoArr once the getPhotos function got the data back.

ReactJS update data in component when user is logged in

I have dashboard which should show data if a user is logged in and other data if no user is logged in. I already managed to figure out if a user is logged in it is not reflected on the page. It only changes after reloading the page.
This is what I have: An Account object with a userstatus component to hold details of the user. The Account object is placed in a context that is wrapped in the App.js. It also has a getSession function which gets the user details from the authentication mechanism. getSession also updates the userstatus according to the result (logged_in or not_logged_in). Second I have a dashboard component which runs the getSession method and puts the result in the console. Everythings fine. But the render function did not get the changed userstatus.
This is my code (Accounts.js):
export const AccountContext = createContext();
export const Account = {
userstatus: {
loggedinStatus: "not_logged_in",
values: {},
touched: {},
errors: {}
},
getSession: async () =>
await new Promise((resolve, reject) => {
...
}
}),
}
This is the Dashboard.js:
const Dashboard = () => {
const [status, setStatus] = useState();
const { getSession, userstatus } = useContext(AccountContext);
getSession()
.then(session => {
console.log('Dashboard Session:', session);
userstatus.loggedinStatus = "logged_in"
setStatus(1)
})
.catch(() => {
console.log('No Session found.');
userstatus.loggedinStatus = "not_logged_in"
setStatus(0);
});
const classes = useStyles();
return (
<div className={classes.root}>
{userstatus.loggedinStatus}
{status}
{userstatus.loggedinStatus === "logged_in" ? 'User logged in': 'not logged in'}
<Grid
container
spacing={4}
...
I already tried with useState and useEffect, both without luck. The userstatus seems to be the most logical, however, it does not update automatically. How can I reflect the current state in the Dashboard (and other components)?
React only re-renders component when any state change occur.
userstatus is simply a variable whose changes does not reflect for react. Either you should use userstatusas your app state or you can pass it in CreateContext and then use reducers for update. Once any of two ways you use, you would see react's render function reflect the changes in userstatus.
For how to use Context API, refer docs

Resources