React async/await usage - Unhandled Rejection (TypeError): Cannot read properties of undefined (reading 'then') - async-await

I am adding data to an addItem array using reducers.
I want to wait until the item is actually added before going forward..I have implemented it as follows
const [addItems, dispatch] = useReducer((state, action) => {
switch (action.type) {
case 'add':
return [
...state,
{
id: state.length,
data: action.data
}
];
default:
return state;
}
}, []);
const linkDataHandler = async(samplesData, test, e) => {
const isSelected = e.target.checked;
await callDispatch(samplesData.id, test.testList.id, isSelected)
.then( r =>{
//do something
}});
};
const callDispatch = (sampleId, testId, checked) => {
const linkedData = {
sampleId: sampleId,
TestId: testId,
isSelected: checked
};
dispatch({
type: "add",
data: linkedData
});
};
function linkDataHandler is called on a checkbox onChange() event.
It is giving me
Unhandled Rejection (TypeError): Cannot read properties of undefined (reading 'then')

I have re-written callDispatch as follows. Since it is expected to return a promise
const callDispatch = (sampleId, testId, checked) => {
const linkedData = {
sampleId: sampleId,
TestId: testId,
isSelected: checked
};
return Promise.resolve(
dispatch({
type: "add",
data: linkedData
}));
};

Related

UseReducer hook doesn't update state (Can't perform a React state update on an unmounted component)

I'm trying to use useReducer instead of useState in a custom hook that loads the initial data from the API, and getting an error updating a state. (I use useReducer here for learning purposes).
The component fetches data firstly correctly, the error occurs when I update the state (book/edit/delete interview).
I left the previous useState code in the comments for better understanding.
import { useReducer, useEffect } from "react";
import axios from "axios";
const SET_DAY = "SET_DAY";
const SET_APPLICATION_DATA = "SET_APPLICATION_DATA";
const SET_INTERVIEW = "SET_INTERVIEW";
const reducer = (state, action) => {
switch (action.type) {
case SET_DAY:
return { ...state, day: action.day }
case SET_APPLICATION_DATA:
return {
...state,
days: action.days,
appointments: action.appointments,
interviewers: action.interviewers
}
case SET_INTERVIEW: {
return { ...state, id: action.id, interview: action.interview }
}
default:
throw new Error();
}
}
export default function useApplicationData() {
// const [state, setState] = useState({
// day: "Monday",
// days: [],
// appointments: {},
// interviewers: {}
// });
const initialState = {
day: "Monday",
days: [],
appointments: {},
interviewers: {}
};
const [state, dispatch] = useReducer(reducer, initialState);
//updates the spots remaining when book/edit/cancel interview
const updateSpots = (requestType) => {
const days = state.days.map(day => {
if(day.name === state.day) {
if (requestType === 'bookInterview') {
// return { ...day, spots: day.spots - 1 }
return dispatch({ type: SET_DAY, spots: day.spots - 1 });
}else {
// return { ...day, spots: day.spots + 1 }
return dispatch({ type: SET_DAY, spots: day.spots + 1 });
}
}
// return { ...day };
return dispatch({ type: SET_DAY, spots: day.spots });
});
return days;
}
//sets the current day data
// const setDay = day => setState(prev => ({ ...prev, day }));
const setDay = (day) => dispatch({ type: SET_DAY, day });
//adds new interview data to database
const bookInterview = (id, interview) => {
const appointment = { ...state.appointments[id] };
const bookOrEdit = appointment.interview ? 'edit' : 'book'; //defines the request type
appointment.interview = { ...interview };
const appointments = { ...state.appointments, [id]: appointment };
let days = state.days;
if (bookOrEdit === 'book') {
days = updateSpots('bookInterview');
}
return axios
.put(`/api/appointments/${id}`, {interview})
.then(() => {
//setState({ ...state, appointments, days });
dispatch({ type: SET_INTERVIEW, id, interview });
})
};
//deletes interview data from database
const cancelInterview = (id) => {
const appointment = {...state.appointments[id], interview: null};
const appointments = {...state.appointments, [id]: appointment };
const days = updateSpots();
return axios
.delete(`/api/appointments/${id}`)
.then(() => {
//setState({ ...state, appointments, days });
dispatch({ type: SET_INTERVIEW, id, interview: null });
})
};
useEffect(() => {
let isMounted = false;
Promise.all([
axios.get('/api/days'),
axios.get('/api/appointments'),
axios.get('/api/interviewers')
])
.then((all) => {
// setState(prev => ({
// ...prev,
// days: all[0].data,
// appointments: all[1].data,
// interviewers: all[2].data}));
// });
if (!isMounted) {
console.log("done!");
}
isMounted = true;
dispatch({ type: SET_APPLICATION_DATA, days: all[0].data, appointments: all[1].data, interviewers:all[2].data });
});
}, []);
return { state, setDay, bookInterview, cancelInterview }
};
I'd be appreciated for pointing me in the right direction on what I'm doing wrong. Thank you!

Redux and data fetching

I am a newbie to React and Redux hope the folks here can help me. I am trying to make 2 api calls as shown below. However only the first api call seem to get run so my rootCategories end up always being set to null. How can I ensure second api call also get executed before state being changed?
Reducer
export const categoryListReducer = (state = {categories: [], rootCategories: []}, action) => {
switch (action.type) {
case CATEGORY_LIST_REQUEST:
return {loading: true, categories: [], rootCategories: []}
case ROOT_CATEGORY_LIST_REQUEST:
return {loading: true, rootCategories: []}
case CATEGORY_LIST_SUCCESS:
return {...state, loading: false, categories: action.payload[0], rootCategories: action.payload[1]}
case ROOT_CATEGORY_LIST_SUCCESS:
return {...state, loading: false, rootCategories: action.payload}
case ROOT_CATEGORY_LIST_FAIL:
return {loading: false, error: action.payload}
case CATEGORY_LIST_FAIL:
return {loading: false, error: action.payload}
default:
return state
}
}
Action
export const listCategories = (breadcrumbs) => async(dispatch) => {
try {
//fire off first reducer and load off empty array of products
dispatch({
type: CATEGORY_LIST_REQUEST
})
const apiEndPoint = breadcrumbs ?
`/api/products/categories/${breadcrumbs}/` :
'/api/products/categories/'
const {
data
} = await axios(apiEndPoint)
const {
data2
} = await axios('/api/products/categories/')
dispatch({
type: CATEGORY_LIST_SUCCESS,
payload: [data, data2],
})
} catch (error) {
console.log('error ' + error)
dispatch({
type: CATEGORY_LIST_FAIL,
payload: error.response && error.response.data.message ? error.response.data.message : error.response.data
})
}
}
Try this:
if (data & data2) {
dispatch({
type: CATEGORY_LIST_SUCCESS,
payload: [data, data2]
})
}
Now, the dispatch is only executed if both const are set.
EDIT:
I found this post about how Axios seems to have its own way to fetch two URLs at once, like this:
import axios from 'axios';
let one = "https://api.storyblok.com/v1/cdn/stories/health?version=published&token=wANpEQEsMYGOwLxwXQ76Ggtt"
let two = "https://api.storyblok.com/v1/cdn/datasources/?token=wANpEQEsMYGOwLxwXQ76Ggtt"
const requestOne = axios.get(one);
const requestTwo = axios.get(two);
axios.all([requestOne, requestTwo]).then(axios.spread((...responses) => {
const responseOne = responses[0]
const responseTwo = responses[1]
// use/access the results
})).catch(errors => {
// react on errors.
})

NextJS: `HYDRATION` action doesn't receive server payload when using `redux-observable`

Packages:
redux-observable#2.0.0-rc.2
rxjs latest
universal-rxjs-ajax dev branch
next-redux-wrapper latest
next.js latest
I have a simple Page with getStaticProps:
export const getStaticProps = wrapper.getStaticProps((store) => async (ctx) => {
store.dispatch({ type: 'ADD_DATA' });
// const response = await fetch('https://rickandmortyapi.com/api');
// const data = await response.json();
// store.dispatch({ type: 'SERVER_ACTION', payload: data.characters });
return {
props: {},
};
});
Action 'ADD_DATA' triggers action 'SERVER_ACTION':
export const AddDataEpic: Epic = (action$) =>
action$.pipe(
ofType('ADD_DATA'),
mergeMap((action) =>
request({ url: 'https://rickandmortyapi.com/api' }).pipe(
map((response) => {
return {
type: 'SERVER_ACTION',
payload: response.response.characters,
};
})
)
)
);
Inside the reducer in the case 'SERVER_ACTION': clause I receive the payload:
const server = (state: State = { data: null }, action: AnyAction) => {
switch (action.type) {
case HYDRATE: {
console.log('HYDRATE >', action.payload); // logs out "HYDRATE > { server: { data: null } }"
return {
...state,
...state.server,
...action.payload.server,
};
}
case 'SERVER_ACTION': {
console.log('SERVER_ACTION >', action.payload); // logs out "SERVER_ACTION > https://rickandmortyapi.com/api/character"
return {
...state,
...state.server,
data: action.payload,
};
}
default:
return state;
}
};
But the payload isn't passed to HYDRATE action:
console.log('HYDRATE >', action.payload); // logs out "HYDRATE > { server: { data: null } }"
If I dispatch the 'SERVER_ACTION' action from inside the getStaticProps:
export const getStaticProps = wrapper.getStaticProps((store) => async (ctx) => {
// store.dispatch({ type: 'ADD_DATA' });
const response = await fetch('https://rickandmortyapi.com/api');
const data = await response.json();
store.dispatch({ type: 'SERVER_ACTION', payload: data.characters });
return {
props: {},
};
});
The HYDRATE action inside the reducer receive the payload:
HYDRATE > { server: { data: 'https://rickandmortyapi.com/api/character' } }
I don't understand what's wrong with my code.
May it be a bug in one of the libraries? Or is it a mistake in my code?
If anyone has any suggestions, PLEASE
#PYTHON DEVELOPER999 It might be due to the latest update on next-redux-wrapper, there are few migration steps =>
https://github.com/kirill-konshin/next-redux-wrapper#upgrade-from-6x-to-7x

React - can't dispatch action in hook component

Using dispatch in useffect hook of functional component,
Below code shows error page like below;
Component:
import { GetParks } from "../../../redux/actions/survey_actions"
...
function BarcodeGenerator(props) {
const dispatch = useDispatch();
useEffect(() => {
dispatch(props.GetParks());
}, []);
actions:
export const GetParks = (Id) => async (dispatch, getState) => {
try {
const response = await axiosHelper.get("api/survey/GetParks", {
params: {
Id,
},
});
debugger;
response = response.data;
if (response.status !== ResponseStatus.SUCCESS) {
dispatch({
type: GET_PARKS,
payload: [1, 4555, 34],
});
}
} catch (error) {
catchCallback(error);
}
};
const _getParks = (data) => ({
type: GET_PARKS,
payload: data,
});
how does dispatch the action to reducer properly
Action must be a plain object, as it is described in the error description. E.g. it is ok to use dispatch directly as is:
if (*statement*) {
dispatch({
action: DO_SMTH,
payload: true
})
}
or to make the action creator returning the equal object if you want to make clean re-usable code:
if (*statement*) {
dispatch(doSmth(true));
}
function doSmth(toggle) {
return ({
action: DO_SMTH,
payload: toggle
})
}

How to write an action that updates Redux's store?

I have a web app where use React-Redux. There is React table (list) that I need to populate with data from database. I use WebApi on the server and automatically generated (by TypeWriter) web-api on the client. The key parts of code looks as following:
1) Routing:
<Route path="/Dictionary/:dictionaryName" component={Dictionary} />
2) State:
export type SingleDictionaryState = Readonly<{
singleDictionary: WebApi.SingleDictionary[];
}>;
export const initialState: SingleDictionaryState = {
singleDictionary: [],
};
3) Reducer:
export const reducer: Reducer<SingleDictionaryState> = (state: SingleDictionaryState = initialState, action: AllActions): SingleDictionaryState => {
switch (action.type) {
case getType(actions.setSingleDictionaryValue):
return { ...state, ...action.payload };
}
return state;
};
4) Actions:
const actionsBasic = {
setSingleDictionaryValue: createAction('singleDictionary/setSingleDictionaryValue', (singleDictionary: any) => singleDictionary),
};
const actionsAsync = {
getDictionaryByName: (dictionaryName: string) => {
const currentState = store.getState().singleDictionary;
WebApi.api.dictionaryQuery.getDictionary(capitalizeForApi(dictionaryName));
},
};
export const actions = Object.assign(actionsBasic, actionsAsync);
const returnsOfActions = Object.values(actionsBasic).map($call);
export type AllActions = typeof returnsOfActions[number];
5) Container:
const mapStateToProps = (state: AppState, ownProps: OwnProps): StateProps => ({
dictionaryType: state.singleDictionary,
});
const mapDispatchToProps = (dispatch: Dispatch<any>): DispatchProps => ({
onLoad: (dictionaryName: string) => {
Actions.singleDictionary.getDictionaryByName(dictionaryName);
},
});
export default withRouter(connect<StateProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)(DictionaryPage));
6) The client web-api:
class DictionaryQueryService {
getDictionary(name: string) {
const user = store.getState().oidc.user;
const headers = new Headers();
headers.append('Accept', 'application/json');
headers.append('Content-Type', 'application/json');
headers.append('Cache-Control', 'no-cache, no-store, must-revalidate');
headers.append('Pragma', 'no-cache');
headers.append('Expires', '0');
if (user) {
headers.append('Authorization', `Bearer ${user.access_token}`);
}
return () => {
return fetch(`api/dictionaries/${encodeURIComponent(name)}`, {
method: 'get',
headers,
})
.then(response => {
if (!response.ok) {
const traceId = response.headers.get("X-Trace-Id");
throw new ApiError(`${response.status} ${response.statusText}`, traceId);
}
return response.status == 204 ? null : response.json() as Promise<any[]>;
});
};
}
Actually, I'm not sure how to write my getDictionaryByName action.
Just my 2 cents. I use ES6 syntax, but Typescript would work as similar way.
actionTypes.js
export const RESET_DICTIONARIES = 'RESET_DICTIONARIES';
export const LOAD_DICTIONARIES_REQUEST = 'LOAD_DICTIONARIES_REQUEST';
export const LOAD_DICTIONARIES_REQUEST_SUCCESS = 'LOAD_DICTIONARIES_REQUEST_SUCCESS';
export const LOAD_DICTIONARIES_REQUEST_FAILURE = 'LOAD_DICTIONARIES_REQUEST_FAILURE';
dictionaryActions.js
/* Load Dictionariies*/
export function loadDictionariesRequestBegin() {
return {type: types.LOAD_DICTIONARIES_REQUEST};
}
export function loadDictionariesRequest(name) {
return function(dispatch) {
dispatch(loadDictionariesRequestBegin());
// eslint-disable-next-line no-undef
const request = new Request(`${YOUR_URL}/api/dictionaries/{name}`, {
method: 'get',
headers: new Headers({
'Content-type': 'application/json',
'Accept': 'application/json',
'Authorization': auth.getToken(),
})
});
return fetch(request)
.then(
response => {
if (!response.ok) {
dispatch(loadDictionariesRequestFailure(response.statusText));
throw new Error(response.statusText);
}
return response.json();
},
error => {
dispatch(loadDictionariesRequestFailure(error));
throw error;
})
.then(dictionaries=> {
if (dictionaries) {
dispatch(loadDictionariesRequestSuccess(dictionaries));
return dictionaries;
} else {
throw new Error('dictionaries NOT found in response');
}
});
};
}
export function loadDictionariesRequestSuccess(dictionaries) {
return {type: types.LOAD_DICTIONARIES_REQUEST_SUCCESS, dictionaries};
}
export function loadDictionariesRequestFailure(error) {
return {type: types.LOAD_DICTIONARIES_REQUEST_FAILURE, error};
}
dictionaryReducer.js
export default function dictionaryReducer(state = initialState.dictionaries, action) {
switch (action.type) {
case types.RESET_DICTIONARIES:
return {
...state,
loaded: false,
loading: false,
error: null,
};
/* load dictionaries*/
case types.LOAD_DICTIONARIES_REQUEST:
return {
...state,
error: null,
loaded: false,
loading: true
};
case types.LOAD_DICTIONARIES_REQUEST_SUCCESS:
return {
...state,
data: action.dictionaries,
error: null,
loaded: true,
loading: false
};
case types.LOAD_DICTIONARIES_REQUEST_FAILURE:
return {
...state,
loaded: false,
loading: false,
error: action.error
};
return state;
}
initialState.js
export default {
actions: {},
dictionaries: {
data: [],
loaded: false,
loading: false,
error: null,
},
}
dictionary client side API
this.props.actions
.loadDictionaryRequest(name)
.then(data => {
this.setState({ data: data, errorMessage: '' });
})
.then(() => {
this.props.actions.resetDictionaries();
})
.catch(error => {
...
});
Hope this may help.

Resources