Next auth credentials - next.js

I'm trying to do a credentials auth with next-auth. I have to use a custom sign-in page and I absolutely can't make it work for approximately one entire week.
I have :
// [...nextauth.js]
import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
import axios from '#api/axios';
const options = {
providers: [
Providers.Credentials({
async authorize(credentials) {
const { data: user, status } = await axios.post('/users/authentication', credentials);
if (user && status === 200) {
return user;
} else {
throw new Error('error message');
}
}
})
],
pages: {
signIn: '/profil/authentication/login',
error: '/profil/authentication/login'
},
session: {
jwt: true,
maxAge: 30 * 24 * 60 * 60 // 30 days
},
debug: true
};
export default (req, res) => NextAuth(req, res, options);
and :
// profil/authentication/login
import { signOut, useSession } from 'next-auth/client';
import AuthenticationForm from '#components/auth/authenticationForm';
import Layout from '#components/layout';
const Login = () => {
const [session] = useSession();
const intl = useIntl();
return (
<Layout>
{!session && <AuthenticationForm />}
{session && (
<>
Signed in as {session.user.email}
<br />
<button onClick={signOut}>Sign out</button>
</>
)}
</Layout>
);
};
export default Login;
// authenticationForm.js
import { signIn, csrfToken } from 'next-auth/client';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import PasswordInput from '#components/auth/passwordInput';
import Button from '#components/form/button';
import TextInput from '#components/form/textInput';
const AuthenticationForm = ({ csrf }) => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const router = useRouter();
const handleChangeUsername = ({ target: { value } }) => setUsername(value);
const handleChangePassword = ({ target: { value } }) => setPassword(value);
const handleLogin = () => {
signIn('credentials', {
username,
password,
callbackUrl: `${window.location.origin}/profil`
})
.then((res) => {
console.log('form::res -> ', res);
router.back();
})
.catch((e) => {
console.log('form::e -> ', e);
setError('login error');
});
};
useEffect(() => {
if (router.query.error) {
setError(router.query.error);
setUsername(router.query.username);
}
}, [router]);
return (
<form onSubmit={handleLogin}>
<TextInput
name="username"
value={username}
onChange={handleChangeUsername}
/>
<PasswordInput handleChange={handleChangePassword} />
{error && <div>{error}</div>}
<Button type="submit">
connexion
</Button>
<input name="csrfToken" type="hidden" defaultValue={csrf} />
</form>
);
};
AuthenticationForm.getInitialProps = async (context) => {
return {
csrf: await csrfToken(context)
};
};
export default AuthenticationForm;
And for sure a NEXTAUTH_URL=http://localhost:3000 in .env.local.
If I go on /profil/authentication/login, I see my form and when I click connect, I always some errors like : "Failed to fetch", nothing more, or :
[next-auth][error][client_fetch_error] (2) ["/api/auth/csrf", TypeError: Failed to fetch]
https://next-auth.js.org/errors#client_fetch_error
Even if I try to delete all the csrf handling in my form and let sign-in "do it alone yea".
I'm really stuck with this lib and I most likely will change for another one but I would like to know what am I doing wrong? Is there a FULL example with custom sign-in page and errors handled on the same sign-in page. This is so basic that I can't understand why I don't find one easily.

#Tralgar
I think that problem is related to CSRF policy on your backend, if you are on localhost then localhost:3000 and localhost:2000 is like two different domains. Just make sure if you have your frontend domain in your backend cors policy (if on localhost it must be with a port)

I was able to fix the error by deleting the .next build folder and creating a new build by running npm run build

Related

Why is my server storage not syncing with the client one in Next.js app?

Problem: My next.js app crash on client side because of empty store object, but if I try to read this object in getServerSideProps it`s ok.
I have 2 pages in my app, profile/[id] and post/[id], all of them have getServerSideProps
User flow:
User coming on profile/[id] by friend`s link
On profile/[id] page he has profile data and 3x3 posts grid, every post is a link to post/[id]
Click on post
Navigate to post/[id] - here he has some post data: username, image, createdAt etc...
Expected: Server render html for post page after successful request
Received: Client crash after trying to read field of empty object
Question: Can you tell my what's wrong with my code? I have HYDRATE for postSlice and default next-redux-wrapper code so I'm confused.
Code:
store.ts
import {configureStore} from "#reduxjs/toolkit";
import {createWrapper} from "next-redux-wrapper";
import profileSlice from './reducers/profileSlice';
import postsSlice from './reducers/postsSlice';
import postSlice from './reducers/postSlice';
export const makeStore = () =>
configureStore({
reducer: {
profile: profileSlice,
posts: postsSlice,
post: postSlice
},
devTools: true
});
export type Store = ReturnType<typeof makeStore>;
export type RootState = ReturnType<Store['getState']>;
export const wrapper = createWrapper<Store>(makeStore);
_app.tsx
//
...imports
//
function App({Component, ...rest}: AppProps) {
const {store, props} = wrapper.useWrappedStore(rest);
const {pageProps} = props;
return (
<Provider store={store}>
<ApolloProvider client={client}>
<GlobalStyle/>
<ThemeProvider theme={theme}>
<LookyHead />
<Component {...pageProps} />
</ThemeProvider>
</ApolloProvider>
</Provider>
);
}
export default App;
postSlice.ts
//
some imports and interfaces...
//
export const postSlice = createSlice({
name: 'post',
initialState,
reducers: {
setPost: (state, action) => {
state.post = action.payload
}
},
extraReducers: {
[HYDRATE]: (state, action) => {
return {
...state,
...action.payload.post,
};
},
},
});
export const { setPost } = postSlice.actions;
export default postSlice.reducer;
Post component of posts grid on profile, here i have link to post/[id]
function Post({previewUrl, likesCount, commentsCount, duration, id}: Props) {
some code...
return (
<Link href={`/post/${id}`}>
<Container
onMouseEnter={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
>
<img src={previewUrl} alt="image"/>
<PostFooter
isHover={isHover}
likesCount={likesCount}
commentsCount={commentsCount}
time={time}
/>
</Container>
</Link>
);
}
export default memo(Post);
getServerSideProps in post/[id]
export const getServerSideProps =
wrapper.getServerSideProps(
(store) =>
async ({query}) => {
const id = query!.id as string
try {
const {data} = await client.query<Res, Vars>({
query: GET_POST,
variables: {
postId: id
}
});
console.log(data.publicPost) // Here I have data!
store.dispatch(setPost(data.publicPost))
} catch (e) {
console.log(e)
}
return {
props: {}
}
})
export default Post;
Data component inside post/[id], where client crash
//
imports...
//
function Data() {
const {post} = useAppSelector(({post}) => post) // looks weird but its ok
const parsed = parseISO(post?.createdAt) // Here my client fall
const date = format(parsed, 'dd MMMM yyyy, HH:MM', {locale: enGB})
return (
...
);
}
export default Data;

Refactoring reducers and actions in a feature based architecture style

This is a purely hypothetical example while I am trying to learn NextJs and I added Redux following the documentation from the official link
but I want to format the code in a certain way, similar to feature based architecture where each feature is encapsulated in a separate folder.
Please feel free to add, remove and suggest any changes, they are all most welcoming.
This is my general folder structure
components
public
pages
|-accounts
| |-index.tsx
|-_app.tsx
|-index.tsx
store
|-account
| |-accountsApi.ts
| |-actions.ts
| |-index.ts
| |-reducer.ts
| |-types.ts
|-index.ts
styles
package.json
...
And this is the Accounts component under accounts folder at pages.
I am dispatching the login action from action creators, is there any way to make the action accept an object with the credentials?
//pages/account/index.tsx
import Link from 'next/link'
import { useState } from 'react'
import Layout from '../../components/Layout'
import { useDispatch } from "react-redux"
import type { Account } from '../../store/account/types'
import { accoutActions } from '../../store/account'
type Props = {
accounts: Account[]
}
const Accounts: React.FunctionComponent<Props> = ({ accounts }) => {
const dispatch = useDispatch();
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const handleSubmit = (event: any) => {
let obj = {
username: username,
password: password
}
dispatch(accoutActions.login(obj)); // how to set it up, so that the action accepts an object with credentials
}
return (
<Layout title="Accounts | Demo">
<h1>Accounts</h1>
<p>You are currently on: /accounts</p>
<p>
<Link href="/">
<a>Go home</a>
</Link>
</p>
<div>
<p>Login</p>
<div>
<label htmlFor="username">Username:</label>
<input onChange={e => setUsername(e.target.value)} type="text" id="username" name="username" value={username} />
<label htmlFor="password">Last name:</label>
<input onChange={e => setPassword(e.target.value)} type="text" id="password" name="password" value={password} />
<button type="submit" onClick={handleSubmit}>Login</button>
</div>
</div>
</Layout>
)
}
export default Accounts
Below is my account store with its respective files:
accountsAPI.ts where the api calls to the server are made, I will try to refactor later in a singleton pattern using axios
however I am learning what NextJs allows and what not.
import { CreateAccountInputModel, LoginInputModel } from "./types"
const url = 'https://127.0.0.1:8888/api/accounts'
export const createAccount = async (objModel: CreateAccountInputModel): Promise<any> => {
const response = await fetch(`${url}/create`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(objModel),
})
const result = await response.json()
return result
}
export const loginAccount = async(objModel: LoginInputModel): Promise<any> => {
const response = await fetch(`${url}/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(objModel),
})
const result = await response.json()
return result
}
export const logoutAccount = async () : Promise<any> => {
const response = await fetch(`${url}/logout`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
})
const result = await response.json()
return result
}
actions.ts contains account actions
import { createAction } from '#reduxjs/toolkit'
import { AccountActionTypes } from './types'
const create = createAction(AccountActionTypes.CREATE)
const login = createAction(AccountActionTypes.LOGIN)
const logout = createAction(AccountActionTypes.LOGOUT)
export const actionCreators = {
create,
login,
logout,
};
index.ts where I combine reducer from the reducer file and make a slice so that I can join all reducers to the store
I want to be able the reducers in a separate file
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit'
import { CreateAccountInputModel } from './types'
import type { AppState, AppThunk } from '..'
import { createAccount, loginAccount, logoutAccount } from './accountsAPI'
import { reducer, initialState } from './reducer'
export const createAsync = createAsyncThunk(
'account/create',
async (objModel: CreateAccountInputModel) => {
const response = await createAccount(objModel)
// The value we return becomes the `fulfilled` action payload
return response.data
}
)
export const accountSlice = createSlice({
name: 'account',
initialState,
reducers: reducer, // error: Type 'ReducerWithInitialState<AccountState>' is not assignable to type 'ValidateSliceCaseReducers<AccountState, SliceCaseReducers<AccountState>>'.
extraReducers: (builder) => {
builder
.addCase(createAsync.pending, (state) => {
state.status = 'loading'
})
.addCase(createAsync.fulfilled, (state, action) => {
state.status = 'idle'
})
},
})
export { actionCreators as accoutActions } from './actions';
export const isAuthenticated = (state: AppState) => state.account.isAuthenticated
export default accountSlice.reducer
Here are reducers reducer.ts and the initial state of the given reducer
import { createReducer, PayloadAction } from '#reduxjs/toolkit'
import { actionCreators } from './actions';
import { Account, AccountState } from './types';
export const initialState: AccountState = {
isAuthenticated: false,
token: '',
status: 'idle',
accounts: [] as Account[],
}
export const reducer = createReducer(initialState, (builder) => {
builder
.addCase(actionCreators.login, (state, action: PayloadAction<any>) => {
state.isAuthenticated = action.payload.isAuthenticated
state.token = action.payload.token
// can I do, return { ...state, ...} so that the previous state is preserved?
})
.addCase(actionCreators.logout, (state, action) => {
state.isAuthenticated = false
})
.addCase(actionCreators.create, (state, action: PayloadAction<any>) => {
state.accounts = [...state.accounts, action.payload]
}).addDefaultCase((state, action) => {
state.isAuthenticated = false
state.token = ''
})
})
All the types and models are stored in types.ts file.
I can write types.ts if it is required, but you can assume a general case of an account.
is there any way to make the action accept an object with the credentials?
const login = createAction<TypeOfObjectWithCredentials>(AccountActionTypes.LOGIN)
Calling login with an object will put it inside the payload automatically.

useEffect hook problem, use redux to get data from backend to frontend

Hello i am new to programming, and i have learn to use MERN to make a sign up,i have no issue with backend but when i tried to use redux ,i have this problem in the frontend
enter image description here
This is the code for the SignInScreen.js
import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Link, useLocation, useNavigate } from 'react-router-dom'
import { login } from '../actions/userAction'
export const SignInScreen = (props, history) => {
const navigate = useNavigate
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const submitHandler = (e) => {
e.preventDefault()
dispatch(login(email, password))
}
const { search } = useLocation()
const redirectInUrl = new URLSearchParams(search).get('redirect')
const redirect = redirectInUrl ? redirectInUrl : '/'
const userlogin = useSelector((state) => state.userlogin)
const { userInfo, loading, error } = userlogin
const dispatch = useDispatch()
useEffect(() => {
if (userInfo) {
navigate(redirect)
}
}, [navigate, userInfo, redirect])
I dont know what wrong with the code,but i do know that it connected with the redux store which have reducer,action and constant..this is for the redux store.js
import { createStore, combineReducers, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import {
userLoginReducer,
} from './reducers/userReducer'
const userInfo = localStorage.getItem('userInfo')
? JSON.parse(localStorage.getItem('userInfo'))
: null
const initialState = {
userLogin: { userInfo },
}
const reducer = combineReducers({
userLogin: userLoginReducer,
})
const middleware = [thunk]
const store = createStore(
reducer,
initialState,
compose(applyMiddleware(...middleware))
)
export default store
This is for constant
export const USER_LOGIN_REQUEST = 'USER_LOGIN_REQUEST'
export const USER_LOGIN_SUCCESS = 'USER_LOGIN_SUCCESS'
export const USER_LOGIN_FAIL = 'USER_LOGIN_FAIL'
export const USER_LOGOUT = 'USER_LOGOUT'
this is userReducer.js
import {
USER_LOGIN_REQUEST,
USER_LOGIN_SUCCESS,
USER_LOGIN_FAIL,
USER_LOGOUT,
} from '../constants/userConstant'
function userLoginReducer(state = {}, action) {
switch (action.type) {
case USER_LOGIN_REQUEST:
return { loading: true }
case USER_LOGIN_SUCCESS:
return { loading: false, userInfo: action.payload }
case USER_LOGIN_FAIL:
return { loading: false, error: action.payload }
case USER_LOGOUT:
return {}
default:
return state
}
}
export userLoginReducer
and lastly for user.js
import Axios from 'axios'
import {
USER_LOGIN_REQUEST,
USER_LOGIN_SUCCESS,
USER_LOGIN_FAIL,
} from '../constants/userConstant'
const login = (email, password) => async (dispatch) => {
try {
dispatch({ type: USER_LOGIN_REQUEST })
const config = { headers: { 'Content-Type': 'application/json' } }
const { data } = await Axios.post(
'/api/users/login',
{ email, password },
config
)
dispatch({ type: USER_LOGIN_SUCCESS, payload: data })
localStorage.setItem('userInfo', JSON.stringify(data))
} catch (error) {
dispatch({
type: USER_LOGIN_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
})
}
}
export login
I just want to get the data into the userInfo but it dont recognized them and said it is a TypeError..I hope u can help me with this..im using the redux latest version
The problem is in userLoginReducer. Each reducer should return a complete new copy of the store.
If you return just the changes, the object you return replaces the entire state.
For example in this code:
switch (action.type) {
case USER_LOGIN_REQUEST:
return { loading: true };
}
The state { userLogin: { userInfo } } will be replaced with { loading: true }. Then you will not have userLogin anymore in the state. That's why you get the error.
To overcome this problem, spread the previous state in returned object (for all actions):
switch (action.type) {
case USER_LOGIN_REQUEST:
return { ....state, loading: true }; // ...state copies exist state to the new copy of state
}
Note: To easily solve similar bugs in the future, I recommend to use redux devtools extension. It is a great extension for debugging and look at changes in redux store.
I have tried that but still cannot fix my problem,this is the rest of my signinScreen,js
import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Link, useLocation, useNavigate } from 'react-router-dom'
import { login } from '../actions/userAction'
export const SignInScreen = (props, history) => {
const navigate = useNavigate
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const submitHandler = (e) => {
e.preventDefault()
dispatch(login(email, password))
}
const { search } = useLocation()
const redirectInUrl = new URLSearchParams(search).get('redirect')
const redirect = redirectInUrl ? redirectInUrl : '/'
const userlogin = useSelector((state) => state.userlogin)
const { userInfo, loading, error } = userlogin
const dispatch = useDispatch()
useEffect(() => {
if (userInfo) {
navigate(redirect)
}
}, [navigate, userInfo, redirect])
return (
<Container className="small-container">
<h1 className="my-3">Sign In</h1>
<Form onSubmit={submitHandler}>
<Form.Group className="mb-3" controlId="email">
<Form.Label>Email</Form.Label>
<Form.Control
type="email"
required
onChange={(e) => setEmail(e.target.value)}
/>
</Form.Group>
<Form.Group className="mb-3" controlId="password">
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
required
onChange={(e) => setPassword(e.target.value)}
/>
</Form.Group>
<div className="mb-3">
<Button type="submit">Sign In</Button>
</div>
<div className="mb-3">
New Customer?{' '}
<Link to={`/signup?redirect=${redirect}`}>Create new account</Link>
</div>
</Form>
</Container>
)
}
it still show the same error like before,i have no idea to solve this

Why, while using useEffect() and .then() in Redux, I get an Error: Actions must be plain objects. Use custom middleware for async actions

using Redux and am now straggling with a signin and signout button while using oauth.
When I press on the button to logIn, the popup window appears and I can choose an account. But in the meantime the webpage throws an error.
I got the following error as stated in the title:
Error: Actions must be plain objects. Use custom middleware for async actions.
I am using hooks, in this case useEffect().then() to fetch the data.
1) Why?
2) Also do not know, why I am getting a warning: The 'onAuthChange' function makes the dependencies of useEffect Hook (at line 35) change on every render. Move it inside the useEffect callback. Alternatively, wrap the 'onAuthChange' definition into its own useCallback() Hook react-hooks/exhaustive-deps
Here is my code:
GoogleAuth.js
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { signIn, signOut } from "../actions";
const API_KEY = process.env.REACT_APP_API_KEY;
const GoogleAuth = () => {
const isSignedIn = useSelector((state) => state.auth.isSignedIn);
console.log("IsSignedIn useSelector: " + isSignedIn);
const dispatch = useDispatch();
const onAuthChange = () => {
if (isSignedIn) {
dispatch(signIn());
} else {
dispatch(signOut());
}
};
useEffect(
() => {
window.gapi.load("client:auth2", () => {
window.gapi.client
.init({
clientId: API_KEY,
scope: "email"
})
.then(() => {
onAuthChange(window.gapi.auth2.getAuthInstance().isSignedIn.get());
console.log("isSignedIn.get(): " + window.gapi.auth2.getAuthInstance().isSignedIn.get());
window.gapi.auth2.getAuthInstance().isSignedIn.listen(onAuthChange);
});
});
},
[ onAuthChange ]
);
const onSignInOnClick = () => {
dispatch(window.gapi.auth2.getAuthInstance().signIn());
};
const onSignOutOnClick = () => {
dispatch(window.gapi.auth2.getAuthInstance().signOut());
};
const renderAuthButton = () => {
if (isSignedIn === null) {
return null;
} else if (isSignedIn) {
return (
<button onClick={onSignOutOnClick} className="ui red google button">
<i className="google icon" />
Sign Out
</button>
);
} else {
return (
<button onClick={onSignInOnClick} className="ui red google button">
<i className="google icon" />
Sign In with Google
</button>
);
}
};
return <div>{renderAuthButton()}</div>;
};
export default GoogleAuth;
reducer/index.js
import { combineReducers } from "redux";
import authReducer from "./authReducer";
export default combineReducers({
auth: authReducer
});
reducers/authReducer.js
import { SIGN_IN, SIGN_OUT } from "../actions/types";
const INITIAL_STATE = {
isSignedIn: null
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case SIGN_IN:
return { ...state, isSignedIn: true };
case SIGN_OUT:
return { ...state, isSignedIn: false };
default:
return state;
}
};
actions/index.js
import { SIGN_IN, SIGN_OUT } from "./types";
export const signIn = () => {
return {
type: SIGN_IN
};
};
export const signOut = () => {
return {
type: SIGN_OUT
};
};
types.js
export const SIGN_IN = "SIGN_IN";
export const SIGN_OUT = "SIGN_OUT";
The reason of the first error is that, inside both onSignInOnClick and onSignInOnClick, dispatch() receives a Promise (since window.gapi.auth2.getAuthInstance().signIn() returns a Promise).
There are different solution to handle effects in redux, the simplest are redux promise or redux thunk.
Otherwise you can dispatch the { type: SIGN_IN } action, and write a custom middleware to handle it.
The reason of the second error, is that the onAuthChange is redefined on every render, as you can see here:
const f = () => () => 42
f() === f() // output: false
Here's a possible solution to fix the warning:
useEffect(() => {
const onAuthChange = () => {
if (isSignedIn) {
dispatch(signIn())
} else {
dispatch(signOut())
}
}
window.gapi.load('client:auth2', () => {
window.gapi.client
.init({
clientId: API_KEY,
scope: 'email',
})
.then(() => {
onAuthChange(window.gapi.auth2.getAuthInstance().isSignedIn.get())
console.log(
'isSignedIn.get(): ' +
window.gapi.auth2.getAuthInstance().isSignedIn.get(),
)
window.gapi.auth2.getAuthInstance().isSignedIn.listen(onAuthChange)
})
})
}, [isSignedIn])

Parent re-rendering after next state change

I am new to Redux and I appear to be having an issue. Once my action has been dispatched it is successful however the parent component does not get the updated state until another state change is made. If I click login then delete a character in the input field the state change is then triggered showing me the Menu. Any help/pointers are much appreciated, thanks.
Main (Parent):
import React, { Component } from 'react'
import { connect } from 'react-redux'
import Login from '../login'
import Menu from '../menu'
type Props = { token: string }
class Main extends Component<Props, {}> {
render() {
const { token } = this.props;
if (!token) {
return (
<Login />
)
}
return (
<Menu />
)
}
}
const mapStateToProps = (state) => ({
token: state.session.token,
})
export default connect(
mapStateToProps,
null,
)(Main)
Login (Child):
import React from 'react'
import { connect } from 'react-redux'
import { login } from '../../redux/session/session.actions'
import { View, StyleSheet } from 'react-native'
import { Button, FormLabel, FormInput, FormValidationMessage } from 'react-native-elements'
import styled from 'styled-components/native'
const Container = styled(View)`
flex: 1;
flex-direction: column;
justify-content: center;
align-items: center;
`
const Wrapper = styled(View)`
width: 300;
`
type Props = { login: Function, error: string, loading: boolean };
type State = { email: string, password: string };
class Login extends React.PureComponent<Props, State> {
constructor(props) {
super(props);
this.state = {
email: null,
password: null,
}
}
render() {
console.log('props', this.props);
console.log('state', this.state);
const { loading, error } = this.props;
return (
<Container>
<Wrapper>
<FormValidationMessage>{loading ? 'Loading...' : null}</FormValidationMessage>
<FormValidationMessage>{error ? 'Unable to login, please try again.' : null}</FormValidationMessage>
<FormLabel>Email:</FormLabel>
<FormInput onChangeText={text => this.setState({ email: text })} />
<FormLabel>Password:</FormLabel>
<FormInput secureTextEntry onChangeText={password => this.setState({ password })} />
<Button title='Login' onPress={this.login} />
</Wrapper>
</Container>
)
}
login = () => {
this.props.login(this.state.email, this.state.password);
}
}
const mapStateToProps = (state) => {
console.log(state);
return {
error: state.session.error,
loading: state.session.loading
}
}
const mapDispatchToProps = ({
login
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(Login);
Reducer:
import {
LOGGING_IN,
LOGIN_SUCCESS,
LOGIN_FAILED
} from './session.types'
const initialState = {
loading: null,
error: null,
token: null,
}
export default (state = initialState, { type, payload }) => {
switch (type) {
case LOGGING_IN:
return {
...state,
loading: true
}
case LOGIN_SUCCESS:
return {
...state,
loading: false,
error: null,
token: payload.token
}
case LOGIN_FAILED:
return {
...state,
loading: false,
error: payload.error
}
default:
return state
}
}
Actions:
import { API_URL } from '../../../app-env'
import axios from 'axios'
import {
LOGGING_IN,
LOGIN_SUCCESS,
LOGIN_FAILED
} from './session.types'
export const login = (email, password) => (
async dispatch => {
console.log('here');
dispatch(loggingIn());
await axios.post(`${API_URL}/login`, {
email,
password
}).then(res => {
dispatch(loginSuccess(res.data.token))
}).catch(err => {
dispatch(loginFailed('Unable to login.'))
})
}
)
export const loggingIn = () => ({
type: LOGGING_IN,
})
export const loginSuccess = (token) => ({
type: LOGIN_SUCCESS,
payload: {
token
}
})
export const loginFailed = (error) => ({
type: LOGIN_FAILED,
payload: {
error
}
})
Since your problem is about Menu not render and Menu is under Main. So, we can ask the question what condition Main component not re-render. Luckily your example Main only depend on solely one props and no state. -I'll say your problem lies on props.token.- Since you initialize your token as null, I'll assume it hold object type. In that case, you need to make sure the token need to be a new object (new reference) else no re-render because react-redux connect by default will check the props changes before trigger the component underneath it.
EDIT: You mentioned the Menu not showing and the token is string, I can think of another reason Main not render is because connect is not trigger. You probably need to check the root of the store and make sure it has the new reference as your code only showing the reducer update state.session but not the state itself.

Resources