immutable reducer in redux - javascript

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

Related

useReducer - Enforce only one true value without explicitly setting others false?

I'm looking for a smarter way to enforce
one value true, setting the others to false
This Python q&a suggests an enum
(I'm not familiar with either)
I can already
switch (action.type) {
case 'loadCurrent':
return {...state,
loadCurrent: true,
loadPrev: false, etc ...};
But this is a lot of boilerplate and intuition (as well as that linked python question) tell me there's a more elegant solution.
function reducer (state, action) {
switch (action.type) {
case 'loadCurrent':
return {...state, loadCurrent: true};
case 'fetchNew':
return {...state, fetchNew: true};
case 'loadPrevHook':
return {...state, loadPrevHook: true };
case 'loadNextHook':
return {...state, loadNextHook: true };
case 'loaded':
return {...state, loaded: true };
}
}
const initialState = {
loadCurrent: false,
fetchNew: false,
loadPrevHook: false,
loadNextHook: false,
loaded: true }
Having separate properties for these doesn't make the most sense. Put all the data into a single property instead - perhaps call it loadStage or whatever you think is appropriate.
If, as the code here suggests, the state doesn't have any other properties, just setting the action.type looks like it'd do.
function reducer (state, action) {
return { loadStage: action.type };
}
If you expanded the reducer to include other actions as well not related to the loading actions, you could make an array corresponding to the loading actions.
const loadStageActions = ['loadCurrent', 'fetchNew', 'loadPrevHook', 'loadNextHook', 'loaded'];
function reducer (state, action) {
if (loadStageActions.includes(action.type)) {
return { ...state, loadStage: action.type };
}
// implement other logic here
}
Then, in the rest of your code, just check the one string property instead of the multiple booleans. Eg instead of doing state.loadCurrent, do state.loadStage === 'loadCurrent'.

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.

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

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?

Why we use spread operator into redux

I have seen the following code:
export default function productReducer(state = initialState, action) {
switch(action.type) {
case FETCH_PRODUCTS_BEGIN:
return {
...state,
loading: true,
error: null
};
case FETCH_PRODUCTS_SUCCESS:
return {
...state,
loading: false,
items: action.payload.products
};
case FETCH_PRODUCTS_FAILURE:
return {
...state,
loading: false,
error: action.payload.error,
items: []
};
default:
return state;
}
}
But don't understand why do we use ...state every time for each case.
Can't we just write:
return {
loading: false,
error: action.payload.error,
items: action.payload.products
};
Anyone can explain this?
Because commonly you want to keep other keys inside your state...
If your state has:
{
items:['a', 'b', 'c'],
loading: false,
error: null
}
and you only return for example:
case FETCH_PRODUCTS_BEGIN:
return {
// ...state, --> Without this line
loading: true,
error: null
};
Your new state will be
{
loading: true,
error: null
}
And your items will be lost.
Then, returning:
case FETCH_PRODUCTS_BEGIN:
return {
...state,
loading: true,
error: null
};
You are saying
Return a copy of state, but overriding loading and error keys"
This is for creating new copied state object with new or updated values (without this you would need manually specify every state field).
Object.assign can be used as alternative
Redux Docs has really good explanation about using spread operator.

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;

Categories

Resources