I'm trying to make some middleware which refreshes my JWT if it's expired. I currently have this middleware:
import { isTokenExpired, refreshToken } from '../modules/auth/auth.service'
export const jwt = store => next => action => {
if (typeof action === 'function') {
console.log("Middleware triggered:", action);
var theState = store.getState();
if (theState.auth && theState.auth.authToken && isTokenExpired(theState.auth.authToken)) {
console.log('This is logging')
//this function is not being called
refreshToken(theState.auth.refreshToken);
}
}
return next(action)
}
I wish to call the refreshToken() function which will start the process, but so far even the console.log() inside the refreshToken is not calling:
export const refreshToken = (refreshToken) => dispatch => {
console.log('Not logging')
}
Also, inside the refreshToken function will be asynchronous while it refreshes the token. I was thinking of setting a "REFRESH_TOKEN_PENDING" type in the reducer, and once the response is received send "REFRESH_TOKEN_SUCCESS" to the reducer. Will this be possible with the middleware?
Thanks
You have to dispatch refreshToken(). If you do have access to dispatch() into jwt function you should call it like dispatch(refreshToken()).
Considering that I'm not sure how you are triggering the jwt action, I think this is a valid solution of how you can easily trigger middleware functions inside actions:
// ================
// Container file
// ================
import {connect} from "react-redux";
import { bindActionCreators } from "redux";
// ... other
function mapActionsToProps(dispatch) {
return bindActionCreators({
jwt: path_to_actions_file.jwt
}, dispatch)}
const mapStateToProps = (state) => ({
// ... your state properties
});
export default connect(mapStateToProps, mapActionsToProps)(YourComponent); // <== YourComponent has to be a React Component
// ==============
// Actions file
// ==============
export const setConvertTypeActive = store => dispatch => {
console.log('This is logging.');
dispatch(refreshToken("md5Token"));
}
export const refreshToken = refreshToken => dispatch => {
console.log('Should log now.');
}
And regarding your last question, yeah, you can create middlewares similar to refreshToken action for setting some statuses on reducer. Check the code below:
// ==============
// Actions file
// ==============
export const refreshToken= {
pending: () => ({
// "REFRESH_TOKEN_PENDING"
}),
success: (data) => ({
// "REFRESH_TOKEN_SUCCESS"
})
};
Related
I am trying to understand how React, Redux and Axios work together but I just hit a wall and I need some help ...
My problem is that inside the action there is a dispatch but after i return the dispatch it does not continue further.
It's most likely that I do not understand how this works so please try to explain in as much as possible details. Thanks in advance.
my combineReducer
import {combineReducers} from "redux";
import getAvailableDatesReducer from "./getAvailableDatesReducer";
export default combineReducers({
availableDates: getAvailableDatesReducer
});
my reducer
import {FETCH_AVAILABLE_DATES} from "../actions/types";
const initialState = {
availableDates: null
};
const getAvailableDatesReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_AVAILABLE_DATES:
return {...state, availableDates: action.availableDates};
default:
console.log('just default..');
return state;
}
}
export default getAvailableDatesReducer;
my action
export const fetchAvailableDates = (appointmentKey) => {
//return (dispatch) => {
axios.post('/app_dev.php/termin/getavailability/new', {
appointmentKey: appointmentKey
}).then((response) => {
console.log('response received...');
return (dispatch) => {
console.log('not hitting this...');
dispatch({type: FETCH_AVAILABLE_DATES, availableDates: response.data.availability});
};
}).catch(err => {
console.log(err);
});
//}
}
my component
import {fetchAvailableDates} from "../actions";
const Calendar = (props, appointmentKey) => {
useEffect(() => {
fetchAvailableDates(appointmentKey);
}, []);
const mapStateToProps = (state) => {
return {
availableDates: state.availableDates,
}
}
export default connect(mapStateToProps, {fetchAvailableDates})(Calendar);
my index.js file
import {Provider} from 'react-redux';
import thunk from 'redux-thunk';
import {applyMiddleware, compose, createStore} from "redux";
import reducers from './reducers';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducers, composeEnhancers(applyMiddleware(thunk)));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root')
);
First, create an action in your action file:
const fetchDatesAction = (response) => ({
type: FETCH_AVAILABLE_DATES,
availableDates: response.data.availability,
});
Then, update connect
export default connect(mapStateToProps, {fetchDatesAction})(Calendar);
Finally, Call api in useEffect, like this:
useEffect(() => {
axios
.post("/app_dev.php/termin/getavailability/new", {
appointmentKey: appointmentKey,
})
.then((response) => {
console.log("response received...");
return (dispatch) => {
console.log("not hitting this...");
props.fetchDatesAction();
};
})
.catch((err) => {
console.log(err);
});
}, []);
Thunk is a library which is responsible for handling side-effects in state management for redux.
Redux is a simple pure function which accepts state and action as an input and based on these two, it returns a new state. So its pretty simple and straight forward.
Now in certain scenarios like the one you have mentioned in your example, we need to perform some asynchronous actions which may not provide immediate result but a promise. In that case, we need to use a third party tool which is also called as enhancer.
That's the reason why you have added
const store = createStore(reducers, composeEnhancers(applyMiddleware(thunk)));
Now when asynchronous action is triggered, it goes to thunk. Thunk processes the request and then triggers one more action which again goes to reducer.
Now reducer being a pure function, does not distinguish between these sources of event and simply update the state based on action and its payload.
Hope this diagram helps you understand the concept.
https://miro.medium.com/max/1400/1*QERgzuzphdQz4e0fNs1CFQ.gif
I am trying to make use of thunk to make async calls to api, but I am still getting the error :
Unhandled Runtime Error: Actions must be plain objects. Use custom middleware for async actions.
This is my custom _app component:
// to connect redux with react
import { Provider } from 'react-redux';
import { createWrapper } from 'next-redux-wrapper';
import { createStore, applyMiddleware } from 'redux';
import reducers from '../redux/reducers';
import thunk from 'redux-thunk';
const store = createStore(reducers, applyMiddleware(thunk));
const AppComponent = ({ Component, pageProps }) => {
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
)
}
AppComponent.getInitialProps = async (appContext) => {
let pageProps = {};
if (appContext.Component.getInitialProps) {
pageProps = await appContext.Component.getInitialProps(appContext.ctx);
};
return { ...pageProps }
}
// returns a new instance of store everytime its called
const makeStore = () => store;
const wrapper = createWrapper(makeStore);
export default wrapper.withRedux(AppComponent);
And this is the landing page where I am dispatching the action creator:
import { connect } from 'react-redux';
import { fetchPosts } from '../redux/actions';
import { bindActionCreators } from 'redux';
import { useEffect } from 'react';
import Link from 'next/link';
const LandingPage = (props) => {
useEffect(() => {
props.fetchPosts();
}, [props]);
return <div>
<Link href="/">
<a>Home</a>
</Link>
</div>
}
LandingPage.getInitialProps = async ({ store }) => {
store.dispatch(await fetchPosts());
}
const mapDispatchToProps = (dispatch) => {
return {
// so that this can be called directly from client side
fetchPosts: bindActionCreators(fetchPosts, dispatch)
}
}
export default connect(null, mapDispatchToProps)(LandingPage);
Action:
import api from '../../api';
// returning a function and dispatching manually to make use of async await to fetch data
export const fetchPosts = async () => async (dispatch) => {
const response = await api.get('/posts');
dispatch({
type: 'FETCH_POSTS',
payload: response
});
};
Sadly the GitHub Next + Redux example NEXT+REDUX is really complicated for me to understand as I am trying redux for the first time with NextJS.
And every blog post has it's own way of doing it and nothing seems to be working.
I do not want it to make it any more complicated. I would really appreciate if anyone could help me why I am getting this error?
the problem is not with next.js when you calling this :
LandingPage.getInitialProps = async ({ store }) => {
store.dispatch(await fetchPosts());
}
fetchPosts here is a Promise and dispatch dispatch action must be a plain object so to solve this remove async word from it like this :
export const fetchPosts = () => async (dispatch) => {
const response = await api.get('/posts');
dispatch({
type: 'FETCH_POSTS',
payload: response
});
};
butt if you want to wait for api response instead you need call it in the component like this :
const App= ()=>{
const dispatch = useDispatch()
useEffect(() => {
const fetch = async()=>{
try{
const response = await api.get('/posts');
dispatch({
type: 'FETCH_POSTS',
payload: response
});
}
catch(error){
throw error
}
}
fetch()
}, []);
return ....
}
I want to navigate to App screen or Auth screen, depending on the isUser prop after fetching it from the server and updating the redux store.
My first component AuthLoading.js which looks like this:
const AuthLoading = (props) => {
const isUser = useSelector((state) => state.authReducer.isUserExists);
const dispatch = useDispatch();
const fetchData = async () => {
const token = await TokensHandler.getTokenFromDevice();
dispatch(isTokenExists(token));
props.navigation.navigate(isUser ? "App" : "Auth");
};
useEffect(() => {
fetchData();
}, []);
My authActions.js looks like this:
export const isTokenExists = (token) => {
return (dispatch) => {
return HttpClient.get(ApiConfig.IDENTITY_PORT, "api/identity", {
userId: token,
}).then((response) => {
console.log(response);
dispatch({
type: IS_USER_EXISTS,
payload: response,
});
});
};
};
My authReducer.js looks like this:
const authReducer = (state = initialState, action) => {
switch (action.type) {
case IS_USER_EXISTS:
return {
...state,
isUserExists: action.payload,
};
default:
return state;
}
};
And the store:
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import rootReducer from "../reducers";
const configureStore = () => {
return createStore(rootReducer, applyMiddleware(thunk));
};
export default configureStore;
Unfortunately, the code inside AuthLoading.js isn't asynchronous, and isn't waiting for the updated isUser value and running the next line without it.
I've tried to .then after dispatch which still doesn't work.
I've tried changing async states and still couldn't find the problem.
I have no idea how to fix this.
Thanks for the help.
You can use another useEffect hook that runs when isUser changes and navigate inside it.
This useEffect runs once the component mounts and every time isUser changes, so someCondition can be anything that determines whether the navigation should happen or not.
useEffect(() => {
if(someCondition) {
props.navigation.navigate(isUser ? "App" : "Auth");
}
}, [isUser]); // add all required dependencies
I used this guide to set up authentication with my api using jwt token authentication from Django rest framework. I can log in just fine. But currently I set up another endpoint to get the user info of the currently authorized user (determined by the token they send with the request).
It's dispatched like so (done some time after the user logs in)
fetchUser: () => dispatch(loadUser()),
My action to load username:
import { RSAA } from 'redux-api-middleware';
import withAuth from '../reducers'
export const USER_REQUEST = '##user/USER_REQUEST';
export const USER_SUCCESS = '##user/USER_SUCCESS';
export const USER_FAILURE = '##user/USER_FAILURE';
export const loadUser = () => ({
[RSAA]: {
endpoint: '/api/user/info/',
method: 'GET',
headers: withAuth({}),
types: [
USER_REQUEST, USER_SUCCESS, USER_FAILURE
]
}
});
Reducer:
import jwtDecode from 'jwt-decode'
import * as user from '../actions/user'
const initialState = {};
export default (state=initialState, action) => {
switch(action.type) {
case user.USER_SUCCESS:
return action.payload;
default:
return state
}
}
export const userInfo = (state) => state.user;
Reducer index.js:
export function withAuth(headers={}) {
return (state) => ({
...headers,
'Authorization': `Bearer ${accessToken(state)}`
})
}
export const userInfo = state => fromUser.userInfo(state.user);
But when I try to get the user info of the logged in user, I get an error..
TypeError: Cannot read property 'type' of undefined
Why is the action type undefined?
Note: I tagged this with thunk because my project does use thunk, but not for this bit of redux.
Edit: My middleware.js and my store.js.
The issue is that in store.js that thunk is applied before the redux-api-middleware middleware.
Just move it to after like this:
const store = createStore(
reducer, {},
compose(
applyMiddleware(apiMiddleware, thunk, routerMiddleware(history)),
window.devToolsExtension ? window.devToolsExtension() : f => f,
),
);
I'm using React.js with Redux, React-Router and React-Router-Redux.
I would like to to manually reroute (or call an action) when the store reaches a specific state. How do I listen if the store has a specific state? Where do I reroute or call the action?
If these calls are made async then using an async action creator and the promise would handle this quite well.
// Action creator
function doTheStuff(someData, router) {
return dispatch => {
dispatch(somePendingAction());
return myRequestLib.post('/the-endpoint/', someData)
.then((response) => {
if (response.isTransactionDone) {
dispatch(transactionDoneAction(response));
router.transitionTo('/theUrl');
}
});
}
}
I wouldn't put this into the store since I believe the stores only job is to contain/handle the data.
To initiate a page change when a series of other actions have completed and been persisted to the store, dispatch the following redux-thunk. The key here is that you have a chain of thunks which return promises, and then once they all complete, you can update the router with another action.
In a component:
import React from 'react';
import { push } from 'react-router-redux';
import { connect } from 'react-redux';
import { doStuff, doMoreStuff } from './actions';
class SomeComponent extends React.Component {
...
onClick = e => {
const { doStuff, doMoreStuff, push } = this.props; // these are already wrapped by dispatch because we use connect
...
doStuff()
.then(r => doMoreStuff())
.then(r => push('/newPath')) // if actions succeeded, change route
.catch(e => console.warn('doStuff() or doMoreStuff failed'));
...
}
...
}
export default connect(null, { doStuff, doMoreStuff, push })(SomeComponent);
some actions in ./actions.js:
export function doStuff() {
return (dispatch, getState) => {
return API.getSomeInfo()
.then(info => dispatch({ type: 'GET_SOME_INFO', payload: info }))
}
}
export function doMoreStuff() {
return (dispatch, getState) => {
return API.getSomeMoreInfo()
.then(info => dispatch({ type: 'GET_SOME_MORE_INFO', payload: info }));
}
}
Alternatively, you could make a single promise that looks like this, where the redirect happens:
export function doAllStuff() {
return dispatch => {
return Promise.all(doStuff(), doAllStuff())
.then(results => dispatch(push('/newPath')))
.catch(e => console.warn('Not all actions were successful'))
}
}
the component code that intiates it would just be
onClick = e => {
...
doAllStuff(); // Since we caught the promise in the thunk, this will always resolve
...
}