Make redux with redux-thunk action synchronous - reactjs

I need to make redux action and then use state to update my url.
I have tried to make a promise:
const opa = (type: string, checked: boolean) => (
dispatch: any,
getState: any ) =>
Promise.resolve().then(() => {
return dispatch(sidebarEmploymentTypeActions.setType(type, checked))
})
And then use it:
const handleEmploymentTypeChange = (type: string, checked: boolean) => {
//#ts-ignore
dispatch(opa(type, checked)).then(console.log(employmentType))
}
But console.log returns previous state.
My action:
export const setType = (type: string, checked: boolean) => {
return {
type: sidebarEmploymentType.SET_TYPE,
employmentType: type,
checked
}
}
And reducer:
const sidebarEmploymentType: Reducer = (
state = defaultState,
{ type, employmentType, checked }
) => {
switch (type) {
case sidebarEmploymentTypeType.SET_TYPE:
return {
...state,
[employmentType]: {
checked: checked,
label: state[employmentType].label,
},
}
default:
return state
}
}
export { sidebarEmploymentType }
Root reducer:
export const rootReducer = combineReducers({
sidebarEmploymentType
})
And store creation:
import thunk from 'redux-thunk'
const store = createStore(
persistReducer(persistConfig, rootReducer),
composeEnhancers(applyMiddleware(thunk))
)

Related

How to I pass an object to react-redux

I'm a little confused on passing an object to the redux store. I have successfully created the store and can add items from the initial state. The function also fires when called
Action:
import { GET_ITEM } from './OrderTypes'
export const getItem = (payload) => {
return {
type: GET_ITEM,
payload: { payload }
}
}
Reducer:
import { GET_ITEM } from './OrderTypes'
const initialState = {
orderList: [],
}
const orderReducer = (state = initialState, action) => {
switch (action.type) {
case GET_ITEM: return {
...state,
orderList: [...state.orderList, action.payload]
}
default: return state
}
}
export default orderReducer
Component:
class TestComponentextends Component {
pushItem = () => {
this.props.getItem({
payload: 'test object'
})
}
render() {
return (
<input type='button' value='test btn' onClick={this.pushItem} />
)
}
}
const mapStateToProps = state => {
return {
orderList: state.orderList
}
}
const mapDispatchToProps = dispatch => {
return {
getItem: () => dispatch(getItem())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(TestComponent)
What happens: An empty object is added to the orderList array.
What I want to happen: Store the object in pushItem in the orderList array.
Your mapDispatchToProps doesn't pass the arguments to the action creator (see mapDispatchToProps function arguments - 2nd example):
const mapDispatchToProps = dispatch => ({
getItem: (...args) => dispatch(getItem(...args))
})
Even easier is to let react-redux handle the mapping by using mapDispatchToProps as an object:
const mapDispatchToProps = {
getItem
}

How to fix Property 'incrementCount' does not exist on type 'ContextType | undefined'

I am trying to abstract the logic of useContext and useReducer to not repeat the code whenever i create a new context but i ran in to some problems when i try to strongly type createContext with typescript.
With this function i automate context creation:
import React, { createContext, ReactElement, useReducer } from 'react';
type ProviderProps = {
children: ReactElement;
};
type ActionType = {
type: string;
payload?: any;
};
export default function <StateType>(
reducer: (state: StateType, action: ActionType) => StateType,
actions: any,
initialState: StateType,
) {
type ContextType = {
state: StateType;
actions:{
[k: string]: Function;
}
};
const Context = React.createContext<ContextType | undefined>(undefined);
const Provider = ({ children }: ProviderProps) => {
const [state, dispatch] = useReducer(reducer, initialState);
const boundActions: any = {};
for (let key in actions) {
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{ state, actions:{
...boundActions
} }}>
{children}
</Context.Provider>
);
};
return { Context, Provider };
}
Example context creation:
import createDataContext from './createDataContext';
import { INCRASE_COUNT, DECRASE_COUNT } from './ActionTypes';
type ActionType = {
type: string;
payload?: any;
};
type StateType = {
count: number;
};
const reducer = (state: StateType, action: ActionType) => {
switch (action.type) {
case INCRASE_COUNT:
return { count: state.count + 1 };
case DECRASE_COUNT:
return { count: state.count - 1 };
default:
return state;
}
};
const incrementCount = (dispatch: React.Dispatch<any>) => {
return () => {
dispatch({ type: INCRASE_COUNT });
};
};
const decrementCount = (dispatch: React.Dispatch<any>) => {
return () => {
dispatch({ type: DECRASE_COUNT });
};
};
export const { Context, Provider } = createDataContext<StateType>(
reducer,
{
incrementCount,
decrementCount,
},
{ count: 69 },
);
When i use it:
import { Context as ExampleContext } from '../context/ExampleContext';
const { state, actions } = useContext(
ExampleContext,
);
it underlines state and actions with a red line and says:
Property 'state, actions' does not exist on type 'ContextType | undefined'
What did i do wrong here is there something that i missed?
PLZZZZZZ HELP ME.
You are getting the error since you specified context type as ContextType | undefined and undefined does not have state and actions properties.
Instead of passing undefined to createContext you can refactor your code like this:
export default function <StateType>(
reducer: (state: StateType, action: ActionType) => StateType,
actions: any,
initialState: StateType,
) {
type ContextType = {
state: StateType;
actions: typeof actions;
};
const Context = React.createContext<ContextType>({
state: initialState,
actions,
});
...
return { Context, Provider };
}

Redux store updated but component is not updating

Like in title, component EventList is not re-rendered when redux state is updated. I think I've tried everything, but nothing works. That the state is modified I can see in the browser extension Redux DevTools.
Please, write what I'm doing wrong.
Thank you in advance for help.
EventList component:
interface RootState {
lastEvent: Event,
allEvents: Event[]
}
const EventList = () => {
const classes = useStyles();
const lastEvent = useSelector((state: RootState) => state.lastEvent);
var allEvents = useSelector((state: RootState) => state.allEvents);
const dispatch = useDispatch();
useEffect(() => {
dispatch(eventActions.getAllEvents());
}, [lastEvent]);
return (
<div>
{allEvents && <div>EventListTable...</div>}
</div>
);};
Action:
const getAllEvents = (): ThunkAction<
void,
RootState,
unknown,
Action<string>
> => (dispatch) => {
eventService.getAllEvents().then((events: Event[]) => {
dispatch(success(events));
});
function success(events: Event[]): EventActionsTypes {
return {
type: GET_ALL_EVENTS,
events: events,
};
}
};
Reducer:
export interface EventState {
lastEvent: Event | null;
allEvents: Event[];
}
const initialState: EventState = {
lastEvent: null,
allEvents: [],
};
export function eventReducer(state = initialState, action: EventActionsTypes) {
switch (action.type) {
case SAVE_EVENT: {
return {
...state,
lastEvent: action.event,
};
}
case GET_ALL_EVENTS: {
return {
...state,
allEvents: [...action.events],
};
}
default: {
return state;
}
}
}
Combined all reducers:
export const rootReducer = combineReducers({
authentication,
registration,
eventReducer,
alert,
});
export type RootState = ReturnType<typeof rootReducer>
Try
var allEvents = useSelector((state: RootState) => state.eventReducer .allEvents);
In redux devtools you can see where allEvents is set, why would you try and get it from state.allEvents
Here you can see how combineReducers work and what state it will produce.

How to manage more than one reducer with createContext and useReducer

I'm trying to use createContext with useReducer and have some trouble
I'm dispatching two actions and both actions are storing their payload at the same place in the state, not to their own.
All the help will be appreciated
Here is my store
import React, { createContext, useReducer, Dispatch } from 'react';
import { InitialStateType } from './types';
import { citiesReducer, CitiesActions } from './citiesReducer';
import { LoadingActions, loadingReducer } from './loadingReducer';
const initialState: InitialStateType = {
cities: [],
loading: false,
};
const store = createContext<{
state: InitialStateType;
dispatch: Dispatch<CitiesActions | LoadingActions>;
}>({
state: initialState,
dispatch: () => {},
});
const { Provider } = store;
const mainReducer = (
{ cities, loading }: InitialStateType,
action: LoadingActions | CitiesActions,
) => ({
cities: citiesReducer(cities, action),
loading: loadingReducer(loading, action),
});
const StateProvider = ({ children }: any): React.ReactElement => {
const [state, dispatch] = useReducer<any>(mainReducer, initialState);
return <Provider value={{ state, dispatch }}>{children}</Provider>;
};
export { store, StateProvider };
Both reducers
import { ActionTypes } from './types';
export type CitiesActions = {
type: ActionTypes.SET_CITIES_DATA;
payload: [];
};
export const citiesReducer = (state: [], action: CitiesActions) => {
switch (action.type) {
case action.type:
return (state = action.payload);
default:
return state;
}
};
import { ActionTypes } from './types';
export type LoadingActions = {
type: ActionTypes.LOADING;
payload: boolean;
};
export const loadingReducer = (state: boolean, action: LoadingActions) => {
switch (action.type) {
case action.type:
return (state = action.payload);
default:
return state;
}
};
Here I'm dispatching the actions one after another
dispatch({ type: ActionTypes.SET_CITIES_DATA, payload: result });
dispatch({ type: ActionTypes.LOADING, payload: false });
And as a result, I'm getting in my state
cities: false
loading: false
instead of
cities: [data],
loading: false
You need to specify the action when handling reducers instead of having a case like case action.type in switch statement otherwise regardess of what action you dispatch all reducers will use it and set the payload. In such a case the last actions data will be set for all states
export type CitiesActions = {
type: ActionTypes.SET_CITIES_DATA;
payload: [];
};
export const citiesReducer = (state: [], action: CitiesActions) => {
switch (action.type) {
case ActionTypes.SET_CITIES_DATA: // specify the action here
return (state = action.payload);
default:
return state;
}
};
import { ActionTypes } from './types';
export type LoadingActions = {
type: ActionTypes.LOADING;
payload: boolean;
};
export const loadingReducer = (state: boolean, action: LoadingActions) => {
switch (action.type) {
case ActionTypes.LOADING: // Specify the action here
return (state = action.payload);
default:
return state;
}
};

Redux store does not update after dispatching an action to my reducer

Following problem: I've tried to write a generic typescript reducer the last few hours, and I feel like it's working fairly well already, but there's just one problem - They way I wired it with my store seems to have problems. It seems like the store does not properly update, as a component I tried to hook up with the data from the reducer does not receive new props.
This is the generic reducer. It's not fully complete yet, but the add functionality should work at least.
// Framework
import * as Redux from "redux";
// Functionality
import { CouldBeArray } from "data/commonTypes";
import { ensureArray } from "helper/arrayUtils";
type ReducerParams<T> = {
actionIdentifier: string;
key: keyof T;
}
export type ReducerState<T> = {
data: Array<T>;
}
type ReducerAction<T> = Redux.Action & {
payload: CouldBeArray<T>;
}
type Reducer<T> = {
add: (data: T) => ReducerAction<T>;
update: (data: T) => ReducerAction<T>;
delete: (data: T) => ReducerAction<T>;
replace: (data: T) => ReducerAction<T>;
reducer: Redux.Reducer<ReducerState<T>, ReducerAction<T>>;
}
export const createReducer = <T>(params: ReducerParams<T>): Reducer<T> => {
const ADD_IDENTIFIER = `${params.actionIdentifier}_ADD`;
const UPDATE_IDENTIFIER = `${params.actionIdentifier}_UPDATE`;
const DELETE_IDENTIFIER = `${params.actionIdentifier}_DELETE`;
const REPLACE_IDENTIFIER = `${params.actionIdentifier}_REPLACE`;
const initialState: ReducerState<T> = {
data: []
};
const reducer = (state = initialState, action: ReducerAction<T>): ReducerState<T> => {
switch (action.type) {
case ADD_IDENTIFIER:
const newState = { ...state };
const newData = [ ...newState.data ];
const payloadAsArray = ensureArray(action.payload);
payloadAsArray.forEach(x => newData.push(x));
newState.data = newData;
return newState;
case UPDATE_IDENTIFIER:
return {
...state,
};
case DELETE_IDENTIFIER:
return {
...state,
};
case REPLACE_IDENTIFIER:
return {
...state,
};
default:
return initialState;
}
}
const addAction = (data: T): ReducerAction<T> => {
return {
type: ADD_IDENTIFIER,
payload: data,
}
};
const updateAction = (data: T): ReducerAction<T> => {
return {
type: UPDATE_IDENTIFIER,
payload: data,
}
};
const deleteAction = (data: T): ReducerAction<T> => {
return {
type: DELETE_IDENTIFIER,
payload: data,
}
};
const replaceAction = (data: T): ReducerAction<T> => {
return {
type: REPLACE_IDENTIFIER,
payload: data,
}
};
return {
add: addAction,
update: updateAction,
delete: deleteAction,
replace: replaceAction,
reducer: reducer,
}
}
Next off, my store:
// Framework
import * as redux from "redux";
// Functionality
import { ReducerState } from "modules/common/Reducer/CrudReducer";
import { reducer as friendsReducer } from "modules/Friends/Reducer/FriendsReducer";
import { Friend } from "modules/Friends/types";
export type ReduxStore = {
friendsReducer: ReducerState<Friend>;
}
export const store: ReduxStore = redux.createStore(
redux.combineReducers({
friendsReducer: friendsReducer.reducer,
})
);
export default store;
and last but not least, the consuming component:
type Props = {
friends: Array<Friend>
}
export const FriendsList: React.FC<Props> = ({ friends }) => {
return (
<Flex className={"FriendsList"}>
Friends
</Flex>
);
}
const mapStateToProps = (store: ReduxStore): Props => {
return {
friends: store.friendsReducer.data,
};
}
export default connect(mapStateToProps)(FriendsList);
The problem usually unfolds in the following order:
Data is properly fetched from network
Update the store via store.dispatch(friendsReducer.add(payload))
With the debugger, I did step through the genericreducer and saw that the new state properly contains the new data.
This is where the problem occurs - The freshly generated state by the reducer is not transferred to my Friendslist component. It will only receive props once, while the data in there is still empty.
Where did I go wrong?
EDIT: By demand, the code for the friendsReducer:
import { createReducer } from "modules/common/Reducer/CrudReducer";
import { Friend } from "modules/friends/types";
export const reducer = createReducer<Friend>({
actionIdentifier: "FRIENDS",
key: "id"
});
export default reducer;
and for the dispatch:
const friendsResponse = await friendsCommunication.getFriends();
if (friendsResponse.success){
this.dispatch(friendsReducer.add(friendsResponse.payload));
}
...
protected dispatch(dispatchAction: Action){
store.dispatch(dispatchAction);
}
Found the problem - My generic reducer returned the following as default:
default:
return initialState;
while it should return state.
Otherwise it just did reset the state of all iterated reducers for every action.

Resources