When I login into the user, it has success response, but the action is not passed to the reducer. But while registering a user, it's working fine.
//saga.js
import { put, takeEvery, all, call } from 'redux-saga/effects';
import {getRequest, postRequest} from '../../helpers/axiosClient';
import actions from "./actions";
function* registerUserAsync(params) {
try {
const response = yield call(() => postRequest('users/register', params.payload));
yield put({
type: actions.ON_REGISTER_SUCCESS,
payload: response.data,
});
} catch (error) {
yield put({
type: actions.ON_REGISTER_FAILURE,
payload: "Something Went Wrong",
});
}
}
function* loginUserAsync(params){
try {
const response = yield call(() => postRequest('users/login', params.payload));
yield put({
type: actions.ON_LOGIN_SUCCESS,
})
} catch (error) {
yield put({
type: actions.ON_LOGIN_FAILURE,
payload:error.response.data.message
})
}}
function* forgetPasswordUser(params){
try {
const response = yield call(() => getRequest(`users/restorepassword/${params.payload}`));
yield put({
type: actions.ON_PASSWORD_RECOVERY_SUCCESS,
payload: "PASSWORD RECOVER",
});
} catch (error) {
}}
function* registerUser() {
yield takeEvery('ON_REGISTER', registerUserAsync)
}
function* loginUser(){
yield takeEvery('ON_LOGIN', loginUserAsync)
}
function* forgetPassword(){
yield takeEvery('ON_FORGET_PASSWORD',forgetPasswordUser )
}
export default function* rootSaga() {
yield all([
registerUser(),
loginUser(),
forgetPassword()
])
}
actions.js
const actions={
ON_REGISTER: "ON_REGISTER",
ON_REGISTER_SUCCESS: "ON_REGISTER_SUCCESS",
ON_REGISTER_FAILURE: "ON_REGISTER_FAILURE",
ON_LOGIN: "ON_LOGIN",
ON_LOGIN_SUCCESS: "ON_LOGIN_SUCCESS",
ON_LOGIN_FAILURE: "ON_LOGIN_FAILURE",
ON_PASSWORD_RECOVERY_SUCCESS: "ON_PASSWORD_RECOVERY_SUCCESS",
ON_FORGET_PASSWORD:"ON_FORGET_PASSWORD",
}
export default actions;
reducer.js
import actions from "./actions";
const stateInit={
registerUser: {
first_name:"",
last_name: "",
email: "",
password:"",
confirm_password:"",
},
loadRegister:false,
signedUp:false,
loginUser: {
email: "",
password: "",
},
}
export default function auth(state=stateInit, action){
switch (action.type){
case actions.ON_REGISTER:{
return {
...state,
registerUser: action.payload,
loadRegister: true
}
}
case actions.ON_REGISTER_SUCCESS: {
return {
...state,
loginUser: action.payload,
loadRegister: false,
signedUp: true,
}
}
case actions.ON_REGISTER_FAILURE:{
alert(action.payload)
return {
...state,
loadRegister: false,
signedUp :false,
}
}
case action.ON_LOGIN :{
alert("on login")
return {
...state,
}
}
case action.ON_LOGIN_SUCCESS:{
alert("ogin suceess")
return {
...state
}
}
case action.ON_LOGIN_FAILURE :{
return {
...state,
}
}
case action.ON_FORGET_PASSWORD: {
alert("FORGET password")
return {
...state
}
}
case action.ON_PASSWORD_RECOVERY_SUCCESS:{
alert("password recovered")
}
default:{
return {
...state
}
}
}
}
Here I have the code, which I have. the same structure which for login as in the register, but when I have success response it dies not go to that actions.
I think there is a small typo in your switch statement. All the actions with their types should be actions.TYPE
For the case of login, you put action.ON_LOGIN instead of actions.ON_LOGIN
Related
After action is happened component gets an old state anyway. I'm not mutating the state btw. The changes appears only after I refresh the page
My component TodoList.tsx
Here's I'm trying to get state via useEffect hook
export const TodoList: FC = () => {
const {
todoList, isLoading, isError, message
} = useTypedSelector(
(state) => state.todo
)
console.log(todoList)
const { getTodo, reset } = useActions()
useEffect(() => {
getTodo()
console.log("render")
if (isError) {
console.log(message)
}
return () => {
reset()
}
}, [])
const statusHandler = (event: React.ChangeEvent<HTMLParagraphElement>) => {
const status = event.target.innerText
if (status === "completed") {
getTodo({ complete: true })
} else if (status === "uncompleted") {
getTodo({ complete: false })
} else if (status === "all") {
getTodo()
}
}
return (
<>
...
</>
)
}
my Reducer
I'm not mutating the state also.
const initialState: TodoState = {
todoList: [],
isError: false,
isSuccess: false,
isLoading: false,
message: ""
}
export const todoReducer = (state = initialState, { type, payload }: TodoAction): TodoState => {
switch (type) {
case "GET_TODOS":
return { ...state, isLoading: true }
case "GET_TODOS_SUCCESS":
return { ...state, isLoading: false, isSuccess: true, todoList: payload }
case "GET_TODOS_ERROR":
return { ...state, isLoading: false, isError: true, message: payload.error }
case "CREATE_TODO":
return { ...state, isLoading: false }
case "CREATE_TODO_SUCCESS":
return { ...state, isLoading: false, isSuccess: true, todoList: [...state.todoList, payload]}
case "CREATE_TODO_ERROR":
return { ...state, isLoading: false, isError: true, message: payload.error }
case "TOGGLE_TODO":
return { ...state, isLoading: true }
case "TOGGLE_TODO_SUCCESS":
return {
...state, isLoading: false, isSuccess: true, todoList: state.todoList.map((todo: ITodoItem) => {
if (todo._id === payload.id) {
console.log(payload)
return {
...todo,
complete: !todo.complete
}
}
return todo
})
}
case "TOGGLE_TODO_ERROR":
return { ...state, isLoading: false, isError: true, message: payload.error }
case "DELETE_TODO":
return { ...state, isLoading: true }
case "DELETE_TODO_SUCCESS":
return {
...state, isLoading: false, isSuccess: true, todoList: state.todoList.filter(
(todo: ITodoItem) => todo._id !== payload.id
)
}
case "DELETE_TODO_ERROR":
return { ...state, isLoading: false, isError: true, message: payload.error }
case "EDIT_TODO":
return { ...state, isLoading: true }
case "EDIT_TODO_SUCCESS":
return {
...state, isLoading: false, isSuccess: true, todoList: state.todoList.map((todo: ITodoItem) => {
if (todo._id === payload.id) {
return {
...todo,
text: payload.text
}
}
return todo
})
}
case "EDIT_TODO_ERROR":
return { ...state, isLoading: false, isError: true, message: payload.error }
case "RESET_TODOS":
return { ...state, todoList: state.todoList }
default:
return state
}
}
my action creator
Here I'm making a request from my own api.
...
export const getTodo = (data?: GetTodos) => {
return async (dispatch: Dispatch<TodoAction>): Promise<void> => {
try {
dispatch({ type: "GET_TODOS", payload: {} })
let response
if (data) {
response = await todoService.getTodo((<any>data).complete)
}
response = await todoService.getTodo()
console.log(response)
dispatch({ type: "GET_TODOS_SUCCESS", payload: response })
} catch (err) {
dispatch({
type: "GET_TODOS_ERROR",
payload: { error: "Cannot fetch the todos. Please try again later" }
})
}
}
}
export const createTodo = (payload: CreateTodo) => {
return async (dispatch: Dispatch<TodoAction>): Promise<void> => {
try {
dispatch({ type: "CREATE_TODO", payload: payload })
const response = await todoService.createTodo(payload)
dispatch({ type: "CREATE_TODO_SUCCESS", payload: response })
} catch (err) {
dispatch({
type: "CREATE_TODO_ERROR",
payload: { error: "Cannot create todo. Please try again later" }
})
}
}
}
export const toggleTodo = (payload: ToggleTodo) => {
return async (dispatch: Dispatch<TodoAction>): Promise<void> => {
try {
const response = await todoService.updateTodo(payload.id, { complete: payload.complete })
dispatch({ type: "TOGGLE_TODO_SUCCESS", payload: response })
} catch (err) {
dispatch({
type: "TOGGLE_TODO_ERROR",
payload: { error: `${err}` }
})
}
}
}
...
}
all code is here: https://github.com/maridoroshuk/todos-ts
the issue
useEffect(callback, dependencies) triggers only when data in dependencies changes. In your code, you put useEffect( ... , []), meaning you don't have any dependencies, so the callback function only runs once when the component mounts.
If you want useEffect to run on every update, you need to not put any dependencies: useEffect(...).
After receiving the result value of the refresh function, axiosInstace is executed before saving the accesstoken to AsyncStorage, so the updated token cannot be retrieved in axios.js through AsyncStorage.getItem. i want to save accesstoken first in refresh and get acecesstoken in axios.js and send to axiosInstace
How can I solve this problem?
this is my code
(saga.js)
function getPostAPI(data) {
return axiosInstace.post('/kakao/getpost', data);
}
function* getPost(action) {
try {
const result = yield call(getPostAPI, action.data);
yield put({
type: GETPOST_SUCCESS,
data: result.data,
});
} catch (err) {
if (err.response.data === 'jwtEx') {
yield put({
type: REFRESH_REQUEST,
// data: action.data,
});
yield put({
type: GETPOST_REQUEST,
data: action.data,
});
} else {
yield put({
type: GETPOST_FAILURE,
error: err.response.data,
});
}
}
}
function refreshAPI() {
return axiosInstace.post('/kakao/refresh');
}
function* refresh() {
try {
const result = yield call(refreshAPI);
yield AsyncStorage.setItem(
'accesstoken',
`${result.data.accessToken}`,
() => {
// console.log('accesstoken 재발급 저장 완료');
},
);
yield put({
type: REFRESH_SUCCESS,
data: result.data,
});
} catch (err) {
yield put({
type: REFRESH_FAILURE,
error: err.response.data,
});
}
}
(axios.js)
AxiosInstance.interceptors.request.use(async (cfg) => {
const acecesstoken = await AsyncStorage.getItem('accesstoken');
const refreshtoken = await AsyncStorage.getItem('refreshtoken');
if (acecesstoken) {
cfg.headers.Authorization = `Bearer ${acecesstoken} ${refreshtoken}`;
}
return cfg;
});
export default AxiosInstance;
A simple solution would be to call your refresh() generator directly:
function* getPost(action) {
try {
const result = yield call(getPostAPI, action.data);
yield put({
type: GETPOST_SUCCESS,
data: result.data,
});
} catch (err) {
if (err.response.data === 'jwtEx') {
yield call(refresh);
// you could also redispatch the original action
yield put(action);
} else {
yield put({
type: GETPOST_FAILURE,
error: err.response.data,
});
}
}
}
Alternatively your can start a race between REFRESH_SUCCESS and REFRESH_FAILURE:
const { success, failure } = yield race({
success: take('REFRESH_SUCCESS'),
failure: take('REFRESH_FAILURE'),
});
if(success) {
// continue
} else {
// handle refresh failure
}
I'm having a lot of issues with this. I've been trying to wire up redux-saga to fetch the user data from the server by calling 'api/current_user'. When I call this API without using saga I get the correct user data, but when I call within saga I get the error message {error: "Please Log In to Continue"} meaning the server does not think I am logged in.
I use a passport/express session on the back end to authenticate the user if it matters.
saga.js
function* login() {
try {
const response = yield fetch(`${API_URL}/api/current_user`, {
method: 'GET',
credentials: 'include',
});
const responseBody = yield response.json();
// console.log(r, 'RESPONSE')
setSession(responseBody);
yield put(loginUserSuccess(responseBody));
} catch (error) {
let message;
switch (error.status) {
case 500:
message = 'Internal Server Error';
break;
case 401:
message = 'Invalid credentials';
break;
default:
message = error;
}
yield put(loginUserFailed(message));
setSession(null);
}
}
working api call page/ button to call saga
const AnalyticsDashboardPage = () => {
const [user, setUser] = useState(null);
useEffect(() => {
API.getUser().then((r) => {
// console.log(r);
setUser(r);
});
}, []);
return (
<React.Fragment>
<Row>
<Col>
<div className="page-title-box">
<div className="page-title-right">
<form className="form-inline">
<div className="form-group">
<HyperDatepicker />
</div>
<button className="btn btn-primary ml-2">
<i className="mdi mdi-autorenew"></i>
</button>
<button className="btn btn-primary ml-1">
<i className="mdi mdi-filter-variant"></i>
</button>
</form>
</div>
<h4 className="page-title">Dashboard</h4>
</div>
</Col>
</Row>
<Row>
<Col>
<h4>User Data</h4>
<pre>{user ? JSON.stringify(user, ' ', 2) : null}</pre>
<Button>Login</Button>
</Col>
</Row>
</React.Fragment>
);
};
const mapStateToProps = (state) => {
console.log(state);
};
export default connect(mapStateToProps, { loginUser })(AnalyticsDashboardPage);
route I am calling
app.get('/api/current_user', requireLogin, (req, res) => {
res.send(req.user);
});
requireLogin middleware
module.exports = (req, res, next) => {
if (!req.user) {
return res.status(401).send({ error: 'You must log in!' });
}
next();
};
auth reducer
const Auth = (state: State = INIT_STATE, action: AuthAction) => {
switch (action.type) {
case LOGIN_USER:
return { ...state, loading: true };
case LOGIN_USER_SUCCESS:
return { ...state, user: action.payload, loading: false, error: null };
case LOGIN_USER_FAILED:
return { ...state, error: action.payload, loading: false };
case REGISTER_USER:
return { ...state, loading: true };
case REGISTER_USER_SUCCESS:
return { ...state, user: action.payload, loading: false, error: null };
case REGISTER_USER_FAILED:
return { ...state, error: action.payload, loading: false };
case LOGOUT_USER:
return { ...state, user: null };
case FORGET_PASSWORD:
return { ...state, loading: true };
case FORGET_PASSWORD_SUCCESS:
return { ...state, passwordResetStatus: action.payload, loading: false, error: null };
case FORGET_PASSWORD_FAILED:
return { ...state, error: action.payload, loading: false };
default:
return { ...state };
}
};
action
export const loginUser = (payload): AuthAction => ({
type: LOGIN_USER,
payload,
});
you need to yield the fetch call itself. as it is a asynchronous operation.
something like this:
import { call, put } from 'redux-saga/effects'
function* login() {
try {
const response = yield call(fetch,`${API_URL}/api/current_user`, {
method: 'GET',
credentials: 'include',
});
const responseBody = yield call(response.json);
// console.log(r, 'RESPONSE')
setSession(responseBody);
yield put(loginUserSuccess(responseBody));
} catch (error) {
let message;
switch (error.status) {
case 500:
message = 'Internal Server Error';
break;
case 401:
message = 'Invalid credentials';
break;
default:
message = error;
}
yield put(loginUserFailed(message));
setSession(null);
}
}
response.json also returns a promise, so you might need to yield it as well.
more details here: https://redux-saga.js.org/docs/basics/UsingSagaHelpers.html
You need to use `yield' for any async call.
function* login() {
try {
const response = yield fetch(`${API_URL}/api/current_user`, {
method: 'GET',
credentials: 'include',
});
const responseBody = yield response.json();
// console.log(r, 'RESPONSE')
setSession(responseBody);
yield put(loginUserSuccess(responseBody));
} catch (error) {
// .....
}
I'm a junior dev and have just joined recently. I'm trying to create a blog-like website where users can save a post and update an already saved post. I'm currently confused as to how to assign the snippetId within the post.
So this website was already made in Angular and I've been asked to migrate it to React. I'm mostly confused about how to store the ID as it is received from the server in response.data for a new post, and also, how I would receive it in the action.js file from the Redux store if it already exists.
Please help me understand the snippetData['snippetId'] part from the Angular and if I should I even use snippetData in the initialState or just use snippetId, snippetDescription, snippetTitle directly in the `initialState.
My code for now looks something like this:
action.js
import { SAVE_POST } from './types';
export const savePost=({snippetId, snippetDescription, snippetTitle})=> async dispatch=>{
const config = {
headers: {
'Content-Type': 'application/json'
}
}
}
const body = JSON.stringify({snippetId, snippetDescription, snippetTitle});
try{
if(snippetId==null){
const res = await axios.post('/api/save', body, config);
dispatch({
type: SAVE_POST,
payload: res.data
});}
else{
snippetData['snippetId']=snippetId
const res = await axios.post('/api/update', body, config);
dispatchEvent({
type: UPDATE_POST,
payload: res.data
})
}
}catch(err){
console.log(err);
}
reducer/post.js
import { SAVE_POST} from '../actions/types';
const initialState={
snippetData: {
snippetId: null,
snippetTitle: null,
snippetDescription: null
}
};
export default function (state=initialState, action){
const {type, payload}=action;
switch(type){
case SAVE_POST:
return {...state,
snippetData: {
snippetId: payload,
snippetDescription: payload,
snippetTitle: payload}
case UPDATE_POST:
return {...state,
snippetId: payload,
snippetDescription: payload,
snippetTitle: payload
}
}
}
This is finally the Angular file from where I've been asked to translate to React:
$scope.savesnippet=function(){
$scope.snippetdata={}
$scope.snippetdata['snippetTitle']=$scope.snippetTitle
$scope.snippetdata['snippetDescription']=$scope.snippetDescription
console.log($scope.snippetId)
if($scope.snippetId==null){
return $http.post('/api/save',$scope.snippetdata).then(function(response){
if(response.status==200){
$scope.snippetId=response.data;
toaster.pop('success','Snippet saved successfully!')
}else{
toaster.pop('danger','An error has occured while saving the snippet. Please try again')
}
});
}else{
$scope.snippetdata['snippetId']=$scope.snippetId
return $http.post('/api/update',$scope.snippetdata).then(function(response,status){
if(response.status==200){
toaster.pop('success','Snippet saved successfully!')
}else{
toaster.pop('danger','An error has occured while updating the snippet. Please try again')
}
});
}
}
edit:
editor.js
performSave = (snippetData) => {
const {enteredText, title} = this.state;
let {snippetId, snippetDescription, snippetTitle} = snippetData;
snippetTitle=title;
snippetDescription=enteredText;
savePost(snippetId, snippetDescription, snippetTitle);
}
const mapStateToProps = state=>({
snippetData: state.snippetData
})
export default connect(mapStateToProps, {savePost})(Editor);
What i understand from you given angular code, on API save success, you dont get entire data. U only get id of the save data. So in payload you need to update snippetId.
In case of save success, you dont need any update. U can just use as payload.
import { SAVE_POST } from "./types";
export const savePost = ({
snippetId,
snippetDescription,
snippetTitle
}) => async dispatch => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
let snippetData = { snippetId, snippetDescription, snippetTitle };
try {
if (snippetId == null) {
const res = await axios.post("/api/save", JSON.stringify(snippetData), config);
snippetData.snippetId = res.data
dispatch({
type: SAVE_POST,
payload: snippetData
});
} else {
const res = await axios.post("/api/update", JSON.stringify(snippetData), config);
dispatchEvent({
type: UPDATE_POST,
payload: snippetData
});
}
} catch (err) {
console.log(err);
}
};
// Reducer:
import { SAVE_POST } from "../actions/types";
const initialState = {
snippetData: {
snippetId: null,
snippetTitle: null,
snippetDescription: null
}
};
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case SAVE_POST:
return {
...state,
snippetData: payload
};
case UPDATE_POST:
return {
...state,
snippetData: payload
};
}
}
In a React app, I do not understand why the Yield line in a generator function is not 'waiting'? Specifically, in the LOGIN function below, I would expect the Yield line immediately following console.log("Step 3") to pause until it was completed; however it does NOT pause and Step 8 is immediately processed. I would expect the STEPS in the console.log to follow the logical numerical order. The actual order that is printed out in the browser console window is: 1,2,3,8,9,10,4,5,6,7. Can someone explain why it is NOT pausing?
export function* LOGIN({ payload }) {
const { email, password } = payload
yield put({
type: 'user/SET_STATE',
payload: {
loading: true,
},
})
let userCog
try {
console.log("Step 1")
userCog = yield call(login, email, password)
console.log("Step 2")
} catch (err) {
if (err.code === 'UserNotConfirmedException') {
yield put({
type: 'user/SET_STATE',
payload: {
loading: true,
email,
},
})
yield history.push('/system/verification')
}
}
console.log("Step 3")
yield put({
type: 'user/LOAD_CURRENT_ACCOUNT',
})
console.log("Step 8")
if (userCog) {
console.log("Step 9")
yield history.push('/dashboard/analytics')
console.log("Step 10")
}
}
export function* LOAD_CURRENT_ACCOUNT() {
yield put({
type: 'user/SET_STATE',
payload: {
loading: true,
},
})
console.log("Step 4")
const response = yield call(currentUser)
console.log("Step 5")
if (response) {
const { username } = response
yield put({
type: 'user/SET_STATE',
payload: {
id: '123',
name: 'Administrator',
email: username,
role: 'admin',
authorized: true,
},
})
}
console.log("Step 6")
yield put({
type: 'user/SET_STATE',
payload: {
loading: false,
},
})
console.log("Step 7")
}
EDIT: Here is the redux dispatch from the Login UI Component
onSubmit = event => {
event.preventDefault()
const { form, dispatch } = this.props
form.validateFields((error, values) => {
if (!error) {
dispatch({
type: 'user/LOGIN',
payload: values,
})
}
})
}