react-redux problem with onSubmit and async action on API call - reactjs

I am just taking my first steps with react and redux.
I started the project first without redux and now I have decided to implement it with redux.
The login worked before I adapted it to redux.
ThunkMiddleware is applied
Now the problem:
When I click the login button, the logger or DevTools only shows LOGIN_FAILURE. The page reloads and displays the login again.
If I change this
onSubmit={() => props.login(username, password)}
to this
onSubmit={props.login(username, password)}
LOGIN_REQEST actions are spammed and finally (if the password is stored in the browser) LOGIN_SUCCESS. I get the actual content with correct data from the server.
What do I have to change to make the login work normally?
Thanks for your help
LoginComponent:
function Login(props) {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
return (
<div>
<form onSubmit={() => props.login(username, password)}>
<TextField
onChange={e => setUsername(e.target.value)}
/>
<br/>
<TextField
onChange={e => setPassword(e.target.value)}
/>
<br/>
<Button type="submit">
Login
</Button>
</form>
</div>);
}
const mapDispatchToProps = dispatch => {
return {
login: (username, password) => dispatch(login(username, password))
}
};
export default connect(
null,
mapDispatchToProps
)(Login)
LoginAction
import {
LOGIN_FAILURE,
LOGIN_REQUEST,
LOGIN_SUCCESS
} from "./LoginTypes";
export const login = (username = '', password = '') => {
return (dispatch) => {
dispatch(loginRequest());
axios.post(`server`, {
//data
}).then(
(res) => {
dispatch(loginSuccess(res));
},
(err) => {
dispatch(loginFailure(err.message));
}
);
}
};
export const loginRequest = () =>{
return {
type: LOGIN_REQUEST
}
};
export const loginSuccess = tabs =>{
return {
type: LOGIN_SUCCESS,
payload: tabs
}
};
export const loginFailure = error =>{
return {
type: LOGIN_FAILURE,
payload: error
}
};
LoginReducer:
const LoginReducer = (state = initialState, action) => {
switch (action.type){
case LOGIN_REQUEST:
return {
...state,
loading: true
};
case LOGIN_SUCCESS:
let tabBars = populateArray1(action.payload);
let navIcons = populateArray2();
return{
...state,
loading: false,
tabBars: tabBars,
navIcons: navIcons,
isLoggedIn: true
};
case LOGIN_FAILURE:
return{
...state,
loading: false,
error: action.payload
};
default: return state;
}
};
component, which controls login and content:
function Main(props) {
if(props.auth){
return(
<NotLogin />
)
}
else{
return <Login />
}
}

Your login page is getting refresh/redirecting due to which its not handling the api request & its response properly. Please try this by updating your login component.
function Login(props) {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const handleLogin = (event) => {
event.preventDefault()
props.login(username, password);
}
return (
<div>
<form onSubmit={handleLogin}>
<TextField
onChange={e => setUsername(e.target.value)}
/>
<br/>
<TextField
onChange={e => setPassword(e.target.value)}
/>
<br/>
<Button type="submit">
Login
</Button>
</form>
</div>);
}
After updating that, please make sure that you are getting correct value in props.auth through redux in your Main component. The Main component should have redux connection with redux auth state in your code.

Related

Updating useContext with multiple properties

I am rying to update a 'user' from my useContext hook.
The use context checks with an axios call to see if the user is authenticated but would also handle the login and the logout.
here are my contexts and custom hooks I made to access those
authContextProvider.js
const AuthContext = React.createContext();
const LoginContext = React.createContext()
const LogoutContext = React.createContext();
const useAuth = () => {
return useContext(AuthContext);
}
const useLogin = () => {
return useContext(LoginContext)
}
const useLogout = () => {
return useContext(LogoutContext)
}
and this is my provider:
authContextProvider.js
const AuthProvider = ({children}) => {
const [auth, setAuth] = useState(false);
useEffect(() => {
axios.get("api/auth")
.then((result) => {
if(result.data.auth) {
setAuth(result.data.auth)
}
})
.catch(err => {
throw err;
})
}, [])
const login = (email, password) => {
axios.post('api/login', {
email: email,
password: password
})
.then((response) => {
console.log(response.data.auth)
setAuth(response.data.auth)
})
}
const logout = () => {
axios.get('api/logout')
.then(response => {
console.log("User has been logged out.")
setAuth(false)
})
}
return (
<AuthContext.Provider value={auth}>
<LoginContext.Provider value={login}>
<LogoutContext.Provider value={logout}>
{children}
</LogoutContext.Provider>
</LoginContext.Provider>
</AuthContext.Provider>
)
}
Problem:
In my login component, I would use the useLogin hook to trigger my API call to log in with an email and password. But I cannot seem to figure out how to pass those from my login component to my context as props.
Login.js
const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
// this is where my email and password would be send to my context
const handleLogin = useLogin()
return (
<>
Login
<div className={'loginForm'}>
<label htmlFor="email">Email</label>
<input type="email" id={'email'} name={'email'} onChange={(e) => {setEmail(e.target.value)}}/>
<br/>
<label htmlFor="password">Password</label>
<input type="password" id={'password'} name={'password'} onChange={(e) => {setPassword(e.target.value)}}/>
<br/>
<button onClick={() => handleLogin}>Go !</button>
</div>
<br/>
<br/>
< />
);
}
My quesion
How do I pass those props?
Or is this not intended and should this be done differently

How do I pass a Redux param to a component?

I am doing a project with React y React-Redux
I am using an api, create a Search component to bring data from this api but I do not know how to pass the word (from what is searched) of redux to the component.
If I want to look for the word "pasta", I don't know how I should pass it on. I'm learning how to use Redux
----- REDUX ----
const INITIAL_STATE = {
search: '',
};
const reducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case SEARCH: return {
...state,
recipes: action.payload,
};
default:
return {...state}
}
};
export function getByName(query) {
return function (dispatch) {
return axios.get("https://www.themealdb.com/api/json/v1/1/search.php?s="+query).then((response) => {
dispatch({
type: SEARCH,
payload: response.data.meals
})
}).catch((error) => console.log(error));
}
}
---- COMPONENTE SEARCH ---
const [search, setSearch ] = useState('')
const query = useSelector((state) => state.recipeReducer.search);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getByName(query))
}, [dispatch])
const handleFilter = e => {
e.preventDefault();
setSearch(e.target.value)
dispatch(getByName(search))
}
return (
<div>
<form>
<label>
Search:
<input type="text" id="title" placeholder='Search recipe...' value={search} onChange={(e) => handleFilter(e)} />
</label>
</form>
</div>
)
}
One thing I noticed is that the "search" in your initial state is redundant. The results are the thing you care about for this toy problem. You should have:
const INITIAL_STATE = {
recipes: [],
}
Then the issue is the construction of your search component. This is the component which is defining your query, not reading it.. Something like this would be more like what you want:
const SearchComponent = ({}) => {
const [search, setSearch] = useState('')
const recipes = useSelector((state) => state.recipeReducer.recipes);
const dispatch = useDispatch();
const handleFilter = e => {
e.preventDefault();
setSearch(e.target.value)
getByName(search)(dispatch) // getByName returns a function.
// That function takes dispatch as an argument.
}
return (
<div>
<form>
<label>
Search:
<input type="text" id="title" placeholder='Search recipe...' value={search} onChange={(e) => handleFilter(e)} />
</label>
</form>
</div>
);
}

TypeError: _firebase__WEBPACK_IMPORTED_MODULE_3__.default.auth is not a function login from authentication with firebase/react

I am trying to make a simple login/register form authentication with firebase and react, but i keep getting this error when I try to load the page. "TypeError:firebase__WEBPACK_IMPORTED_MODULE_3_.default.auth is not a function"
It's a simple app that contains 3 pages. first should be the login page and then if login details are correct, that should redirect you to the HomePage.
my firebase.js file contains the usual config file. at the end i have export that file like this.
const fireDb = firebase.initializeApp(firebaseConfig);
export default fireDb.database().ref();
I am not quite sure what am I missing here, maybe just a typo? maybe I should have import it some other modules? any help will be much appreciate.
Down below my Login.js code.
Login.js
import React, { useState, useEffect } from "react";
import "./Login.css";
import { useHistory } from "react-router-dom";
import fireDb from "../firebase";
function Login() {
const history = useHistory();
const [user, setUser] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [emailError, setEmailError] = useState("");
const [passwordError, setPasswordError] = useState("");
const clearInputs = () => {
setEmail("");
setPassword("");
};
const clearErrors = () => {
setEmailError("");
setPasswordError("");
};
const handleLogin = () => {
clearErrors();
fireDb
.auth()
.signInWithEmailAndPassword(email, password)
.then((auth) => {
history.push("/HomePage");
})
.catch((err) => {
// eslint-disable-next-line default-case
switch (err.code) {
case "auth/invalid-email":
case "auth/user-disabled":
case "auth/user-not-found":
setEmailError(err.message);
break;
case "auth/wrong-password":
setPasswordError(err.message);
break;
}
});
};
const handleSignup = () => {
clearErrors();
fireDb
.auth()
.createUserWithEmailAndPassword(email, password)
.then((auth) => {
if (auth) {
history.push("/HomePage");
}
})
.catch((err) => {
// eslint-disable-next-line default-case
switch (err.code) {
case "auth/email-already-in-use":
case "auth/invalid-email":
setEmailError(err.message);
break;
case "auth/weak-password":
setPasswordError(err.message);
break;
}
});
};
const authListener = () => {
fireDb.auth().onAuthStateChanged((user) => {
if (user) {
clearInputs();
setUser(user);
} else {
setUser("");
}
});
};
useEffect(() => {
authListener();
}, []);
return (
<div className="login">
<div className="login__container">
<h1>Sign-in</h1>
<form>
<h5>E-mail</h5>
<input
type="text"
autoFocus
required
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<p className="errorMsg">{emailError}</p>
<h5>Password</h5>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<p className="errorMsg">{passwordError}</p>
<button
type="submit"
onClick={handleLogin}
className="login__signInButton"
>
Sign In
</button>
</form>
<button onClick={handleSignup} className="login__registerButton">
Create your Account
</button>
</div>
</div>
);
}
Since you do this:
export default fireDb.database().ref();
You're effectively calling:
fireDb.database().ref().auth()
And if you check the reference documentation, you'll notice that DatabaseReference doesn't have a auth() method.
I recommend exporting the FirebaseApp object instead:
export default fireDb;
And then importing it:
import firebase from "../firebase";
And:
const fireDb = firebase.database().ref();
const fireAuth = firebase.auth();
Alternatively, you can keep your current export and import and get to FirebaseAuth with:
fireDb
.app
.auth()
.signInWithEmailAndPassword(email, password)
...
this worked for me :
import firebase from 'firebase/compat/app'
import "firebase/compat/auth";
...
onSignUp(){
const { email , password , name} = this.state;
console.log(email);
firebase.auth().createUserWithEmailAndPassword(email , password)
.then((result)=>{
console.log(result)
})
.catch((error) => {
console.log(error)
})
}

How i can create private and public routes in NextJs?

I'm studying Nextjs, I come from React and a lot seems to be the same but I was lost with the authentication and private routes.
I looked for a lot of codes on the internet, but either they broke, they didn't make sense to me, or they didn't explain my doubts correctly. My scenario is basically:
I have an application
This application has public and private routes
Users need to login to access private routes
Private routes have the same Navbar
My questions are:
How to create private routes using ReactContext.
How to share the same NavBar between pages (Without having to place the NavBar component on each screen)
How to correctly authenticate a user with my own code using preferably ReactContext
How to reset the routes after authentication (The user is unable to return to the login screen if he clicks the back button on the browser)
How to correctly save the JWT token so that it saves the user's session for longer so that he does not need to log in again
My code is working so far, but I'm sure it is horrible and completely flawed.
I have the following files:
_app.js (Root of the Nextjs project)
index.js (Login page)
privateRoute.js (File that verifies if the user is logged in and allows or not his access)
userContext.js (File that saves user information to be accessed by other components and pages)
NavContext.js (File that checks whether someone is logged in to render the NavBar or not)
_app.js
function MyApp({ Component, pageProps }) {
return (
<UserContextFunc>
<NavContext>
<Component {...pageProps} />
</NavContext>
</UserContextFunc>
);
}
export default MyApp;
index.js
export default function App() {
const [loading, setLoading] = React.useState(false);
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
const { setState } = React.useContext(UserContext);
const router = useRouter();
const login = () => {
setLoading(true);
fetch("/api/auth/login", {
method: "POST",
body: JSON.stringify({
email,
password,
}),
headers: {
"Content-Type": "application/json",
},
})
.then((res) => {
setLoading(false);
if (res.status === 200) {
res.json().then(({ token, roles }) => {
setState({ roles, token });
window.localStorage.setItem(
"rou",
btoa(unescape(encodeURIComponent(JSON.stringify(roles))))
);
window.localStorage.setItem("token", token);
router.replace("/app/adm/home");
});
}
})
.catch((err) => {
});
};
return (
<>
<ReactNotification />
<label htmlFor="exampleInputEmail1" className="form-label">
Email
</label>
<input
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="form-control"
/>
<label htmlFor="exampleInputPassword1" className="form-label">
Senha
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="form-control"
/>
<button
onClick={() => login()}
type="button"
className="btn btn-primary btn-block"
>
{loading ? (
<div
className="spinner-border text-light spinner-border-sm"
role="status"
>
<span className="visually-hidden"></span>
</div>
) : (
"Login"
)}
</button>
</>
);
}
privateRoute.js
const indexPage = "/";
const withAuth = (Component) => {
const Auth = (props) => {
const { setState } = React.useContext(UserContext);
const router = useRouter();
React.useEffect(() => {
const token = window.localStorage.getItem("token");
var roles = window.localStorage.getItem("rou");
if (roles) {
roles = decodeURIComponent(escape(atob(roles)));
}
if (!token || !roles || token == "undefined") {
window.localStorage.removeItem("token");
window.localStorage.removeItem("rou");
return router.replace(indexPage);
} else {
setState({ roles, token });
}
}, []);
return <Component {...props} />;
};
if (Component.getInitialProps) {
Auth.getInitialProps = Component.getInitialProps;
}
return Auth;
};
export default withAuth;
userContext.js
export const UserContext = React.createContext();
export const UserContextFunc = ({ children }) => {
const [state, dispatch] = React.useReducer(
(prevState, action) => {
switch (action.type) {
case 'SET':
return {
...prevState,
...action.newState,
};
}
},
{
roles: []
}
);
const setState = newState => {
dispatch({ type: 'SET', newState });
}
const getState = async () => {
return state
}
return (
<UserContext.Provider value={{ getState, setState, state }}>
{children}
</UserContext.Provider>
);
}
NavContext.js
function NavContext(props) {
const { state } = React.useContext(UserContext);
return (
<>
{state.roles && state.token && <NavBar />}
{props.children}
</>
);
}
export default NavContext;
In private files I export them this way
import withPrivateRoute from "../../../utils/privateRoute";
...
export default withPrivateRoute(Dashboard);
I hope I managed to explain it well, I know it's a lot, but I didn't find any content explaining how to create a private route in Nextjs or how to authenticate correctly without using the authentication templates found in the Next documentation.
This code works, but as I said it seems completely wrong. I accept tips too.

React hooks: Cannot redirect after redux success action

Whenever I try to redirect after an update of props, I get this error:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
Tried this already:
React.useEffect(() => {
if (props.user) {
props.history.push("/admin/me");
}
}, [props.user]);
And this (Directly in the render method):
if (props.user) {
return <Redirect to="/admin/me" />
}
Don't know why redux triggers an update after the component is unmounted. I think that's the problem.
How can I unsubscribe from redux updates before the component is unmounted???
[EDIT]
This is the component in question:
export interface Props extends WithStyles<typeof styles>, RouteComponentProps, React.StatelessComponent, InjectedNotistackProps {
enqueueSnackbar: (a: any, b: any) => any;
login: (u: User) => any;
auth: AuthState;
api: ApiManager;
};
const LoginPage = (props: Props) => {
const { classes } = props;
const api = props.api;
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!email || !password) {
return;
}
const user = new User({ email, password });
props.login(user)
.then(function success(resp: User) {
api.setToken(resp.token);
});
}
React.useEffect(() => {
if (props.auth.user && props.auth.user.token) {
// return <Redirect to="/profile/me" /> This does not work here...
props.history.push("/profile/me");
}
}, [props.auth.user]);
return (
<main className={classes.main}>
<CssBaseline />
<form className={classes.form} onSubmit={onSubmit}>
<Input onChange={e => setEmail(e.target.value)} name="email" />
<Input onChange={e => setPassword(e.target.value)} name="password" />
<Button type="submit">
Sign in
</Button>
</form>
</main>
);
}
const stateToProps = (state: AppState) => ({
auth: state.auth || { token: undefined, user: null }
});
const actionToProps = (dispatch: Dispatch) => ({
login: (user: User): Promise<User> => {
dispatch(createLoginStartAction("start"));
return user.login()
.then(function (loggedInUser: User) {
// This seems to be dispatched after the redirect or something!
dispatch(createLoginSuccessAction(loggedInUser));
return loggedInUser;
});
}
});
export default connect(stateToProps, actionToProps)(
withStyles(styles)(
withSnackbar(LoginPage)
)
);
It turns out that it does redirect, the problem is it goes back to login. Why!?!?
This may be an anti-pattern, but I used to get around this issue like this.
In the constructor of your class, re-assign the setState function like this
const originSetState = this.setState.bind(this);
this.setState = ( ...args ) => !this.isUnmounted&&originSetState(...args);
Then in componentWillUnmount set the boolean to true, preventing setState from attempting to update the state:
componentWillUnmount() {
this.isUnmounted = true;
}
This feels dirty to me, so I've not used it in a while, but it works well for a quick fix.
The simplest fix (while also following the conventional React state pattern) is to have the Redirect conditionally render using the component's state. Add a constructor with the state this.state = {isUser: false}, and add {this.state.isUser && <Redirect to="/admin/me" />} to the bottom of your render method right before </main>. Finally change
if (props.user) {
return <Redirect to="/admin/me" />
}
to
if (props.user) {
this.setState({isUser: true);
}
The result of this follows the conventional React pattern, and when the state changes to true, it'll automatically redirect without performing a no-op!

Resources