JHipster and React: How to call function after calling asynchronous reducer action? - javascript

Use Case: I have a react application generated with JHipster. I need to get data from API, map to form contract, and then submit the form.
Problem: JHipster generated reducer code doesn't return a promise, so how do I know when a reducer action is finished? How can I call a function after getting an entity to update state?
Getting Entity and Returning the ICrudGetAction:
export interface IPayload<T> { // redux-action-type.ts (JHipster Code from node_modules)
type: string;
payload: AxiosPromise<T>;
meta?: any;
}
export type IPayloadResult<T> = ((dispatch: any) => IPayload<T> | Promise<IPayload<T>>);
export type ICrudGetAction<T> = (id: string | number) => IPayload<T> | ((dispatch: any) => IPayload<T>);
// Start of my reducer code
export default (state: AdState = initialState, action): AdState => {
switch (action.type) {
case REQUEST(ACTION_TYPES.FETCH_MYENTITY_LIST):
return {
...state,
errorMessage: null,
updateSuccess: false,
loading: true
};
case FAILURE(ACTION_TYPES.FETCH_MYENTITY_LIST):
return {
...state,
loading: false,
updating: false,
updateSuccess: false,
errorMessage: action.payload
};
case SUCCESS(ACTION_TYPES.FETCH_MYENTITY_LIST): {
const links = parseHeaderForLinks(action.payload.headers.link);
return {
...state,
loading: false,
links,
entities: loadMoreDataWhenScrolled(state.entities, action.payload.data, links),
totalItems: parseInt(action.payload.headers['x-total-count'], 10)
};
}
case ACTION_TYPES.RESET:
return {
...initialState
};
default:
return state;
}
};
export const getEntity: ICrudGetAction<IMyEntity> = id => {
const requestUrl = `${apiUrl}/${id}`;
return {
type: ACTION_TYPES.FETCH_ENTITY,
payload: axios.get<IMyEntity>(requestUrl)
};
};
I try to do this and it works but gives me compile errors:
this.props.getEntity(this.props.match.params.id).then((response) => {
// Map to form attributes and call setState
});
I get error message:
TS2339: Property 'then' does not exist on type 'IPayload | ((dispatch: any) => IPayload)'.
Property 'then' does not exist on type 'IPayload'.
This makes sense because we are not returning a promise. but how can I update the code to return a promise such that I don't break all the things that were autogenerated while also keeping the redux store updated?

Related

does not exist on type 'string'.ts(2339)

Im new to typescript, here i have been adding ts to my react project, i'm getting this kind of error: Property 'identifier' does not exist on type 'string'.ts(2339), the code works as it is but when added ts it gives that error, i can solve it like this: currentBroker: "" as any, but then thats not the right way.
any help/suggestion ?
my code:
const initialSelectionState = {
currentBroker: "",
};
export function selectionReducer(
state = initialSelectionState,
action: selectionReducerAction
) {
switch (action.type) {
case ActionType.CHANGE_SITE:
return {
...state,
currentSite: action.payload,
};
case ActionType.CHANGE_CAMERA:
return {
...state,
currentCamera: action.payload,
};
case ActionType.CHANGE_ANALYSER:
return {
...state,
currentAnalyser: action.payload,
};
case ActionType.CHANGE_PLATFORM:
return {
...state,
currentPlatform: action.payload,
};
case ActionType.CHANGE_BROKER:
return {
...state,
currentBroker: action.payload,
};
case ActionType.CLEAR_ANALYSER_DATA:
return {
...state,
currentAnalyser: "",
};
case ActionType.CLEAR_CAMERA_DATA:
return {
...state,
currentCamera: "",
};
case ActionType.CLEAR_PLATFORM_DATA:
return {
...state,
currentPlatform: "",
};
case ActionType.CLEAR_BROKER_DATA:
return {
...state,
currentBroker: "",
};
case ActionType.UPDATE_BROKER:
const ucurrentBroker =
state.currentBroker.identifier == action.payload.identifier
? action.payload
: state.currentBroker;
return { ...state, currentBroker: ucurrentBroker };
}
}
currentBroker is of type string, it's not an object, and typescript tells you that you cannot access properties because it doesn't have them.
If you are using typescript I suggest you to declare interface of your state first:
interface IState {
currentBroker: string;
}
const initialSelectionState: IState = {
currentBroker: "",
};
and of you need your broker to have an identifier property you can modify the state interface as you need:
interface IState {
currentBroker: {
identifier: string;
}
}
const initialSelectionState: IState = {
currentBroker: {
identifier: ""
};
};
With this you should not have the error again (if I understood correctly)
You initialise the "currentBroker" as a string.
const initialSelectionState = {
currentBroker: "",
};
Then later attempt to access the currentBroker as if it's an object with a property identifier
state.currentBroker.identifier == action.payload.identifier
Casting to any will allow the code to run, and most likely just return undefined when trying to get that property.
If all you are trying to do is update a string in a state object, surely no logic is required?
const initialSelectionState = {
currentBroker: "",
};
export function selectionReducer(
state = initialSelectionState,
action: selectionReducerAction
) {
switch (action.type) {
case ActionType.UPDATE_BROKER:
return { ...state, currentBroker: action.payload};
}
}
Since you mentioned the structure of the payload is as follows;
{
identifier: 'b9b6381e-2d42-4953-b747-4b25fd382a04',
name: 'Broker 244',
host: 'localhost',
port: 1783,
site_id: '0-0-0-0'
}
Then your currentBroker should in some way match the structure of this. Alternatively, if there is 'no current broker initially', you should make it undefined. In either case, it definitely shouldn't be an empty string i.e. "" since that is a totally different data type.

Ngrx store dispatch is not accepting string as arguments

I'm taking a test in which I should write code a in such a way that all unit test case gets passed.
Case 1:
it('should dispatch action when dispatchAction is called', async() => {
// you need to spy on store's 'dispatch' method
store = TestBed.get(Store);
spyOn(store, 'dispatch').and.callThrough();
// if you call function dispatchAction with 'movies' paramter. expect store to dispatch action='movies'
component.dispatchAction('movies');
fixture.detectChanges();
expect(store.dispatch).toHaveBeenCalledWith('movies');
});
My code:
dispatchAction($event: string) {
this.store.dispatch({type: 'movie'});
}
But the spec is getting failed throwing the below error
Expected spy dispatch to have been called with [ 'movies' ] but actual calls were [ Object({ type: 'movies' }) ].
Reducer,
export function news (state = initialState, action: Action) {
switch (action.type) {
case LOAD_SECTION_NEWS: {
return {
newsList: mockNewsList,
filter: action.type
};
}
case FILTER_SUBSECTION: {
return {
newsList: mockNewsList,
filter: action.payload
};
}
default:
return state;
}
}
export const getNewsList = (state: any) => {
return state;
};
export const getFilter = (state: any) => {
return state;
};
Action
export class NewsActions {
static LOAD_SECTION_NEWS = '[News] LOAD_SECTION_NEWS';
static FILTER_SUBSECTION = '[News] FILTER_SUBSECTION';
LoadSectionNews(list: News[]): Action {
return {
type: '',
payload: ''
};
}
FilterSubsection(subsection: string) {
return {
type: '',
payload: ''
};
}
}
How do I modify, the reducer in such a way that the unit test case get passed.
This Ngrx is out of syllabus and I've no idea. Please help.
The error reported is about .toHaveBeenCalledWith('movies'); from your test case. The expectation is the word movies to have been used as argument, and this is incorrect.
When you call this.store.dispatch({type: 'movies'}); in your controller, it is passing the object {type: 'movies'} as argument.
as your test is expecting only the word movie, it fails
change your expectation to
expect(store.dispatch).toHaveBeenCalledWith({type: 'movies'});
that will fix your test
Good luck with your studies
var data = 'movies';
this.store.dispatch(data as any)
var data = 'movies';
this.store.dispatch(data as any)
You can achieve the result by casting the string to any

Saving to redux store by key, nested state

Chat application:
I would like to save different ChatMessages arrays per conversation id.
I've imagined the state would look something like this:
state {
messages: {
isRequesting: false,
messageByConversationId: {
"23523534543": [messages],
"64634523453": [messages],
}
}
}
But I can't seem to save a nested state, is it possible?
My code: (That does not run because of this line: chatMessage[conversationId]: payload.chatMessages)
export const loadChatMessagesSuccess: Reducer<ImmutableChatMessagesState> =
(state: ImmutableChatMessagesState, {payload}: AnyAction & { payload?: LoadChatMessagesSuccessParams }) =>
payload ? {...state, requesting: false, chatMessage[conversationId]: payload.chatMessages}
: state;
Not sure I fully understand your question. But you could do the following to add loaded messages to the messageByConversationId object while keeping other loaded messages.
NB. This assumes you have somehow get conversationId as a variable. For example you could make it to be a part of payload
export const loadChatMessagesSuccess: Reducer<ImmutableChatMessagesState> = (
state: ImmutableChatMessagesState,
{ payload }: AnyAction & { payload?: LoadChatMessagesSuccessParams }
) =>
payload
? {
...state,
requesting: false,
messageByConversationId: {
...state.messageByConversationId,
[payload.conversationId]: payload.chatMessages
}
}
: state;

immutable reducer in redux

Is this reducer looks fine? why the author used a let users;, that look unnecessary to me. Isn't that will cause 2 users in FETCH_USER_FULLFILLED case?
const initalState = {
users: [],
loading: false,
error: null,
};
// REDCUER
function usersReducer(state = initalState, action) {
let users;
switch (action.type) {
case 'FETCH_USER_PENDING':
return { ...state, loading: true };
case 'FETCH_USER_FULFILLED':
users = action.payload.data.results;
return { ...state, loading: false, users };
case 'FETCH_USER_REJECTED':
return { ...state, loading: false, error: `${action.payload.message}` };
default:
return state;
}
}
export default usersReducer;
The reducer is fine and it will not cause two users in FETCH_USER_FULLFILLED. However, you are right, there is no need for let users;. So the code will look like
case 'FETCH_USER_FULFILLED':
return { ...state, loading: false, users: action.payload.data.results };
When the reducer first executed, it will take its start state from initalState as it is the default if no param passed. So users will be empty array at the beginning and will be filled on FETCH_USER_FULLFILLED action
Edit users Added to take advantage of property value shorthand feature in ES6

Flow complaining about action union type in reducer

Flow throws 3 errors (property not found) for each parameter (action.location, action.weatherResult and action.error). The only solution I found is to not union and have just one action type with the 3 different properties as optional maybes but the properties aren't optional so it doesn't solve my problem.
Actions
// #flow
import actionTypes from './index';
export type FetchWeatherStartAction = {
type: string,
location: string
};
export type FetchWeatherSuccessAction = {
type: string,
weatherResult: ?string
};
export type FetchWeatherFailAction = {
type: string,
error: string | false
};
export type WeatherAction = FetchWeatherStartAction | FetchWeatherSuccessAction | FetchWeatherFailAction;
const fetchWeatherStart = (location: string): FetchWeatherStartAction => ({
type: actionTypes.WEATHER_FETCH_START,
location
});
const fetchWeatherSuccess = (weatherResult: ?string): FetchWeatherSuccessAction => ({
type: actionTypes.WEATHER_FETCH_SUCCESS,
weatherResult
});
const fetchWeatherFail = (error: string | false): FetchWeatherFailAction => ({
type: actionTypes.WEATHER_FETCH_FAIL,
error
});
export {
fetchWeatherStart,
fetchWeatherSuccess,
fetchWeatherFail
}
Action Types
// #flow
const actionTypes = {
WEATHER_FETCH_START: 'WEATHER_FETCH_START',
WEATHER_FETCH_SUCCESS: 'WEATHER_FETCH_SUCCESS',
WEATHER_FETCH_FAIL: 'WEATHER_FETCH_FAIL'
}
export default actionTypes;
Reducer
// #flow
import actionTypes from './../actions';
import type { WeatherAction } from './../actions/weather';
/*export type WeatherActionType = {
type: string,
error?: boolean | string,
weatherResult?: string | null,
location?: string
};*/
export type WeatherStateType = {
location: string,
fetchedFromServer: boolean,
isFetching: boolean,
fetchError: boolean | string,
weatherResult: ?string
};
const defaultState: WeatherStateType = {
location: 'Barcelona',
fetchedFromServer: false,
isFetching: false,
fetchError: false,
weatherResult: null
};
const weather = (state: WeatherStateType = defaultState, action: WeatherAction): WeatherStateType => {
switch (action.type) {
case actionTypes.WEATHER_FETCH_START:
return {
...state,
isFetching: true,
fetchError: false,
location: action.location
};
case actionTypes.WEATHER_FETCH_SUCCESS:
return {
...state,
fetchedFromServer: true,
isFetching: false,
fetchError: false,
weatherResult: action.weatherResult
};
case actionTypes.WEATHER_FETCH_FAIL:
return {
...state,
fetchedFromServer: false,
isFetching: false,
fetchError: action.error
};
default:
return state;
}
};
export default weather;
You are attempting to rely on type information that is not actually encoded in your types.
For example in the definition of FetchWeatherStartAction:
export type FetchWeatherStartAction = {
type: string,
location: string
};
type is declared to be a string. Any string at all.
But later, in this switch case:
switch (action.type) {
case actionTypes.WEATHER_FETCH_START:
...
action.location
...
You are expecting Flow to know that FetchWeatherStartAction is the only possible alternative of the WeatherAction enum which could have 'WEATHER_FETCH_START' as the value of its type property. Based on the types alone, any action could have any value for its type. The only thing we can be sure about is that it's a string.
The solution is to define your action variants to have more specific types, which incorporate their legal values.
export type FetchWeatherStartAction = {
type: 'WEATHER_FETCH_START',
location: string
};
export type FetchWeatherSuccessAction = {
type: 'WEATHER_FETCH_SUCCESS',
weatherResult: ?string
};
export type FetchWeatherFailAction = {
type: 'WEATHER_FETCH_FAIL',
error: string | false
};
When you check that type === 'WEATHER_FETCH_START', Flow can be certain that the actual type is FetchWeatherStartAction. This is possible because it already knows that action is a WeatherAction and WeatherAction is an enum with only those three possible values.
It's a little unfortunate that you have to repeat the string literals, rather than refer to a constant. I know people get uneasy about that, but I would argue in this case that all of the reasons why magic constants are considered bad practice are dealt with by Flow's type checking. In Javascript, using a syntactic identifier to access a field is semantically no different than accessing it by its string name.
In case anyone stumble upon this problem.
Root cause: Flow will remove all refinements to the types when you do a function call.
Solution: Access the param before you make the function call
For example, in your case, the reducer can be written in this way:
const reducer = (state: {}, action: WeatherAction): WeatherStateType => {
switch (action.type) {
case actionTypes.WEATHER_FETCH_START:
const {location} = action;
return { ...state, isFetching: true, fetchError: false, location: location};
case actionTypes.WEATHER_FETCH_SUCCESS:
const {weatherResult} = action;
return {...state, fetchedFromServer: true, isFetching: false, fetchError: false,, weatherResult: weatherResult};
case actionTypes.WEATHER_FETCH_FAIL:
const {error} = action;
return {...state. fetchedFromServer: false, isFetching: false, fetchError: action.error};
default:
return state
}
}

Categories

Resources