How do I update Redux store with POST response? - reactjs

I have an application that is using React, Redux (Redux Thunk). I having an issue updating the state in the reducer after a fetch post inserts into a table.
I am trying to dispatch an action and pass some information from the action to the reducer but not able to do so. I am specifically trying to pass the fetch response into the dispatch. I have a key named res within my dispatch. I set it to a value of data but I believe this value of data is undefined.
export function insertSearchTerm(searchTerm) {
console.log('C')
return (dispatch) => {
fetch('http://localhost:3001/api/v1/searches?searchterm='+ searchTerm, {
headers: {
'Content-Type':'application/json', //headers - tells y9ou that it is json
},
method:'POST',
body: JSON.stringify(searchTerm) //stringifies searchTerm
}).then(res => console.log('Inside insertSearch Term resp', res.json()))
.then(data => {
dispatch({
type:'INSERT_SEARCH_TERM',
searchTerm: searchTerm,
res : data
})
}
)
}
console.log('E')
}
export default function allSearchTermsReducer(state = {allSearchTerms: []}, action) {
switch (action.type) {
case 'ALL_SEARCHES':
console.log("Allsearch reducer",action.payload);
return {...state, allSearchTerms: action.payload}
case 'INSERT_SEARCH_TERM':
console.log('insert search term action', action)
return {
...state,
allSearchTerms: [...state.allSearchTerms, action.id, action.searchTerm, action.created_at] }
default:
return state
}
};

In your action-creator, for the first .then block you are returning a console.log() not the data itself. So there's no data to dispatch in the proceeding .then block. Should be updated to:
export function insertSearchTerm(searchTerm) {
console.log('C')
return (dispatch) => {
fetch('http://localhost:3001/api/v1/searches?searchterm='+ searchTerm, {
headers: {
'Content-Type':'application/json', //headers - tells y9ou that it is json
},
method:'POST',
body: JSON.stringify(searchTerm) //stringifies searchTerm
}).then(res => res.json())
.then(data => {
dispatch({
type:'INSERT_SEARCH_TERM',
searchTerm: searchTerm,
res : data
})
}
)
}
console.log('E')
}

Related

How to get data from an API using redux?

I am requesting an API using redux and saga
I the request is working fine I am able to get API returned data in the reducer, but I am not able to get that data in the home.js I am new to redux please help me.
in my home.js this is how I am making a call
const fetchSessionData = () => {
dispatch(
fetchSession({
token: token
})
)
}
action.js
export const fetchSession = data => {
return {
type: Action.SESSION_DATA,
payload: {data},
};
};
this is reducer file
export const SessionData = (state = sessionState, action) => {
console.log('inside session reducer', JSON.stringify(action));
switch (action.type) {
case Action.SESSION_FETCH_SUCCESS:
return {
...state,
isLoading: false,
};
case Action.SESSION_FETCH_ERROR:
return {
...state,
sagaerror: action.error,
isLoading: false,
};
case Action.SESSION_DATA:
return {
...state,
isLoading: true,
};
default:
return state;
}
};
and this is the api
export function fetchSessionData(payload) {
return fetch(`${url}/session/`, {
method: 'GET',
headers: {
Authorization: `Bearer ${payload.token}`,
Accept: 'application/json',
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(res => {
return res;
})
.catch(error => {
throw error;
});
}
how can I get the returning data from api in home.js?
Looks like you are not storing back the response from saga.
Please have an action for storing the response into your reducer.you may use
yield put(storeactionname(payload received from api reponse);
in your saga method and in reducer your action.payload should be stored into a state variable
and in your component you can use useSelector like below
const { yourvariableNameInStateInReducer } =
useSelector((state) => state.yourreducername);
As you say you will able to get data to reducer. It's then fine in that section. In the component you need to select stored data from the store. If you are using functional component you can use useSelector hook in react-redux. If you are using class component then you need to use connect function, you need to pass mapStateToProps and mapDispatchToProps arguments to the connect function. Refer https://react-redux.js.org/api/connect

It seems to ignore the Async / Await

On a React page, I have a method called goOut. This method calls upon a Redux action > Node controller > Redux reducer. I can confirm that the correct data values are returned inside the Redux action, the controller method, and the reducer. However, nonetheless, at point 1 below inside the goOut method, it returns undefined.
What am I doing wrong / how could it return undefined if the the reducer is returning the correct values? It is as if the await inside the goOut method is not working...
React page:
import { go_payment } from "../../appRedux/actions/paymentAction";
<button onClick={this.goOut}>
Button
</button>
async goOut(ev) {
try {
const data = { user: parseInt(this.state.userId, 10) };
let result = await this.props.go_payment({data});
console.log(result);
// 1. RETURNS UNDEFINED. As if it tries to execute this line before it has finished the previous line.
{
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(
{go_payment}, dispatch
);
};
Redux Action:
export const go_payment = (data) => {
let token = getAuthToken();
return (dispatch) => {
axios
.post(`${url}/goController`, data, { headers: { Authorization: `${token}` } })
.then((res) => {
if (res.status === 200) {
// console.log confirms correct data for res.data
return dispatch({ type: GO_SUCCESS, payload: res.data });
})
}
}
Node controller method:
Returns the correct data in json format.
Reducer:
export default function paymentReducer(state = initial_state, action) {
switch (action.type) {
case GO_SUCCESS:
// console.log confirms action.payload contains the correct data
return { ...state, goData: action.payload, couponData: "" };
}
}

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 State is Repeated on Every GET Request

I am mapping through an array where I am taking every item in the array and calling a GET request on each item. Then I am pushing them all together in my redux state. The issue is that every time I reload the information is repeated. I am thinking that the way to solve this issue is to clean out my redux store everytime that the request is run however, I do not want to wipe everything, just the photos portion in this case.
momentcontent.js:
componentDidMount () {
this.props.photos.map(photo => {
this.props.fetchPhoto(this.props.token, photo)}
)
}
index.js (actions):
export const fetchPhoto = (token, photo) => dispatch => {
console.log('right token')
console.log(token);
fetch(photo, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Token ${token}`,
}
})
.then(res => res.json())
.then(parsedRes => {
console.log('photo data')
console.log(parsedRes)
dispatch(getPhoto(parsedRes))
})
}
export const getPhoto = (photo) => {
console.log('RES')
console.log(photo)
return {
type: GET_PHOTO,
photo: photo
}
}
photo.js:
import {
GET_PHOTO
} from '../actions';
const initialState = {
photo: []
}
const photoReducer = (state = initialState, action) => {
switch(action.type) {
case GET_PHOTO:
return {
...state,
photo: [...state.photo, action.photo]
}
default:
return state;
}
}
export default photoReducer
my console (I should have 9 items in the array, I have 27 here):
Rather than clearing the redux store photos object and re-adding, you can check if the photo already exists in the store or not.
Assuming that your state contains a photo array, action contains photo object and photo (the image url instance) is a unique object
Therefore you can modify your reducer as
case GET_PHOTO:
return state.photo.some(( { photo } ) => photo === action.photo.photo)
? state
: [...state.photo, action.photo];

Dispatch multiple actions using Redux Thunk and the await/async syntax to track loading

I am currently editing some reducers to be able to track the loading state of axios operations. Most of my async syntax is written in async/await fashion and would like to keep it that way to keep my code organized.
I am not sure how to dispatch two action creators one after the other: the first one to fire off the FETCHING_USER action type and keep track of the reduced isFetching state, while the other one to fire off the actual axios GET request. The code currently looks like this to get the API request:
export const fetchUser = () => async dispatch => {
const res = await axios.get(`${API_URL}/api/current_user`, {
headers: { authorization: localStorage.getItem("token") }
});
dispatch({ type: FETCH_USER, payload: res.data });
};
I am not sure how to dispatch the FETCHING_USER and then fire off the FETCH_USER action.
First you need to modify your reducer to have isFetching statement and requesting and receiving data cases:
const INITIAL_STATE = { isFetching: false, data: [] };
export default(state = INITIAL_STATE, action) => {
switch(action.type) {
case REQUEST_USER: {
return {...state, isFetching: true};
}
case RECEIVE_USER: {
return {...state, isFetching: false, data: action.payload};
}
default: return state;
}
}
Then modify your action to use try/catch statements:
export const fetchUser = () => async dispatch => {
dispatch({ type: REQUEST_USER });
try {
const res = await axios.get(`${API_URL}/api/current_user`, {
headers: { authorization: localStorage.getItem("token") }
});
dispatch({ type: RECEIVE_USER, payload: res.data });
}
catch(e){
//dispatch your error actions types, (notifications, etc...)
}
};
Then in component you can use condition like: isFetching ? //show loader : //show content (data[])

Resources