import axios from "axios";
class ApiCaller {
CallApi = (SERVICE_URL, data) => {
return new Promise((resolve, reject) => {
axios.post(SERVICE_URL, data).then(function (response) {
var data = response.data
if (data.status) {
data = data.data
resolve(data)
} else {
if (data.isExpired != undefined && data.isExpired) {
console.log('here in expired')
}
reject(data.message)
}
}).catch(function (error) {
console.log(error);
reject(error)
});
})
}
}
export default new ApiCaller();
// export default connect()(new ApiCaller());
here i am calling all the webservices throughout application
and data.isExpired is a boolean where server tells me that provided jwt token is expired and i need to remove user information from redux and navigate to login again i have used this method almost more than 50 places and i cant afford it to change on all those places
how can i dispatch user logout from here ?
import { logoutUser } from "app/redux/actions/UserActions";
const mapStateToProps = state => ({
logoutUser: PropTypes.func.isRequired
});
export default withRouter(connect(mapStateToProps, { logoutUser })(ApiCaller));
this might be the solution , to do that i need to extend component and it will break ApiCaller implementation on other places
i have tried functional components too but it isnt helping, i need to logout from my ApiCaller so i don't need to handle jwt expire exception on every single view
i am new to React Redux
anyone please
I think this is a pretty good example of where you can use Redux Thunks.
// this is an action that can be dispatched to call the api
function callApi() {
return (dispatch) => {
apiCaller.CallApi()
.then(() => {
dispatch(callApiSuccess()); // success action defined elsewhere
})
.catch(() => {
dispatch(callApiError()); // error action defined elsewhere
});
}
}
Related
I am creating a website using the MERN stack however I don't know how to get data to the frontend that needs authorization from the backend and I tried to console log the problem and it shows me the HTML of my login page even though I am logged in. Any will be appreciated thank you so much.
My backend code:
router.get("/questions", ensureAuthenticated, (req, res) => {
math = Math.floor(Math.random() * 3) + 1;
Security.findOne({
user: req.user.id
}, (err, user) => {
if (err) {
console.log(err);
}
if (math === 1) {
res.send({
question: user.firstQuestion
});
} else if (math === 2) {
res.send({
question: user.secondQuestion
});
} else {
res.send({
question: user.thirdQuestion
});
}
});
});
My Frontend code:
class QuestionForm extends Component {
constructor(props) {
super(props);
this.state = {
data: ''
}
}
componentDidMount() {
axios.get("http://localhost:5000/users/questions")
.then((res) => {
this.setState({
data: res.data
});
}).catch((err) => console.log(err));
}
render() {
return <h1 > {
this.state.data
} < /h1>
}
}
a lot of changes should be made.
you never want to use the port in your Axios request
add to you package.json an proxy attribute
"proxy": "http://localhost:5000"
then you can change your axios get to
axios.get("/users/questions")
best practice when using autorization is to add to axios interceptors
follow this thread :
How can you use axios interceptors?
and also here is an example for using authorization with JWT token
const tokenHandler = axios.create();
tokenHandler.interceptors.request.use(config => {
const token = localStorage.getItem("token");
if (token) {
config.headers["Authorization"] = token;
}
return config;
});
export default tokenHandler;
let's say you create a token on the login page and store it inside your local storage.
now you can import the token handler and your request should look something like this :
import {tokenHandler} from '<TOKEN HANDLER PATH>'
..
..
tokenHandler.get("/users/questions")
.then((res)=>{
this.setState({data:res.data});
}).catch((err)=>console.log(err));
i want to get data (array) from /{url} and i tried this code
// Fetch the list on first mount
componentDidMount() {
this.getList();
}
// Retrieves the list of items from the Express app
getList = () => {
fetch('/main')
.then(res => res.json())
.then(list => this.setState({ list }))
}
this is working fine but then i decided to switch to axios and tried literally same code
// Fetch the list on first mount
componentDidMount() {
this.getList();
}
// Retrieves the list of items from the Express app
getList = () => {
axios.get('/main')
.then(res=> res.json())
.then(list => this.setState({list}))
}
but it did not worked and gave me error in this line: .then(res=> res.json())
so i do not know what is problem if anyone knows the clue i will be glad if you tell me
// Fetch the list on first mount
componentDidMount() {
this.getList();
}
// Retrieves the list of items from the Express app
getList = () => {
axios.get('/main')
.then(res=> res.data)
.then(list => this.setState({list}))
.catch(error => this.setState({error: error.message}))
}
It is because axios has different response, instead of res.json() return data already like : return res.data or pass it to state directly something like
getList = () => {
axios.get('/main')
.then(res=> this.setState({list: res.data}))
i would recommend some changes in your design, as im using axios successfully in many projects, its not a requirement but it helps and is working very reliable:
Create a service like api.js
import axios from 'axios';
export default axios.create({
baseURL: `http://my.api.url/`
});
Then you can use it like this
import API from '../Services/api'; // this is your service you created before
LoadStats = async event => {
try {
var response = await API.get('api/targetroute');
if (response.data != null) {
this.setState({
stats: {
mydata: response.data
}
});
console.log('Receiving result: '+JSON.stringify(response.data));
}
} catch (error) {
console.log('ERROR: '+error)
}
}
I have followed some tutorial to build an authentication in React, Node and Redux. The basic functionality works, however, when I keep the application open and then get back to it (when the session expired), I get this error message:
Unhandled Rejection (TypeError): Cannot read property 'uploadURL' of undefined
Then I refresh the page and I get this error message:
TypeError: Cannot read property 'push' of undefined
Then, I refresh the page again and I am finally redirected on the homepage. The first 2 errors are a problem I am not sure how to get rid off them.
This is what my code looks like:
...
class Event extends Component {
constructor() {
super();
...
}
UNSAFE_componentWillMount() {
// I thought this if-block will redirect the user if the session is expired
if(!this.props.auth.isAuthenticated) {
console.log('unauthorized');
this.props.history.push('/');
}
this.uppy2 = new Uppy({ id: 'uppy2', autoProceed: true, debug: true })
.use(Tus, { endpoint: 'https://master.tus.io/files/' })
.on('complete', (result) => {
console.log(`Upload complete! We’ve uploaded these files: ${result.successful[0].uploadURL}`);
});
}
...
}
Event.propTypes = {
registerUser: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth,
errors: state.errors
});
export default connect(mapStateToProps,{ registerUser })(withRouter(Event))
Here's the Redux code (I am beginner with the MERN stack):
import axios from 'axios';
import { GET_ERRORS, SET_CURRENT_USER } from './types'; // we list here the actions we'll use
import setAuthToken from '../../setAuthToken';
import jwt_decode from 'jwt-decode';
export const registerUser = (user, history) => dispatch => {
axios.post('/api/users/register', user)
.then(res => history.push('/login'))
.catch(err => {
dispatch({
type: GET_ERRORS,
payload: err.response.data
});
});
}
export const loginUser = (user) => dispatch => {
axios.post('/api/users/login', user)
.then(res => {
//console.log(res.data);
const { token } = res.data;
localStorage.setItem('jwtToken', token);
setAuthToken(token);
const decoded = jwt_decode(token);
dispatch(setCurrentUser(decoded));
})
.catch(err => {
dispatch({
type: GET_ERRORS,
payload: err.response.data
});
});
}
export const setCurrentUser = decoded => {
return {
type: SET_CURRENT_USER,
payload: decoded
}
}
export const logoutUser = (history) => dispatch => {
localStorage.removeItem('jwtToken');
setAuthToken(false);
dispatch(setCurrentUser({}));
history.push('/login');
}
How do I prevent the errors happening when the session is expired?
Thank you in advance!
ComponentWillMount won't be called if the page is loaded before the session expires. I suspect the first error is caused by some missing data because the request with the expired token failed. You would need to make sure the 401 or 403 error is handled and clear out the Redux state so the login page is shown when that happens.
I am not sure with this part !this.props.auth.isAuthenticated. Did you use mapDispatchToProps and connect for redux? You need to do this in your Event class to reach your reducer.
Also the thing that you can do is, before rendering your jsx code, declare a variable like let redirect = null and if !this.props.auth.isAuthenticated is correct, set this redirect variable to redirect = <Redirect to="/" /> (If you use browser routing!) and use this variable like this,
render() {
return (
{redirect}
)
}
I'm brand new to this stack. I've seen quite a few other questions about this and have read the Thunk documentation but I can't stitch this together.
When I run the code below I get the error "Actions must be plain objects, use custom middleware for async actions" which is exactly the problem I'm trying to solve with Thunk.
My action looks like this:
src/actions/recipes.js
// this calls the API
function fetchApiGetRecipes() {
return fetch('https://mywebsite.com/endpoint/', {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + idToken
}
}).then((json) => {
dispatch({
type: 'RECIPES_REPLACE',
data: json
})
});
}
// this is passed into my container to use to refresh the recipe list
export function getRecipes() {
if (Firebase === null) return () => new Promise(resolve => resolve());
if (Firebase.auth().currentUser !== null) {
Firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
// console.log(idToken);
return dispatch => new Promise(resolve => fetchApiGetRecipes(idToken) )
}).catch(function(error) {
// Handle error
});
} else {
console.log("Null user");
}
}
What is the correct syntax to use Thunk here and fix the error I'm getting when the app starts up?
EDIT: I create the store like this:
import { createStore, applyMiddleware, compose } from 'redux';
import { persistStore, persistCombineReducers } from 'redux-persist';
import storage from 'redux-persist/es/storage'; // default: localStorage if web, AsyncStorage if react-native
import thunk from 'redux-thunk';
import reducers from '../reducers';
// Redux Persist config
const config = {
key: 'root',
storage,
blacklist: ['status'],
};
const reducer = persistCombineReducers(config, reducers);
const middleware = [thunk];
const configureStore = () => {
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
compose(applyMiddleware(...middleware)),
);
const persistor = persistStore(
store,
null,
() => { store.getState(); },
);
return { persistor, store };
};
export default configureStore;
Your getRecipes function doesn't return a function in the if (Firebase.auth().currentUser !== null) clause.
You need to return a function where you are just doing
Firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
// console.log(idToken);
return dispatch => new Promise(resolve => fetchApiGetRecipes(idToken) )
}).catch(function(error) {
// Handle error
});
The dispatch function (I assume is the intended one to return) is being returned in the then clause of the promise. That doesn't return the dispatch function to the outer method getRecipies. Hence the error
probably you forgot pass dispatch to func args?
// you use dispatch in this func
function fetchApiGetRecipes() {...}
// you forget pass
return dispatch => new Promise(resolve => fetchApiGetRecipes(idToken) )
Creating my store with Thunk middleware
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
const store = createStore(
reducer,
initialState,
applyMiddleware(thunk)
);
And creating my action which calls a promise
export function getArticle(url) {
return function (dispatch) {
fetchArticle(url).then(
article => dispatch(setArticle(article)),
);
};
}
function fetchArticle(url) {
return new Promise((resolve, reject) => {
request
.get(url)
.end( (err, res) => {
if (err || !res.ok) {
reject('Oh no! error');
} else {
resolve(res.body);
}
});
})
}
export function setArticle(article){
return {
type: constants.SET_ARTICLE,
article
}
}
In my article component I am calling dispatch on componentDidMount()
componentDidMount(){
this.props.dispatch(
getArticle('http://api.example.com/')
);
}
But getting error: "Actions must be plain objects. Use custom middleware for async actions.".
What is wrong with this setup? I have tried calling compose(applyMiddleware(thunk)) but to no avail.
Your code looks ok except that it's missing how to handle errors (promise rejection). Your api might be returning errors and you're not handling it which could result to that error message.
Try adding
export function getArticle(url) {
return function (dispatch) {
fetchArticle(url)
.then(article => dispatch(setArticle(article)))
.catch(err => dispatch({ type: 'SOME_ERROR', err }));
};
}
Change
return function (dispatch) {
fetchArticle(url).then(
article => dispatch(setArticle(article)),
);
};
To
return function (dispatch) {
return fetchArticle(url).then(
article => dispatch(setArticle(article)),
);
};
Try the following:
export function getArticle(url) {
return fetchArticle(url).then(
article => dispatch(setArticle(article)),
);
}
In Redux every action must return an object.
You may use:
export const getArticle= url=> dispatch => {
fetchArticle(url)
.then(res =>
dispatch({ type: SET_ARTICLE, payload: res.data })
)
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
};