Waiting for state to change in react with redux - javascript

i am using redux in react-native to fetch data from an api, here is whhat i have done so far
api_type.js
export const USER_LOGIN = 'user_login_action';
export const USER_LOGINING = 'logining_users';
export const USER_LOGEDIN = 'user_logged_in';
index.js
import axios from 'axios';
import { USER_LOGIN, USER_WALLETS,USER_LOGINING } from './api_types';
const AUTH_API_URL = 'http:/api/v1';
const CORE_API_URL = 'http:/api/v1';
let username="";
let password="";
let auth_token ="";
let AuthStr = "";
export function UserWallets(){
return function(dispatch){
AuthStr ="Bearer "+auth_token;
console.log ("new auth : "+AuthStr);
axios.defaults.headers.common['Authorization'] = AuthStr
axios.get(`${CORE_API_URL}/wallet/allwallets`)
.then(response => {
dispatch({
type: USER_WALLETS,
payload: response['data']
});
}).catch((error) => {
console.log(error);
})
}
}
export function UserLogin() {
return function(dispatch) {
dispatch({
type:USER_LOGINING
});
axios.post(
`${AUTH_API_URL}/authenticate/users`,
{
email: username,
password: password
}
)
.then(response => {
dispatch({
type: USER_LOGIN,
payload: response['data']
});
auth_token=response['data']['token'];
}
)
.catch((error) => {
console.log(error);
})
}
}
export function username(term) {
username=term;
console.log("username " +username);
return{
type:"username",
username
};
}
export function password(term) {
password=term;
console.log("password " +password);
return{
type:"password",
password
};
}
export function authToken (term){
auth_token = term;
return{
type:"authtoken",
auth_token
}
}
auth_reducer.js
import { USER_LOGIN ,USER_LOGINING } from '../actions/api_types';
const INTIAL_STATE = {
message: '',
token:'',
logging: false,
loggedin: false,
loginerr: null,
};
export default function (state = INTIAL_STATE, action) {
console.log("present state"+action.type);
switch(action.type) {
case USER_LOGIN:{
return { ...state, message: action.payload.message, token:action.payload.token,loggedin:true};
}
case USER_LOGINING:{
return {...state,logging:true }
}
default:{
console.log("default "+action.type);
}
}
return state;
}
index.js // combine reducer
import { combineReducers } from 'redux';
import drawer from './drawer';
import AuthReducer from './auth_reducer';
import CoreReducer from './core_reducer';
export default combineReducers({
auth: AuthReducer,
});
i have created and configured the store and wrapped my app with the provider from react-redux, and i have passed the store to the provider, in a nutshell i can now access the store from my componets.
below is a function in my login_component, that triggers once i click on login
login(){
if(this.state.email==""){
alert("Email require");
return;
}else if(this.state.password==""){
alert("password require");
return;
}else{
//set the paramter for the reducer to use
this.props.username(this.state.email);
this.props.password(this.state.password);
//activate the user login action
this.props.UserLogin();
if(!this.props.auth.loggedin){
console.log("logging in");
//show loadging gif
}
//checking from response from the auth api
if(this.props.auth.message=="user successfully logged in"){
alert(this.props.auth.token);
Actions.home();
}else{
alert("invalid Username/Password");
}
}
}
Now this is problem, once i click on login, the block of code i commented (check response from api) will not wait for the store value to change before it perform it action, please i need a way around this.

i finally got a solution to the problem, the api call was async but the problem was that in the component, i tested for the response before the store changes so here is the solution, i added the following to my login component
componentWillReceiveProps(nextProps) {
console.log("component update");
if(nextProps.auth.loggedin==true){
if(nextProps.auth.message=="user successfully logged in"){
this.setState(previousState => {
return { spinnerv: false };
});
Actions.home();
}else{
alert("invalid Username/Password");
}
}
}
what happens here is that function componentWillReceiveProps, check if the states has changed and then text if the response is componentWillReceiveProps.
thanks jmargolisvt for your support.
i hope this help someone else.

You need to perform this API call asynchronously. Basically, you will have your login function dispatch an async action that will make the API call. Then from your success/fail methods of the API call, you'll dispatch another (synchronous) call that either logs the user in or not.
You'll want to incorporate Redux Thunks to make your async call.
https://github.com/gaearon/redux-thunk

Related

Where to set Sentry's setUser in Next.js app?

I have been trying to set user data into Sentry's scope globally, so every time there's an error or event, user info is passed to it.
My app is built in Next.js, so naturally I added the config as it is in Sentry's documentation for Next.js.
I haven't got the idea on where to add the Sentry.setUser({id: user.Id}) method in order for it to set the user globally.
So far I have added it to the Sentry's _error.js file, inside the getInitialProps method:
import NextErrorComponent from 'next/error';
import * as Sentry from '#sentry/nextjs';
import { getUser } from '../lib/session';
const MyError = ({ statusCode, hasGetInitialPropsRun, err }) => {
if (!hasGetInitialPropsRun && err) {
Sentry.captureException(err);
}
return <NextErrorComponent statusCode={statusCode} />;
};
MyError.getInitialProps = async (context) => {
const errorInitialProps = await NextErrorComponent.getInitialProps(context);
const { req, res, err, asPath } = context;
errorInitialProps.hasGetInitialPropsRun = true;
const user = await getUser(req, res);
// Set user information
if (user) {
console.log('Setting user');
Sentry.setUser({ id: user.Id });
}
else {
console.log('Removing user');
Sentry.configureScope(scope => scope.setUser(null));
}
if (res?.statusCode === 404) {
return errorInitialProps;
}
if (err) {
Sentry.captureException(err);
await Sentry.flush(2000);
return errorInitialProps;
}
Sentry.captureException(
new Error(`_error.js getInitialProps missing data at path: ${asPath}`),
);
await Sentry.flush(2000);
return errorInitialProps;
};
export default MyError;
But when trying to log errors, the user info doesn't show in Sentry, only the default user ip:
I have also tried setting the user after successful login, and still nothing..
Help is appreciated!!
Not sure if this is the right way, but the above solutions didn't work for me. So I tried calling setUser inside _app.tsx.
import { useEffect } from "react";
import { setUser } from "#sentry/nextjs";
import { UserProvider, useUser } from "#auth0/nextjs-auth0";
import type { AppProps } from "next/app";
function SentryUserManager() {
const { user } = useUser();
useEffect(() => {
if (user) {
setUser({
email: user.email ?? undefined,
username: user.name ?? undefined,
});
} else {
setUser(null);
}
}, [user]);
return null;
}
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<UserProvider>
<Component {...pageProps} />
<SentryUserManager />
</UserProvider>
);
}
Still not sure why this worked for me and the other solutions didn't, but figured it was worth sharing.
I would suggest using the callback handler to set your Sentry user context.
import { handleAuth, handleLogin, handleCallback } from "#auth0/nextjs-auth0";
import * as Sentry from "#sentry/nextjs";
import { NextApiHandler } from "next";
const afterCallback = (_req, _res, session, _state) => {
Sentry.setUser({
id: session.user.sub,
email: session.user.email,
username: session.user.nickname,
name: session.user.name,
avatar: session.user.picture,
});
return session;
};
const handler: NextApiHandler = handleAuth({
async login(req, res) {
await handleLogin(req, res, {
returnTo: "/dashboard",
});
},
async callback(req, res) {
try {
await handleCallback(req, res, { afterCallback });
} catch (error) {
res.status(error.status || 500).end(error.message);
}
},
});
export default Sentry.withSentry(handler);
You can set the user in Sentry right after successful login
const handleLogin = {
try {
const res = await axios.post("/login", {"john#example.com", "password"})
if (res && res?.data) {
// Do other stuff
Sentry.setUser({ email: "john#example.com" });
}
}
}
Additionaly you can clear the user while logging out
const handleLogout = {
// Do othe stuff
Sentry.configureScope(scope => scope.setUser(null));
}

Dispatching with react-redux

I'm trying to call a function from React JSX button and I have a problem with that because when in react-redux actions in the defined function I don't put dispatch function, works as it supposed to.
const like_function = (id) => {
let post_id = id;
if (isAuthenticated) {
console.log(post_id, user_id);
like_post(post_id, user_id);
} else {
<Redirect to="/login" />;
console.log("Redirect to login");
}
};
Here in this button I invoke function with one parameter.
<button onClick={() => like_function(post.id)}>Like</button>
This is redux action. Here is the problem. When dispatch is deleted function works but with dispatch is not even called, it wont even log data to console before async request
export const like_post = (post_id, user_id) => async (dispatch) => {
const data = { post_id: post_id, user_id: user_id };
console.log(data);
dispatch({
type: POST_LIKE_LOADING,
});
try {
const res = await axios.put(`http://localhost:8000/api/like_list/`, data);
//console.log(res.data);
dispatch({
type: POST_LIKED,
payload: res.data,
});
} catch (err) {
console.log(err);
dispatch({
type: POST_LIKEING_FAIL,
});
}
};
Here are my redux reducers
case POST_LIKE_LOADING:
return {
...state,
isLoading: true,
};
case POST_LIKED:
return {
...state,
isLoading: true,
message: "OK"
};
Sorry about my English, hope you understood me, thanks in advance
You're not dispatching the action.
import { useDispatch } from 'react-redux'
const dispatch = useDispatch()
dispatch(like_post(post_id, user_id))

Creating posts with react redux

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
};
}
}

How to display error messages from django-rest-framework in React

I am trying to implement user Registration form using Django rest framework and react, redux. I am able to register user successfully, but I am facing issue in displaying error those are provided by Django in case of error.
What I have done so far
export const AUTH_START = 'AUTH_START';
export const AUTH_SUCCESS = 'AUTH_SUCCESS';
export const AUTH_FAIL = 'AUTH_FAIL';
export const AUTH_LOGOUT = 'AUTH_LOGOUT';
Here is Reducer functionality
const initialState = {
token: null,
error: null,
loading: false
}
const authFail = (state, action) => {
return updateObject(state, {
error: action.error,
loading: false
});
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.AUTH_START:
return authStart(state, action);
case actionTypes.AUTH_SUCCESS:
return authSuccess(state, action);
case actionTypes.AUTH_FAIL:
return authFail(state, action);
case actionTypes.AUTH_LOGOUT:
return authLogout(state, action);
default:
return state;
}
}
export default reducer;
export const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
}
}
Here is store functionality
export const authFail = (error) => {
return {
type: actionTypes.AUTH_FAIL,
error: error
}
}
export const authSignup = (username, email, password1, password2) => {
return dispatch => {
dispatch(authStart());
axios.post('http://127.0.0.1:8000/rest-auth/registration/', {
username: username,
email: email,
password1: password1,
password2: password2
}).then(res => {
const token = res.data.key;
const expirationDate = new Date(new Date().getTime() + 3600 * 1000);
localStorage.setItem('token', token);
localStorage.setItem('expirationDate', expirationDate);
dispatch(authSuccess(token));
dispatch(checkAuthTimeOut(3600));
}).catch(err => {
dispatch(authFail(err))
})
}
}
Here is settings.py
INSTALLED_APPS = [
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'corsheaders',
'rest_auth',
'rest_auth.registration',
'rest_framework',
'rest_framework.authtoken',
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
)
}
You can full error response from server like this
axios.get('/user/12345')
.catch(function (error) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
console.log(error.config);
});
So if you have error you can use dispatch to dispatch error something like this
dispatch(displayError(error.message));
dispatch(displayError(error.response.data));
dispatch(displayError(error.response.status));

Call Multiple actions one after another in the react-redux, redux-thunk

I am totally new to the react-redux. I am using redux-thunk . Here, I have one login action. On that action I am calling an API which will give me some token, that I have to store in the state. Then immediately, after success of this action, I have to make another API request which will have this token in the header and will fetch more data. Based on this, I would like to redirect the user.
so,
import { generateToken } from '../APIs/login';
import HttpStatus from 'http-status-codes';
import { LOGIN_FAILED, LOGIN_SUCCESS } from '../constants/AppConstants';
import { fetchUserJd } from './GetUserJd';
import history from '../history';
export function fetchToken(bodyjson) {
return (dispatch) => {
getLoginDetails(dispatch, bodyjson);
}
}
export function getLoginDetails(dispatch, bodyjson) {
generateToken(bodyjson)
.then((response) => {
if (response.status === 200)
dispatch(sendToken(response.payload))
else
dispatch(redirectUser(response.status));
})
}
export function sendToken(data) {
return {
type: LOGIN_SUCCESS,
data: data,
}
}
export function redirectUser(data) {
return {
type: LOGIN_FAILED,
data: data,
}
}
This is my login action.
import { FETCHING_JOBDESCRIPTION_SUCCESS, FETCHING_DATA_FAILED,FETCHING_JOBS } from '../constants/AppConstants';
import { getUserJobs } from '../APIs/GetUserJd';
import history from '../history';
export function fetchUserJd(token) {
console.log(token);
return (dispatch) => {
dispatch(fetchingJobDescription());
}
};
export function getUserJd(dispatch, token) {
getUserJobs(token)
.then((response) => {
if (response.status === 200)
dispatch(sendUserJd(response.payload))
else
dispatch(fetchFailure(response.status));
})
}
export function fetchFailure(data) {
return {
type: FETCHING_DATA_FAILED,
data: data,
}
}
export function sendUserJd(data) {
return {
type: FETCHING_JOBDESCRIPTION_SUCCESS,
data: data,
}
}
export function fetchingJobDescription() {
return {
type: FETCHING_JOBS
}
}
This is my 2nd action.
Now,
handleClick(event) {
event.preventDefault();
var bodyJson = {
"username": this.state.UserName,
"password": this.state.password
}
this.props.fetchToken(bodyJson);
}
This is from the container will get called on click of login button.
Now, so, how can I call the second action after a successful login request? I also want to dispatch both the actions.
I tried ->
export function getLoginDetails(dispatch, bodyjson) {
generateToken(bodyjson)
.then((response) => {
if (response.status === 200)
dispatch(sendToken(response.payload))
dispatch(fetchUserJd(dispatch))
else
dispatch(redirectUser(response.status));
})
}
But No luck . Can any one help me with this ?
When you have an if/else clause with more than one line you must use curly braces.
export function getLoginDetails(dispatch, bodyjson) {
generateToken(bodyjson)
.then((response) => {
if (response.status === 200) {
dispatch(sendToken(response.payload));
dispatch(fetchUserJd(dispatch));
} else {
dispatch(redirectUser(response.status));
}
})
}

Categories

Resources