Can I access state inside a createAsyncThunk w/axios with redux toolkit? - reactjs

I'm fairly new to redux toolkit so I'm still having a few issues with it!
As per the code below, I'm trying to access state (loginDetails.username and loginDetails.password) inside my createAsyncThunk. I'm obviously doing something wrong here - I've tried writing the createAsyncThunk function inside a different file, attempting to access the state inside that file and then importing the function, but either way it's failing.
// Import: Packages
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
// AsyncThunk: getUserDetails
export const getUserDetails = createAsyncThunk(
"userDetails/getUserDetails",
async () => {
try {
const apiUrl = process.env.REACT_APP_URL;
var config = {
method: "get",
url: `${apiUrl}/claimSet?UserName=${state.loginDetails.username}&Password=${state.loginDetails.password}`,
headers: {
accept: "application/json",
},
};
const response = await axios(config);
const data = await response.data;
return data;
} catch (error) {
console.log(error);
}
}
);
// Slice: userDetailsSlice
export const userDetailsSlice = createSlice({
name: "userDetails",
initialState: {
loginDetails: {
username: "",
password: "",
},
details: [],
status: null,
},
reducers: {
addUsername: (state, { payload }) => {
state.loginDetails.username = payload;
},
addPassword: (state, { payload }) => {
state.loginDetails.password = payload;
},
},
extraReducers: {
[getUserDetails.pending]: (state, action) => {
state.status = "loading";
},
[getUserDetails.fulfilled]: (state, { payload }) => {
state.details = payload;
state.status = "success";
},
[getUserDetails.rejected]: (state, action) => {
state.status = "failed";
},
},
});
// Actions: addUsername, addPassword
export const { addUsername, addPassword } = userDetailsSlice.actions;
// Reducer: userDetailsSlice.reducer
export default userDetailsSlice.reducer;
The code in the config url ${state.loginDetails.username}, etc. is just one of many failed attempts to get hold of the state. I understand that part of the issue is that the createAsyncThunk is declared before the state/slide is below, but I still can't seem to find a way around it.
Any help would be really appreciated!
Thanks in advance <3

The async function consumes a "payload" argument, and secondly a thunkAPI object that contains a getState method.
payloadCreator
thunkAPI: an object containing all of the parameters that are normally
passed to a Redux thunk function, as well as additional options:
dispatch: the Redux store dispatch method
getState: the Redux store getState method
extra: the "extra argument" given to the thunk middleware on setup, if available
requestId: a unique string ID value that was automatically generated to identify this request sequence
signal: an AbortController.signal object that may be used to see if another part of the app logic has marked this request as needing
cancelation.
rejectWithValue: rejectWithValue is a utility function that you can return in your action creator to return a rejected response with a
defined payload. It will pass whatever value you give it and return it
in the payload of the rejected action.
// AsyncThunk: getUserDetails
export const getUserDetails = createAsyncThunk(
"userDetails/getUserDetails",
async (arg, { getState }) => { // <-- destructure getState method
const state = getState(); // <-- invoke and access state object
try {
const apiUrl = process.env.REACT_APP_URL;
var config = {
method: "get",
url: `${apiUrl}/claimSet?UserName=${state.loginDetails.username}&Password=${state.loginDetails.password}`,
headers: {
accept: "application/json",
},
};
const response = await axios(config);
const data = await response.data;
return data;
} catch (error) {
console.log(error);
}
}
);

Related

How to only fetch the required data according to a boolean value in Redux Toolkit?

i have a query regarding redux toolkit. I want to get the list of array on the click of a checkbox. the array objects has a field called "verified " which is either true or false. According to that, Onclick of the check box i'd like to get all the objects which have the verified value set to true.
Also there isn't any end point called "verified " Which I can pass in the url to fetch a list of only verified array of objects which is want i want to achieve.
array structure looks like this =>
{_id(pin):"61bc940989b38d9bc53832e3"
venture(pin):"xyz"
rating(pin):2.3
promotion(pin):0
verified(pin):false
},
{_id(pin):"sjkdfkla38d9bc53832e3"
venture(pin):"xyz"
rating(pin):2.3
promotion(pin):0
verified(pin):true
},
{_id(pin):"blahbdsfasd3"
venture(pin):"xyz"
rating(pin):2.3
promotion(pin):0
verified(pin):true
},
below is my reducer and action.
Action = >
// get all Media Verified Cards
export const getVerifiedCards = createAsyncThunk(
'verifiedmedia/getall',
async () => {
try {
const header = {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${Token_Media_Seller}`,
ClientId: `Bearer ${ClientId}`
}
}
const response = await axiosInstance.get(
`/media/?token=${Token_Media_Seller}&client_id=${ClientId}`,
header
)
console.log(response)
return response.data
} catch (error) {
throw error
}
}
)
Reducer=>
import { createSlice } from '#reduxjs/toolkit'
import { getMediaCards } from '../actions/mediacards'
const initialState = {
mediaCards: []
}
export const mediacardSlice = createSlice({
name: 'medialist',
initialState: initialState,
reducers: {},
extraReducers: {
[getMediaCards.fulfilled.type]: (state, { payload }) => {
return {
...state,
mediaCards: payload.data
}
}
}
})
My checkbox component =>
All the exports =>
const dispatch = useAppDispatch()
const selector = useAppSelector(state => state.mediacard.mediaCards) //all the items
//onclick function and the action which i want to make to get the verified values
const sortVerified = () => {
dispatch(getVerifiedCards())
console.log('action dispatched')}
JSX=>
<div className=''>
<input
type='checkbox'
value={'checked'}
onClick={sortVerified}
/>
</div>
pls help ive been trying differrent methods but i dont what to do :(
well there was no true value so when ever i tried to filter out the array it gave nothing as the output so yeah...

Access to API using Redux

I have a react-redux app. I need to call API and used it in my component. The app is called with fetch in function in utills.
All functions are group and export like this:
export const sportTeam = {
getBasketballTeam,
getBasketballTeamById,
}
function getBasketballTeam() {
let token = store.getState().UserReducer.token;
fetch(
actions.GET_BASKETBALLTEAM,
{
method: "GET",
headers: { Authorization: `Bearer ${token}` },
}
)
.then((res) => {
if (res.status == 200 ) {
return res.json();
}
})
.then((response) => {
console.log(response);
})
.catch((err) => {
console.log(err);
});
}
getBasketballTeam contains an array of objects.
How can I get getBasketballTeam and used it in the component in the view to returning the list with this data?
You don't want your getBasketballTeam function to access the store directly through store.getState().
What you want is a "thunk" action creator that gets the store instance as an argument when you dispatch it.
The flow that you want is this:
Component continuously listens to the basketball team state with useSelector (or connect).
Component mounts.
Component dispatches a getBasketballTeam action.
Action fetches data from the API.
Reducer saves data from the action to the state.
State updates.
Component re-renders with the new data from state.
The easiest way to do this is with the createAsyncThunk function from Redux Toolkit. This helper handles all errors by dispatching a separate error action. Try something like this:
Action:
export const fetchBasketballTeam = createAsyncThunk(
"team/fetchBasketballTeam",
async (_, thunkAPI) => {
const token = thunkAPI.getState().user.token;
if ( ! token ) {
throw new Error("Missing access token.");
}
const res = await fetch(actions.GET_BASKETBALLTEAM, {
method: "GET",
headers: { Authorization: `Bearer ${token}` }
});
if (res.status !== 200) {
throw new Error("Invalid response");
}
// what you return is the payload of the fulfilled action
return res.json();
}
);
Reducer:
const initialState = {
status: "idle",
data: null
};
export const teamReducer = createReducer(initialState, (builder) =>
builder
.addCase(fetchBasketballTeam.pending, (state) => {
state.status = "pending";
})
.addCase(fetchBasketballTeam.fulfilled, (state, action) => {
state.status = "fulfilled";
delete state.error;
state.data = action.payload;
})
.addCase(fetchBasketballTeam.rejected, (state, action) => {
state.status = "rejected";
state.error = action.error;
})
);
Store:
export const store = configureStore({
reducer: {
team: teamReducer,
user: userReducer,
}
});
Component:
export const BasketballTeam = () => {
const { data, error, status } = useSelector((state) => state.team);
const dispatch = useDispatch();
useEffect(
() => {
dispatch(fetchBasketballTeam());
},
// run once on mount
// or better: take the token as an argument and re-run if token changes
[dispatch]
);
if (status === "pending") {
return <SomeLoadingComponent />;
}
if (!data) {
return <SomeErrorComponent />;
}
// if we are here then we definitely have data
return <div>{/* do something with data */}</div>;
};
After you get response you need to do the following things
call dispatch function to store the data received in REDUX state.
Now when you have data in redux state, you can use useSelector() to get that state and make use of it in your jsx file.

redux and redux-saga : Reducer returns a promise instead of a regular object

Just like the title says, my reducer returns a promise instead of a regular object. I am assuming this is because I am using redux-saga for async requests, which kinda does not make sense. Is there a way to have my reducer return a resolved object instead?
// reducer.tsx
...
const initialState = {
token: '',
error: null,
loading: false
};
const authenticationReducer = async (state = initialState, action) => {
switch (action.type) {
case EMAIL_LOGIN: {
console.log('action.payload: ', action.payload);
const {token} = action.payload;
return {
...state,
token
};
}
...
// saga.tsx
function* emailLogin({type, payload}) {
try {
const {email, password} = payload;
const loginUserInfo = {
email,
password,
};
const response = yield call(axios, `${USER_ADDRESS}/login/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: loginUserInfo,
});
yield put(setLoginStatus(response));
} catch (e) {}
}
But for some reason both useSelector() and useStore() indicates that my reducer returns a promise
// Login.tsx
...
const token = useSelector(state =>{ console.log(state)}) // {authentication: Promise}
const state = useStore().getState();
console.log(state) // {authenitcation: Promise}
...
Please help!
async functions always return a promise. Redux reducers must never be async!
In this case, all you should need to do is remove the async keyword from the reducer.

Integrating API middleware with Redux-Thunk

This is stemming off of this SO Question
I am trying to integrate redux-thunk with my API middleware. The current flow of logic is like this:
action dispatched from component this.props.onVerifyLogin();
=>
action goes to action creator which creates an API call to the middleware like so:
// imports
const verifyLoginAC = createAction(API, apiPayloadCreator);
export const verifyLogin = () => dispatch => {
return verifyLoginAC({
url: "/verify/",
method: "POST",
data: {
token: `${
localStorage.getItem("token")
? localStorage.getItem("token")
: "not_valid_token"
}`
},
onSuccess: result => verifiedLogin(dispatch, result),
onFailure: result => dispatch(failedLogin(result))
});
};
const verifiedLogin = (dispatch, data) => {
console.log("verifiedLogin");
const user = {
...data.user
};
dispatch(setUser(user));
dispatch({
type: IS_LOGGED_IN,
payload: true
});
};
// failedLogin function
const setUser = createAction(SET_USER);
apiPayloadCreator in utils/appUtils:
const noOp = () => ({ type: "NO_OP" });
export const apiPayloadCreator = ({
url = "/",
method = "GET",
onSuccess = noOp,
onFailure = noOp,
label = "",
isAuthenticated = false,
data = null
}) => {
return {
url,
method,
onSuccess,
onFailure,
isAuthenticated,
data,
label
};
};
and then the middleware intercepts and performs the actual API call:
// imports
// axios default config
const api = ({ dispatch }) => next => action => {
next(action);
console.log("IN API");
console.log("Action: ", action);
// this is where I suspect it is failing. It expects an action object
// but is receiving a function (see below for console.log output)
if (action.type !== API) return;
// handle Axios, fire onSuccess/onFailure, etc
The action is created but is a function instead of an action creator (I understand this is intended for redux-thunk). But when my API goes to check action.type it is not API so it returns, never actually doing anything including call the onSuccess function. I have tried to also add redux-thunk before api in the applyMiddleware but then none of my API actions fire. Can someone assist?
Edit:
This is the received data to the API middleware:
ƒ (dispatch) {
return verifyLoginAC({
url: "/verify/",
method: "POST",
data: {
token: "" + (localStorage.getItem("token") ? localStorage.getItem("token") : "not_valid_toke…
Status Update:
Still unable to get it work properly. It seems like redux-saga has a pretty good following also, should I try that instead?
My API was interferring. I switched to redux-saga and got everything working like so:
/**
* Redux-saga generator that watches for an action of type
* VERIFY_LOGIN, and then runs the verifyLogin generator
*/
export function* watchVerifyLogin() {
yield takeEvery(VERIFY_LOGIN, verifyLogin);
}
/**
* Redux-saga generator that is called by watchVerifyLogin and queries the
* api to verify that the current token in localStorage is still valid.
* IF SO: SET loggedIn = true, and user = response.data.user
* IF NOT: SET loggedIn = false, and user = {} (blank object}
*/
export function* verifyLogin() {
try {
apiStart(VERIFY_LOGIN);
const token = yield select(selectToken);
const response = yield call(axios.post, "/verify/", {
// use redux-saga's select method to select the token from the state
token: token
});
yield put(setUser(response.data.user));
yield put(setLoggedIn(true));
apiEnd(VERIFY_LOGIN);
} catch (error) {
apiEnd(VERIFY_LOGIN);
yield put(setLoggedIn(false));
yield put(setUser({})); // SET USER TO BLANK OBJECT
}
}

render view after a post request in react/redux

I have post method helper where I'm making the rest calls to the server which is basically running but the view/container is not rerendering after the call.
export function postData(action, errorType, isAuthReq, url, dispatch, data) {
const requestUrl = API_URL + url;
let headers = {};
if (isAuthReq) {
headers = {headers: {'Authorization': cookie.load('token')}};
}
axios.post(requestUrl, data, headers)
.then((response) => {
dispatch({
type: action,
payload: response.data
});
})
.catch((error) => {
errorHandler(dispatch, error.response, errorType)
});
}
I'm getting the the following error: dispatch is not defined in the browser when I'm calling this method
my call from the container is as followed:
handleFavorite(buildingId) {
const url = `/building/${buildingId}/toogle-favorite`;
postData(FETCH_All_BUILDING, AUTH_ERROR, true, url, this.props.dispatch, {});
}
This is how my connect method is looks like:
function mapStateToProps(state) {
return {
buildings: state.building.buildings,
error: state.building.error,
userId: state.auth.userId
}
}
export default connect(mapStateToProps, {buildingsAll})(BuildingAll);
My Question is...
How can I re render my view? This dispatch that I want to give to the method is not available. Is there a possibility to bind that rest to the state perhaps with mapDispatchToProps. Any idea how I can solve that problem, I'm fairly new to react/redux - it's my first side project in that lib.
Thanks
Update 1
I have updated the code but getting the next error and my view is now not rendering (nothing showing).
mapDispatchToProps() in Connect(BuildingAll) must return a plain object. Instead received function
bundle.js:26 Uncaught TypeError: finalMergeProps is not a function
const mapDispatchToProps = (dispatch) => bindActionCreators(postDataThunk, dispatch);
export default connect(mapStateToProps, mapDispatchToProps, {buildingsAll})(BuildungAll);
You need to bind your action creators in your container
const { bindActionCreators } = require("redux");
const mapStateToProps = (state) => {
return {
buildings: state.building.buildings,
error: state.building.error,
userId: state.auth.userId
}
}
const mapDispatchToProps = (dispatch) => bindActionCreators(YourActions, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(BuildingAll);
And then your action becomes something like this:
import thunk from 'redux-thunk';
const postData = (action, errorType, isAuthReq, url, data) => {
return (dispatch) => {
const requestUrl = API_URL + url;
let headers = {};
if (isAuthReq) {
headers = { headers: { 'Authorization': cookie.load('token') } };
}
axios.post(requestUrl, data, headers)
.then((response) => {
dispatch({
type: action,
payload: response.data
});
})
.catch((error) => {
errorHandler(dispatch, error.response, errorType)
});
};
};
Because your postData might have a few side effects because it's fetching something asynchronously, you'll need a thunk
Read this article on it: http://redux.js.org/docs/advanced/AsyncActions.html

Resources