I'm calling an action in componentDidMount as follows
componentDidMount() {
const { allowedEvcCards} = this.props;
allowedEvcCards(id);
}
With these actions i'm doing API calls and receiving some data as the response. I have set the data to a state with my reducer. I want to do some logic in the componentDidMount it self with the data received in the response.
For example in my reducer i'm doing this
case ALLOWED_EVC_SUCCESS:
return {
...state,
allowedEvc: action.data
}
And in componentDidMount i want to use allowedEvc . But it returns undefined as the action call is not complete at the time.
My action
// Get allowed Evc cards
export const ALLOWED_EVC_LOADING = 'ALLOWED_EVC_LOADING';
export const ALLOWED_EVC_SUCCESS = 'ALLOWED_EVC_SUCCESS';
export function allowedEvcCardsLoading() {
return {
type: ALLOWED_EVC_LOADING
}
}
export function allowedEvcCardsSuccess(data) {
return {
type: ALLOWED_EVC_SUCCESS,
data
}
}
export function allowedEvcCards(id) {
return dispatch => {
dispatch(allowedEvcCardsLoading());
axios.get(`${API_URL}/****/****/${id}/*****`, {
headers: {
// 'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
})
.then(res => {
console.log("Allowed EVC response ", res.data);
if (res.data.success === true) {
dispatch(allowedEvcCardsSuccess(res.data));
} else {
console.log("error");
// alert("error");
}
})
}
}
Unfortunately, componentDidMount is only called when a component is mounted. Unless, you unmount it you can't use that property. However, you could use componentDidUpdate since it is called as soon as it receives props.
Read more on this lifecycle method.
Edit: maybe you could try returning the axios promise along with the data and use it.
// Component
async componentDidMount() {
const { allowedEvcCards} = this.props;
const data = await allowedEvcCards(id);
// ... do something with data
}
// Action
export function allowedEvcCards(id) {
return dispatch => {
dispatch(allowedEvcCardsLoading());
return axios.get(`${API_URL}/****/****/${id}/*****`, {
headers: {
// 'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
})
.then(res => {
console.log("Allowed EVC response ", res.data);
if (res.data.success === true) {
dispatch(allowedEvcCardsSuccess(res.data));
return res.data;
} else {
console.log("error");
// alert("error");
}
})
}
}
Related
I am using Next.js. I have created an Axios interceptor where a rejected Promise will be returned. But where there is a server-specific error that I need. Next.js is showing the error in the application like this.
And there is the code of the Axios interceptor and instance.
import axios from "axios";
import store from "../redux/store";
import getConfig from 'next/config';
const { publicRuntimeConfig } = getConfig();
let token = "";
if (typeof window !== 'undefined') {
const item = localStorage.getItem('key')
token = item;
}
const axiosInstance = axios.create({
baseURL: publicRuntimeConfig.backendURL,
headers: {
Authorization: token ? `Bearer ${token}` : "",
},
});
axiosInstance.interceptors.request.use(
function (config) {
const { auth } = store.getState();
if (auth.token) {
config.headers.Authorization = `Bearer ${auth.token}`;
}
return config;
},
function (error) {
return Promise.reject(error);
}
);
axiosInstance.interceptors.response.use(
(res) => {
console.log(res)
return res;
},
(error) => {
console.log(error)
return Promise.reject(error);
}
);
export default axiosInstance;
Also, I am using redux and there is the action.
import axios from "../../api/axios";
import { authConstants } from "../types";
export const login = (data) => {
return async (dispatch) => {
try {
dispatch({
type: authConstants.LOGIN_REQUEST,
});
const res = axios.post("/user/login", data);
if (res.status === 200) {
dispatch({
type: authConstants.LOGIN_SUCCESS,
payload: res.data,
});
}
} catch (error) {
console.log(error, authConstants);
dispatch({
type: authConstants.LOGIN_FAILURE,
payload: { error: error.response?.data?.error },
});
}
};
};
Your problem is here...
const res = axios.post("/user/login", data);
You're missing await to wait for the response
const res = await axios.post("/user/login", data);
This fixes two things...
Your code now waits for the response and res.status on the next line will be defined
Any errors thrown by Axios (which surface as rejected promises) will trigger your catch block. Without the await this does not happen and any eventual promise failure bubbles up to the top-level Next.js error handler, resulting in the popup in your screenshot.
I want to try and use react to fetch data efficiently using useEffect appropriately.
Currently, data fetching is happening constantly, instead of just once as is needed, and changing when there is a input to the date period (calling different data).
The component is like this,
export default function Analytics() {
const {
sentimentData,
expressionsData,
overall,
handleChange,
startDate,
endDate,
sentimentStatistical,
} = useAnalytics();
return (
UseAnalytics is another component specifically for fetching data, basically just a series of fetches.
i.e.,
export default function useAnalytics() {
....
const { data: sentimentData } = useSWR(
`dashboard/sentiment/get-sentiment-timefilter?startTime=${startDate}&endTime=${endDate}`,
fetchSentiment
);
....
return {
sentimentData,
expressionsData,
overall,
handleChange,
setDateRange,
sentimentStatistical,
startDate,
endDate,
};
}
Thanks in advance,
The apirequest is like this,
export async function apiRequest(path, method = "GET", data) {
const accessToken = firebase.auth().currentUser
? await firebase.auth().currentUser.getIdToken()
: undefined;
//this is a workaround due to the backend responses not being built for this util.
if (path == "dashboard/get-settings") {
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
if (response.error === "error") {
throw new CustomError(response.code, response.messages);
} else {
return response;
}
});
}
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
if (response.status === "error") {
// Automatically signout user if accessToken is no longer valid
if (response.code === "auth/invalid-user-token") {
firebase.auth().signOut();
}
throw new CustomError(response.code, response.message);
} else {
return response.data;
}
});
}
I think using useEffect here is the right approach. i.e.,
useEffect(()=>{
// this callback function gets called when there is some change in the
// state variable (present in the dependency array)
},[state variable])
I'm confused about how to update the constants properly, something like this seems like one approach, but not sure about how I can use useEffect to update these variables properly, or if I should be doing this inside of useAnalytics?
i.e.,
const [analytics, setAnalytics] = useState({
sentimentData: {},
expressionsData: {},
overall: {},
handleChange: () => {},
startDate: '',
endDate: '',
sentimentStatistical:{},
});
useEffect(()=>{
// this callback function gets called when there is some change in the
// state variable (present in the dependency array)
},[state variable])
const {
sentimentData,
expressionsData,
overall,
handleChange,
startDate,
endDate,
sentimentStatistical,
} = useAnalytics();
Realised SWR is a hook, need to use SWR documentation :P
You have to store the requested information in states inside your custom hook. Then you could consume this hook wherever you want. This should work.
Define custom hook
const useAnalitycs = () => {
const [analytics, setAnalytics] = useState({
sentimentData: {},
expressionsData: {},
overall: {},
startDate: '',
endDate: '',
sentimentStatistical:{},
});
const handleChange = () => {
/* */
};
useEffect(() => {
const fetchData = async () => {
// const response = await apiCall();
// setAnalytics(...)
};
fetchData();
}, []); // called once
return {
...analytics,
handleChange
};
};
Consume useAnalytics hook
const ComponentConsumerA = () => {
/*
const { state/methods you need } = useAnalytics()
...
*/
};
const ComponentConsumerB = () => {
/*
const { state/methods you need } = useAnalytics()
...
*/
};
I have a separate fetch request function that logins user and saves auth token to localStorage, then my data request fetch should be send with that saved token bearer, but data fetch doesn't wait for token and receives Unauthorized access code.
My data request fetch looks like this :
// to check for fetch err
function findErr(response) {
try {
if (response.status >= 200 && response.status <= 299) {
return response.json();
} else if (response.status === 401) {
throw Error(response.statusText);
} else if (!response.ok) {
throw Error(response.statusText);
} else {
if (response.ok) {
return response.data;
}
}
} catch (error) {
console.log("caught error: ", error);
}
}
const token = JSON.parse(localStorage.getItem("token"));
// actual fetch request
export async function getData() {
const url = `${URL}/data`;
var obj = {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: "Bearer " + `${token}`,
},
};
const data = await fetch(url, obj)
.then((response) => findErr(response))
.then((result) => {
return result.data;
});
return data;
}
My fetch requests are in a different js file, I'm importing them in my components like this:
import React, { useState, useEffect } from "react";
function getInfo() {
const [info, setInfo] = useState()
const importGetDataFunc = async () => {
const data = await getData();
setInfo(data);
};
useEffect(() => {
importGetDataFunc();
}, []);
return (
<div>
</div>
)
}
export default getInfo
Now when I go to the getInfo component after login at first fetch request returns 401, but after I refresh the page fetch request goes with token bearer and data gets returned. My problem is that I don't know how to make getData() fetch request to wait until it gets token from localStorage or retry fetch request on 401 code. I tried to implement if statement like this
useEffect(() => {
if(token){
importGetDataFunc();
}
}, []);
where useEffect would check if token is in localStorage and only then fire fetch request, but it didn't work. Any help on how I can handle this would be greatly appreciated.
You are close. You need to add token as a dependency to your useEffect. Also, you need to move your token fetching logic into your component.
Something like this should work:
import React, { useState, useEffect } from "react";
function getInfo() {
const [info, setInfo] = useState()
const token = JSON.parse(localStorage.getItem("token"));
const importGetDataFunc = async () => {
const data = await getData();
setInfo(data);
};
useEffect(() => {
if(token) {
importGetDataFunc(token);
}
}, [token]);
return (
<div>
</div>
)
}
export default getInfo
You can also modify your importGetDataFunc to receive the token as a parameter.
const importGetDataFunc = async (token) => {
const data = await getData(token);
setInfo(data);
};
export async function getData(token) {
const url = `${URL}/data`;
var obj = {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: "Bearer " + `${token}`,
},
};
const data = await fetch(url, obj)
.then((response) => findErr(response))
.then((result) => {
return result.data;
});
return data;
}
What actually helped me is to make a function to check for a token inside get fetch request, like this:
export const findToken = () => {
const token =localStorage.getItem("token")
return token;
};
export async function getData(token) {
const url = `${URL}/data`;
var obj = {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: "Bearer " + `${findToken()}`,
},
};
const data = await fetch(url, obj)
.then((response) => findErr(response))
.then((result) => {
return result.data;
});
return data;
}
I am working on refresh token. I faced some problems with context api store. Store gives me old value.
Please look at refreshToken method there is comment explaining error. I dont't understant why if I console.log(store) React return me old value not new value.
Repeating because Stackoverlow ask me to more describe text
I am working on refresh token. I faced some problems with context api store. Store gives me old value.
Please look at refreshToken method there is comment explaining error. I dont't understant why if I console.log(store) React return me old value not new value.
import React, { useState, createContext, useEffect } from 'react';
import {MainUrl, ApiUrl} from '../config';
export const StoreContext = createContext();
export const StoreProvider = props => {
const getToken = () => localStorage.getItem("token");
const initState = () => ({
token: getToken(),
isAuth: false,
userRole: "",
userName: "",
userGroupId: null,
mainUrl: MainUrl,
apiUrl: ApiUrl,
})
const [store, setStore] = useState(initState());
const getUserInfo = async () => {
if (getToken()) {
try {
const apiConfig = {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${store.token}`,
},
};
const response = await fetch(`${store.apiUrl}get-me`, apiConfig);
const responseJson = await response.json();
if (response.ok) {
// Update Context API
setStore({
...store,
userRole: responseJson.role,
userName: responseJson.name,
userGroupId: responseJson.group_id,
isAuth: true,
})
} else if(response.status === 401) {
await refreshToken();
// Here I want call this function with refreshed token but React gives old token, although I updated token in refreshToken method
getUserInfo();
} else {
throw new Error(`Возникла ошибка во получения информации об пользователе. Ошибка сервера: ${responseJson.error}`);
}
} catch (error) {
console.log(error);
}
}
}
const logout = async (routerHistory) => {
try {
const apiConfig = {
method: "GET",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": `Bearer ${store.token}`,
},
};
const response = await fetch(`${store.apiUrl}logout`, apiConfig);
const responseJson = await response.json();
if (response.ok) {
// Remove token from localstorage
localStorage.removeItem("token");
// Reset Context API store
setStore(initState());
// Redirect to login page
routerHistory.push("/");
} else if(response.status === 401) {
await refreshToken();
logout(routerHistory);
} else {
throw new Error(`Возникла ошибка во время выхода. Ошибка сервера: ${responseJson.error}`);
}
} catch (error) {
console.log(error);
}
}
const refreshToken = async () => {
try {
const apiConfig = {
method: "GET",
headers: {
"Accept": "application/json",
"Authorization": `Bearer ${store.token}`,
},
};
const response = await fetch(`${store.mainUrl}refresh-token`, apiConfig);
const responseJson = await response.json();
if (response.ok) {
// Update token in local storage
localStorage.setItem("token", responseJson.token);
// Update Context API
setStore({
...store,
userRole: 'some new role',
token: responseJson.token,
})
// Here I expect that userRole and token are changed but if I console.log(store) I get old token and null for userRole
console.log(store);
} else {
throw new Error(`Возникла ошибка во время обновления токена`);
}
} catch (error) {
throw error;
}
}
useEffect(() => {
// Get user info
getUserInfo();
}, [])
return(
<StoreContext.Provider value={[store, setStore, logout, getUserInfo]}>
{props.children}
</StoreContext.Provider>
);
}
Your setStore() function from useState() hook is an async function, hence you don't get the updated value from your console.log() call (just after the setStore()) immediately.
Also, there's no facility of providing a callback as the second argument to setStore() function, where you can log the updated state.
However, you can move your console.log(store) call inside a useEffect() hook, which triggers after every state update and ensures that you'll get the updated state.
useEffect(() => {
console.log(store);
})
So, as far as the title of your question "React Context API not updating store" is concerned, it's actually updating. It's just you are logging it before it is updated.
Hope this helps!
During implementing login feature with React, Redux, isomorphic-fetch, ES6 Babel.
Questions
I do not know how to properly combine promises after the checkstatus promise in order to get parsed JSON data from my server.
what am I doing wrong here?
also, do I need to replace isomorphic-fetch package with other more convenient one?
any suggestion for other package is welcome!
loginAction.js
import * as API from '../middleware/api';
import * as ActionTypes from '../actionTypes/authActionTypes';
import 'isomorphic-fetch';
function encodeCredentials(id, pwd) {
return btoa(`${id}{GS}${pwd}`);
}
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
response;
} else {
const error = new Error(response.statusText);
error.response = response;
throw error;
}
}
function parseJSON(response) {
return response.json();
}
export function loginFailure(error) {
return { error, type: ActionTypes.LOGIN_FAILURE };
}
export function loginSuccess(response) {
return dispatch => {
dispatch({ response, type: ActionTypes.LOGIN_SUCCESS });
};
}
export function loginRequest(id, pwd) {
return {
type: ActionTypes.LOGIN_REQUEST,
command: 'login',
lang: 'en',
str: encodeCredentials(id, pwd),
ip: '',
device_id: '',
install_ver: '',
};
}
export function login(id, pwd) {
const credentials = loginRequest(id, pwd);
return dispatch => {
fetch(`${API.ROOT_PATH}${API.END_POINT.LOGIN}`, {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(credentials),
})
.then(checkStatus)
.then(parseJSON)
.then(data => {
console.log(`parsed data ${data}`);
dispatch(loginSuccess(data));
})
.catch(error => {
console.log(`request failed ${error}`);
});
};
}
In my projects usually, I have a helper function fetchJSON that does all utility logic, such as JSON parsing and status check.
Here it is:
import fetch from 'isomorphic-fetch';
function checkStatus(response) {
if(response.ok) {
return response;
} else {
const error = new Error(response.statusText);
error.response = response;
throw error;
}
}
function parseJSON(response) {
return response.json();
}
export default function enhancedFetch(url, options) {
options.headers = Object.assign({
'Accept': 'application/json',
'Content-Type': 'application/json'
}, options.headers);
if(typeof options.body !== 'string') {
options.body = JSON.stringify(options.body);
}
return fetch(url, options)
.then(checkStatus)
.then(parseJSON);
}
Then you can use it in actions:
import fetchJSON from '../utils/fetchJSON'; // this is the enhanced method from utilities
export function login(id, pwd) {
const credentials = loginRequest(id, pwd);
return dispatch => {
fetchJSON(`${API.ROOT_PATH}${API.END_POINT.LOGIN}`, {
method: 'post',
body: credentials
}).then(data => {
console.log(`parsed data ${data}`);
dispatch(loginSuccess(data));
}).catch(error => {
console.log(`request failed ${error}`);
});
};
}
It helps you to keep actions code clean from some boilerplate code. In big projects with tons of similar fetch calls it is a really must-have thing.
You're doing it right, you just forgot return in checkstatus; you should return the response such that the next promise in the chain can consume it.
Also, it seems that checkstatus is synchronous operation, so it's no need to chain it by .then (although, it's OK if you like it that way), you can write:
fetch(...)
.then(response=>{
checkStatus(response)
return response.json()
})
.then(data=>{
dispatch(loginSuccess(data))
})
.catch(...)
I see no reason to get rid of isomorphic-fetch for now - it seems that it does its job.