My application is calling the action correctly as redux shows it gets the expected data it needs. The problem is it never stores the payload from the action. it is similar to my profile reducer and that works great.
This is the action
export const getCurrentFriends = () => dispatch => {
axios
.get("/api/friends")
.then(res =>
dispatch({
type: GET_FRIENDS,
payload: res.data
})
)
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: {}
})
);
};
This is the reducer
export default function(state = [], action) {
switch (action.type) {
case GET_FRIENDS:
return [
...state,
{
friends: action.payload
}
];
default:
return state;
}
}
Related
There is such kind of code that I have:
const mapStateToProps = (state, ownProps) => ({
historyData: getHistoryForSavedVariants(state)[ownProps.savedVariant.variantId],
isHistoryLoading: getHistoryLoading(state),
})
const mapDispatchToProps = (dispatch, ownProps) => ({
loadData: () => {
-----> dispatch(loadHistoryForSavedVariant(ownProps.savedVariant))
},
})
export default connect(mapStateToProps, mapDispatchToProps)(HistoryButton)
In another file loadHistoryForSavedVariant is the following:
export const loadHistoryForSavedVariant = (savedVariant) => {
return (dispatch) => {
dispatch({ type: REQUEST_HISTORY })
const url = `/api/saved_variant/${savedVariant.variantId}/saved_variant_history`
new HttpRequestHelper(url,
(responseJson) => {
dispatch({ type: RECEIVE_HISTORY })
dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
},
(e) => {
dispatch({ type: RECEIVE_HISTORY })
dispatch({ type: RECEIVE_DATA, error: e.message, updatesById: {} })
},
).get({ xpos: savedVariant.xpos, ref: savedVariant.ref, alt: savedVariant.alt, familyGuid: savedVariant.familyGuids[0] })
}
}
So, as can be seen dispatch ultimately gets a function - (dispatch) => {...} and not an action. Why? I don't understand how that works. On Redux official webpage I see everwhere that dispatch gets an action and not a function, so I am confused. The code is, of course, working fine, I am just interested in this particular mechanism, in whats happening here.
That is a "thunk function". Thunks are a Redux middleware that allow you to pass functions into dispatch(), which is useful for writing async logic separate from your components.
For more details, see these Redux tutorials:
https://redux.js.org/tutorials/fundamentals/part-6-async-logic
https://redux.js.org/tutorials/essentials/part-5-async-logic
New to React, first attempt at building anything related to authentication. After following the Redux tutorial, I've seen it uses the reduxjs/toolkit to combine actions, reducers, and services using createSlice, createAsyncThunk, etc. All the tutorials I've read on authentication / login don't follow this pattern, and I can't wrap my head around setting up the initialState for authentication, users, and JWT. Is it possible to combine actions, reducers, and services into a "slice" or is it better to keep them separate?
The closest guide to what I want uses redux-thunkas a middleware, but if my app is already using redux/toolkit.
Any ideas how to go from this:
//_service auth.service.js
const login = (username, password) => {
//return JSON object
//Response: { "token": "lkjdfs9876fd", "user": "username", "type": "partner", "customer": "servicetrace" }
return axios.post(API_URL, 'signin', {
username,
password
})
.then((response) => {
if(response.data.accessToken){
localStorage.setItem('user', JSON.stringify(response.data))
}
return response.data;
})
.catch((error) => console.log(error));
};
//_reducer auth.js
const user = JSON.parse(localStorage.getItem("user"));
const initialState = user
? { isLoggedIn: true, user }
: { isLoggedIn: false, user: null };
export default function (state = initialState, action){
const { type, payload } = action;
switch (type) {
case LOGIN_SUCCESS:
return {
...state,
isLoggedIn: true,
user: payload.user,
};
case LOGIN_FAIL:
return {
...state,
isLoggedIn: false,
user: null,
};
case LOGOUT:
return {
...state,
isLoggedIn: false,
user: null,
};
default:
return state;
}
}
//_actions auth.js
export const login = (username, password) => (dispatch) => {
return AuthService.login(username, password)
.then(
(data) => {
dispatch({
type: LOGIN_SUCCESS,
payload: { user: data }
});
return Promise.resolve();
},
(error) => {
const message = (
error.response && error.response.data && error.response.data.message) ||
error.message ||
error.toString();
dispatch({
type: LOGIN_FAIL,
});
dispatch({
type: SET_MESSAGE,
payload: message
});
return Promise.reject();
}
);
};
into a single slice:
//authSlice.js
import { createSlice } from '#reduxjs/toolkit'
const initialState = {
isAuthenticated: false,
user: null,
token: null,
error: null
}
const authSlice = createSlice({
name: 'auth',
initialState,
reducers:{
loginSuccess: (state, action) => {
//
},
loginFailure: (state, action) => {
//
}
},
extraReducers:{}
});
export default authSlice.reducer
You'd transform the reducer to the slice, but keep the thunks as they are. The redux-thunk middleware is already included with redux toolkit, so you can directly use them - but they still need to be defined outside your slice.
You can also replace this hand-written thunk by one created with createAsyncThunk, but I'd first try to get this up & running and then take a look at the docs (or even better yet, the Async Logic and Data Fetching part of the official essentials tutorial) and refactor it over to cAT after that. No need to do everything at once.
I am trying to implement a check for authentication and to login/logout users using redux and firebase. I have the following code:
Action Types:
export const LOGIN_REQ = 'AUTH_REQ';
export const LOGOUT_REQ = 'LOGOUT_REQ';
export const AUTH_SUCCESS = 'AUTH_SUCCESS';
export const AUTH_FAILED = 'AUTH_FAILED';
export const GET_AUTH = 'GET_AUTH';
Reducers:
import * as ActionTypes from './ActionTypes';
export const auth = (state = {
isAuth: false,
user: null
}, action) => {
switch (action.type) {
case ActionTypes.LOGIN_REQ:
return { ...state, isAuth: false, user: null };
case ActionTypes.LOGOUT_REQ:
return { ...state, isAuth: false, user: null };
case ActionTypes.AUTH_FAILED:
return { ...state, isAuth: false, user: null };
case ActionTypes.AUTH_SUCCESS:
return { ...state, isAuth: true, user: action.payload };
case ActionTypes.GET_AUTH:
return state;
default:
return state;
}
}
Thunks:
export const getAuth = () => (dispatch) => {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
console.log('Get AUTH called');
dispatch(authSuccess());
}
else {
console.log('Get AUTH called');
dispatch(authFailed());
}
});
}
export const loginReq = (email, password, remember) => (dispatch) => {
firebase.auth().signInWithEmailAndPassword(email, password)
.then((cred) => {
if (remember === false) {
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);
console.log('Logged In with Redux without persist');
}
else {
console.log('Logging in with Persist');
}
console.log('Dispatching Success !');
dispatch(authSuccess(cred.user.uid));
})
.catch((err) => {
console.log(err);
dispatch(authFailed(err));
});
}
export const logoutReq = () => (dispatch) => {
firebase.auth().signOut()
.then(() => dispatch(getAuth()))
.catch((err) => console.log(err));
}
export const authSuccess = (uid = null) => ({
type: ActionTypes.AUTH_SUCCESS,
payload: uid
});
export const authFailed = (resp) => ({
type: ActionTypes.AUTH_FAILED,
payload: resp
});
And I am calling it from a component as shown below:
const mapStateToProps = state => {
return {
isAuth: state.isAuth,
user: state.user
}
}
const mapDispatchToProps = dispatch => ({
getAuth: () => { dispatch(getAuth()) },
loginReq: (email, password, remember) => { dispatch(loginReq(email, password, remember)) },
logoutReq: () => { dispatch(logoutReq()) }
})
handleLogin() {
this.props.loginReq(this.state.email, this.state.password, this.state.remember);
}
handleLogOut() {
this.props.logoutReq();
}
<BUTTON onClick=()=>this.handleLogOut()/handleLogin()>
I am close to tears because I cannot figure out why my loginReq fires one or many gitAuth() methods even when i click on the button once. This happens only for the loginReq() action. I have not specified anywhere that loginReq() should fire it.
Also i have called the getAuth() method in the component did mount method of my main screen which checks authentication status once at the start of the app.
EDIT: I have console logged in the component did mount method in the main component so I know that this getAuth() call is not coming from there.
Imo the answer is badly done, try to reestructure it better, what you call "Thunks" are actually "Actions". But if I were to tell you something that could help is that maybe the problem lies in the thunk middleware config or with the way firebase is beign treated by the dispatcher, so I would say that you better try coding an apporach with the react-redux-firebase library (this one: http://react-redux-firebase.com/docs/getting_started ) it makes easier to connect redux with a firebase back end. Other great reference, the one that I learned with, is The Net Ninja's tutorial playlist about react, redux and firebase.
A friend of mine told me this has to do with something known as an 'Observer' which is in the onAuthStateChange() provided by firebase. Basically there is a conflict between me manually considering the user as authenticated and the observer doing so.
I am using Redux with react and redux-thunk as a middleware.
When I make http requests I have to dispatch three actions in my thunks.
I will use my auth example.
here are my actions:
export const loginSuccess = () => ({
type: AUTH_LOGIN_SUCCESS,
})
export const loginFailure = (errorMessage) => ({
type: AUTH_LOGIN_FAILURE,
errorMessage,
})
export const loginRequest = () => ({
type: AUTH_LOGIN_REQUEST,
})
and here is the thunk which combines above three actions:
export const login = (credentials) => dispatch => {
dispatch(loginRequest())
const options = {
method: 'post',
url: `${ENDPOINT_LOGIN}?username=${credentials.username}&password=${credentials.password}`,
}
axiosInstance(options)
.then(response => {
dispatch(loginSuccess())
dispatch(loadUser(response.data)) // I have separate action for user and separate reducer.
window.localStorage.setItem(ACCESS_TOKEN_KEY, response.data.token)
})
.catch(error => {
return dispatch(loginFailure(error))
})
}
And here is my reducer:
const initialState = {
pending: false,
error: false,
errorMessage: null,
}
export const loginReducer = (state = initialState, action) => {
switch (action.type) {
case AUTH_LOGIN_SUCCESS:
return {
...state,
pending: false,
error: false,
errorMessage: null,
}
case AUTH_LOGIN_FAILURE:
const { errorMessage } = action
return {
...state,
pending: false,
error: true,
errorMessage,
}
case AUTH_LOGIN_REQUEST:
return {
...state,
pending: true,
}
default:
return state
}
}
I have to do almost exact same things when I am sending another request, for example in case of logout. I feel like I am repeating myself a lot and there must be a better way.
I need to know what is the best practice to handle this issue.
Any other corrections and recommendations will be appreciated.
If you are looking for "ready to use" solution take a look at:
https://redux-toolkit.js.org/api/createAsyncThunk
https://redux-resource.js.org/ (but it is written with js (not TS), and no #types definition for this library)
If you are looking for a custom solution you can create a few factories:
factory for reducer
factory for three actions
factory for thunk
const actions = createActions('My request name');
const reducer = createReducer(actions);
...
const thunk = createThunk(config);
or even you can combine them:
const { actions, reducer, thunk } = createRequestState('Name...', config);
... but this is just an idea.
I'm trying to implement Async/Await function in my redux, but it doesn't seem to be working.
Actions.js
export function getUserCart(token) {
return async dispatch => {
await axios
.get(`${apiUrl}/user/cart`, {
headers: { Authorization: `Bearer ${token}` }
})
.then(response => {
dispatch({
console.log(GET_USER_CART_SUCCESS);
type: GET_USER_CART_SUCCESS,
cart: response.data
});
})
.catch(error => {
dispatch({
type: GET_USER_CART_FAILED,
error: error.response.data
});
});
};
}
Reducers.js
case GET_USER_CART_SUCCESS:
return {
...state,
cart: action.cart,
type: action.type
};
Component.js
componentDidMount() {
console.log("before");
this.props.getUserCart(this.props.user.idToken);
console.log("after");
}
The console returned :
before
after
GET_USER_CART_SUCCESS
However, if I do this
async componentDidMount() {
console.log("before");
await this.props.getUserCart(this.props.user.idToken);
console.log("after");
}
it returns the correct output :
before
GET_USER_CART_SUCCESS
after
I'm not comfortable with adding async and await in my component's function, it shouldn't be that way either. Is there anything I'm missing from my actions.js?