Fetch data after login - reactjs

I have a login component:
const login = async (e) => {
e.preventDefault();
const {data} = await loginUserRes({variables: {
...formData,
}});
if (data) {
currentUserVar({
...data.loginUser,
isLoggedIn: true,
});
}
};
Which allows user to send some data to the server, if the credentials are correct a JWT is stored in a cookie for authentication.
I'm wondering what he best approach is to fetching data after a log in, on the dashboard I have a list of movies that the user has added. Should I use a react effect to react to the currentUserVar changing and then do request for the movies? Or perhaps I could add all the dashboard data to the return object of the apollo server, that way the data is present on login.

Yes you should react to your authorization data, But this info always considered as global state that you will need it in the whole app. so It is recommended to save your currentUserVar in a react context or whatever you are using to handle global state, Even if the app was small and you don't have global state then you can left it to your root component so you can reach this state in all your components.
Here how I handle auth in reactjs, I don't know if it is the better approach but I'm sure it is a very good solution:
Create a wrapper component named AuthRoute that checks if the user is logedin or
not.
Wrap all components that require user auth with AuthRoute
If we have user data then render wrapped component.
If there is no user data then redirect to Login component
const AuthRoute = ({component, children, ...rest}) => {
const Component = component;
const {globalState} = useContext(GlobalContext);
// Return Component if user is authed, else redirect it to login page
return (
<Route
{...rest}
render={() =>
!!globalState.currentUser ? ( // if current user is exsits then its login user
children
) : (
<Redirect
to={ROUTES_PATH.SIGN_IN.index}
/>
)
}
/>
);
};
Then any component can fetch the needed data in it's mount effect useEffect(() => { fetchData() }, []). and this is an indirect react to userData.
Note: if you are not using routing in your app, you still can apply this solution by converting AuathRoute component to HOC.

Related

How to add/append routes programatically in React18

I'm a quite new to React, but I'd like to achieve the following:
FrontendApp should:
check if user is authenticated -> draw MainLayout and build menu dynamically, based on JSON received from server api. For the menu to work, I accordingly need to add data to Routes
if user is not authenticated -> draw Login
What I did:
component, which is checking isAuthenticated and loading json from data from server:
const Preloader = () =>
{
const [menu, setMenu] = useState([]);
const {isAuthenticated} = useAuth()
async function fetchMenu()
{
const loadedMenu = await MenuService.getMenu();
setMenu(loadedMenu)
}
useEffect(() =>
{
fetchMenu()
}, [])
if(isAuthenticated)
return <Login/>;
return <MainLayout/>
}
And I'm using this component Preloading in my App
const App = () => {
return (
<BrowserRouter>
<Preloader />
</BrowserRouter>
);
};
auth check works fine, I receive MainLayout or Login depending on isAuthenticated status.
But, how to add all received from server routes to BrowserRouter?
UPD 1:
I'm receiving routes from server to determine, what routes and components are available to current authenticated user. And draw MainMenu correspondingly.

Do I need to store isUserSignIn for componet rendering in Local Storage(React,Next,Firebase Auth)

(Firebase Google Signin + React(Next) )
If user sign in , setState to true.
And render the home component and welcome(login) component in index.js by state value.I made an application by storing that state in local storage and I think it is not a good practice. So if I don't use local storage there is a issue when user signed in and it should reach the home component but , user can also see welcome component before home component.
Logic index.js -> if userSignin (Home) : (Login)
Conditional Rendering
{isSignin ? (<>Home Component</>) : (<Welcome Component/>)}
state
const [isSignin, setisSignin] = useState(false);
changing state if user signin
useEffect(() => {
onAuthStateChanged(auth, (user) => {
if (user) {
setisSignin(true);
console.log(user);
}else {
setisSignin(false);
}
});
}, []);
firebase auto use localstorage when u signin an account. please check it in your browser localstorage. You don't need to store it by yourself. Then, when user revisit that website from the same web browser, firebase auto login (unless he did not logout). However, that auto login needs some amount of time. you need to do something during that amount of time. after that time, isSignIn will be true. I suggest something like this.
const [isSignIn, setSignIn] = useState(null);
useEffect(()=> listener_to_authstate_change() ,[])
if (isSignIn === null) return null; // something you like to show example - <Loading />
if (isSignIn === false) return <SignIn Component />
if (isSignIn === true) return <Home Component />

How do I avoid duplicate action dispatches caused by to a delay in redux store update/propagation of redux state to connected components?

I'm working on a React app that uses React-Router and Redux.
This app has certain routes that require authentication, in this case I'm redirecting the user to a login page as follows.
return <Redirect
to={{
pathname: '/login',
search: `?ret=${encodeURIComponent(location.pathname)}`
}}
The login page is rendered using a Login component, which is connected to the redux store and checks a boolean isSignedIn. When isSignedIn === true, I redirect the user to the path specified in the ret query param. The relevant code is below.
const Login = (props) => {
if (!props.isSignedIn) {
// show a message and CTA asking the user to log in
}
const queryParams = qs.parse(props.location.search, { ignoreQueryPrefix: true });
const ret = queryParams.ret || '/';
return <Redirect to={ret} />
}
I'm using google oAuth2 in the app, and after the user signs in via Google, I dispatch a SIGN_IN action which updates isSignedIn to true. The oAuth flow is handled by a GoogleAuth component, which checks whether a user is signed in each time a page is rendered, and also handles the sign in/sign out click events. This component renders on every page of the app.
class GoogleAuth extends React.Component {
componentDidMount() {
window.gapi.load('client:auth2', () => {
window.gapi.client.init({
clientId: 'someclientid',
scope: 'email'
}).then(() => {
this.auth = window.gapi.auth2.getAuthInstance();
// initial check to see if user is signed in
this.onAuthChange(this.auth.isSignedIn.get());
// listens for change in google oAuth status
this.auth.isSignedIn.listen(this.onAuthChange);
})
});
};
onAuthChange = async (isGoogleSignedIn) => {
if (!this.props.isSignedIn && isGoogleSignedIn){
await this.props.signIn(this.auth.currentUser.get().getId());
} else if (this.props.isSignedIn && !isGoogleSignedIn) {
await this.props.signOut();
}
}
handleSignIn = () => {
this.auth.signIn();
}
handleSignOut = () => {
this.auth.signOut();
}
The issue I'm facing is that the SIGN_IN action (dispatched by calling this.props.signIn()) is getting called multiple times when I log in from the '/login' page, and get redirected.
It appears that the redirect occurs before the redux store has properly updated the value of isSignedIn and this results in a duplicate SIGN_IN action dispatch.
How can I prevent this? I considered adding a short delay before the redirect, but I'm not sure that's the right way.
EDIT:
I found out that this is happening because I'm rendering the GoogleAuth component twice on the login page (once in the header, and once on the page itself). This resulted in the action getting dispatched twice. Both GoogleAuth components detected the change in authentication status, and as a result both dispatched a SIGN_IN action. There was no delay in propagation of redux store data to the connected component, at least in this scenario.
Ok, figured this out.
I'm rendering the GoogleAuth component twice on the login page - once in the header, and once within the main content. That's why the action was getting dispatched twice.
Both GoogleAuth components detected the change in authentication status, and as a result both dispatched a SIGN_IN action.

Is it good practice to fetch user data and save it to Context on every call in Next.js

I decided to use React's ContextAPI to store authenticated user data.
I've implemented a custom App component and wrote a getInitialProps function to retrieve user's data from the backend.
class MyApp extends App {
render() {
const { Component, pageProps, authenticated, user } = this.props;
return (
<AuthProvider authenticated={authenticated}>
<UserProvider userData={user}>
<Component {...pageProps} />
</UserProvider>
</AuthProvider>
);
}
}
MyApp.getInitialProps = async (appContext) => {
let authenticated = false;
let user = null
const request = appContext.ctx.req;
if (request) {
request.cookies = cookie.parse(request.headers.cookie || '');
authenticated = !!request.cookies.sessionid;
if (authenticated){
user = await getCurrentUserData(request.headers.cookie)
}
}
// Call the page's `getInitialProps` and fill `appProps.pageProps`
const appProps = await App.getInitialProps(appContext);
return { ...appProps, authenticated, user };
};
export default MyApp;
What's concerns me the most is that getCurrentUserData is called every time a page is being rendered, so there is always one extra API call.
Is it good practice to do so?
Should I store user's data in a cookie, and update context from there?
How can I optimize this solution?
Thanks
If you don't want it to be called for every page. You can create HoC for the Auth.. and use this HoC only for the page that use the user data.
usually the page that don't need user data is static page which is quite situational most of the time.

react-redux-firebase login promise resolving before auth state is updated, causing render troubles

I am using redux-form, react-redux-firebase, along with react-router-redux and react-router.
When logging in a user with a redux-form connected form, in the onSubmit callback function, I am using the react-redux-firebase function from the withFirebase HOC called props.firebase.login(). Inside the return of this promise, I am attempting to use react-router to navigate to a "protected" view of mine.
This all works well and looks like this:
export default withFirebase(
connect(mapStateToProps, mapDispatchToProps)(
reduxForm({
form: SINGLE_PAGE_FORMS.login,
onSubmit: (values, dispatch, props) => {
const { firebase } = props;
const { username: email, password } = values;
firebase.login({
email,
password,
}).then(response => {
if(!!response) {
dispatch(
openNotificationCenterAC({ message: 'Successfully logged in!', messageType: MESSAGE_TYPE_SUCCESS })
);
// The problem here with login is that the Private Route has not yet seen the Uid update and therefore
// won't load a private route. I need to know the UID is in state before navigating to dashboard.
dispatch( push( ROUTE_OBJS.SECURED[ROUTES.DASHBOARD].path ) );
dispatch( reset(SINGLE_PAGE_FORMS.login) );
}
}).catch(error => {
dispatch(
openNotificationCenterAC({ message: error.message, messageType: MESSAGE_TYPE_ERROR })
);
});
},
})(LoginContainerView)
)
);
The trouble (as seen in comments) is that I have switched over (today) to using a really cool new way of making some of my routes Private. I used the examples found in this link here to create this concept.
My Private component looks like this:
export default class PrivateRouteView extends Component {
render() {
const {
key,
auth,
path,
renderRoute,
showErrorMessage,
} = this.props;
const { uid } = auth;
return (
<Route
exact
key={key}
path={path}
render={
(!!uid && isLoaded(auth) && !isEmpty(auth)) ? renderRoute :
props => {
return (
<Redirect
to={{
pathname: '/login',
state: { from: props.location }
}} />
);
}
} />
);
}
}
The auth prop here is from the withFirebase HOC injection. It's attached to this.props.firebase.auth.
The Problem
Whenever I successfully log in, get the user data returned to me in the .then function of the firebase.login() promise... by the time I get to the dispatch of .push navigation, the PrivateRoute has re-rendered without the new auth object and never re-renders.
I can step through and watch it. It goes:
Login success
Enter into .then function
Notify user of login success
Change navigation to dashboard view
PrivateRoute component re-renders and see's no UID or auth
firebase.login promise entirely resolved
Application state update completes and firebase.auth now has Uid and all other profile/auth objects in state
Container to react-router BrowserRouter, Switch and Route's runs normally
View attached to this container re-renders fine, down to the function where I return the PrivateRoute components
PrivateRoute render method is never reached on the second render, the one where the firebase.login user data finally makes it into firebase.auth object state. So now I am authorized, however it never see's the new auth object because of no rendering...

Resources