Weird behavior with redux not updating store state - reactjs

I just ran into an issue, with my redux store not updating after a matched action is dispatched.
I managed to get my reducer to work, here it is:
// working approach
import { AnyAction } from 'redux';
export interface IReducerState {
config: (Record<string, any> & {
name: string;
age: string;
}) | null
}
const initialState = {
config: null,
};
const reducer = (state: IReducerState = initialState, action: AnyAction): IReducerState => {
const { type, payload } = action;
switch (type) {
case 'MATCHED_ACTION':
return {
...state,
config: {
...state.config,
name: payload.clear ? '' : state.config.name,
age: payload.clear ? '' : state.config.age,
[payload.field]: payload.value,
},
};
default:
return { ...state };
}
};
export default reducer;
But before, however, I was doing the following:
// alternative approach
case 'MATCHED_ACTION':
const result = { ...state.config };
if (payload.clear) {
result.name = '';
result.age = '';
}
if (payload.field && payload.value) {
result[payload.field] = payload.value;
}
return {
...state,
config: result
};
What am I doing wrong? Why is the alternative approach incorrect?

Related

Redux : assigning new value to state in reducer doesn't update state but mutating works

In this code, when i change the value of state in the setMessage() reducer to action.payload, the changes aren't getting picked up by the selectors.
import { createSlice } from '#reduxjs/toolkit'
const initialState = {
value: null,
type: null
}
export const messageSlice = createSlice({
name: 'message',
initialState: initialState,
reducers: {
setMessage: (state, action) => {
state = action.payload //not working
},
clearMessage: state => {
state = initialState //not working
}
}
})
but if i change the "value" or "type" field of the message state instead of changing the whole object, the selectors are able to pick up the changes and everything works fine:
import { createSlice } from '#reduxjs/toolkit'
const initialState = {
value: null,
type: null
}
export const messageSlice = createSlice({
name: 'message',
initialState: initialState,
reducers: {
setMessage: (state, action) => {
state.value = action.payload.value //works
state.type = action.payload.type //works
},
clearMessage: state => {
state.value = null //works
state.type = null //works
}
}
})
There are no pointers in JavaScript. When = is used with a variable a new value is assigned to it, not to a memory address
function setMessage(state) {
state = {
x: 5
}
}
let state = {
x: 3
}
setMessage(state)
console.log(state)
function setMessage(state) {
state.x = 5
}
let state = {
x: 3
}
setMessage(state)
console.log(state)

store updated value in redux

i got two values i.e.company and id from navigation.
let id = props.route.params.oved.toString();
console.log("id-->",id);
let company = props.route.params.company.toString();
console.log("company--->",company);
i got two values as a integer like this:--
id-->1
comapny-->465
enter image description here
Description of the image:---
if i am giving input 1 in that textInput and click on the card(lets say first card i.e.465 then i am getting those two values in navigation as in interger that i have mention above.so each time i am getting updated values.
i am getting updated values from navigation.
so i want to store those values in redux.
action.js:--
import { CHANGE_SELECTED_COMPANY } from "./action-constants";
export const changeCompany = (updatedCompany, updatedId) => {
return {
type: CHANGE_SELECTED_COMPANY,
updatedCompany,
updatedId,
};
};
reducer.js:--
import { CHANGE_SELECTED_COMPANY } from "../actions/action-constants";
const initialState = {
company: "",
id: "",
};
const changeCompanyReducer = (state = initialState, action) => {
switch (action.type) {
case CHANGE_SELECTED_COMPANY:
return {
company: {
company: action.updatedCompany,
id: action.updatedId,
},
};
}
return state;
};
export default changeCompanyReducer;
congigure-store.js:--
import changeCompanyReducer from "./reducers/change-company-reducer";
const rootReducer = combineReducers({changeCompanyReducer});
How can i store the update values getting from navigation in Redux?
could you please write code for redux??
First I would recommend writing your action like this:
import { CHANGE_SELECTED_COMPANY } from "./action-constants";
export const changeCompany = (payload) => {
return {
type: CHANGE_SELECTED_COMPANY,
payload // inside payload you can pass: { updatedCompany: '...', updatedId: '...' }
};
};
And then you need to change your reducer from what you wrote to this:
import { CHANGE_SELECTED_COMPANY } from "../actions/action-constants";
const initialState = {
company: "",
id: "",
};
const changeCompanyReducer = (state = initialState, action) => {
switch (action.type) {
case CHANGE_SELECTED_COMPANY:
const { updatedCompany, updatedId } = action.payload;
// notice the changes I made in the return statment.
return {
...state,
company: updatedCompany,
id: updatedId
};
// you can return the state here
default:
return state;
}
};
export default changeCompanyReducer;

React Context; useReducer Pattern: Can/Should properties on initialState have functions to update initialState?

I have a custom hook which I am trying to move into the context/reducer pattern:
import { sortDateStrings } from '#/Utils/SortDateStrings'
function useDiscounts ({ data }) {
const [effectiveDates] = useState(data.effectiveDates || [])
const sortedEffectiveDates = sortDateStrings(effectiveDates, { desc: true })
const effectiveDateOptions = sortedEffectiveDates.map(item => ({ id: item, value: item, label: dayjs(item).format(DATE_FORMAT) }))
return {
effectiveDateOptions
}
}
And this is the beginning of `initialState` for the useReducer pattern:
const initialState = {
effectiveDates: [],
sortedEffectiveDates: sortDateStrings(this.effectiveDates, { desc: true }),
}
You can see I have a property on the initialState which is calling a function using this
I am thinking you should do something like this:
function DiscountsReducer (state, action) {
switch (action.type) {
case 'effectiveDateOptions': {
return { ...state, ...{ // but not sure what to do here? }
}
default: {
throw new Error(`Unhandled action type: ${action.type}`)
}
}
}
Can anyone advise what is the way to handle this in using this pattern?

Typescript inference issue

I'm trying to build a generic Store using React's useReducer and useContext but I'm having an issue with the inference of the default state.
The store generator function is the following:
export function generateStore<Actions extends ReducerAction, State = any>(defaultValue: State, reducer: (state: State, action: Actions) => State): {
provider: (props: { children: ReactNode }) => ReactElement;
dispatcher: (action: Actions) => void;
useStore: () => State;
} {
const store = createContext(defaultValue);
const { Provider } = store;
let dispatch: React.Dispatch<Actions>;
const ProviderElm = (props: { children: ReactNode }): ReactElement => {
const { children } = props;
const [state, dispatcher] = useReducer(reducer, defaultValue);
dispatch = dispatcher;
return <Provider value={state}>{children}</Provider>;
};
return {
provider: ProviderElm,
dispatcher: (action: Actions) => dispatch && dispatch(action),
useStore: () => useContext(store),
};
}
An initializer example could be:
const defaultState = {
auth: {
authenticated: false,
},
};
type StoreActions =
| {
type: 'LOGIN';
payload: {
token: string;
};
}
| {
type: 'LOGOUT';
};
const { dispatcher, provider, useStore } = generateStore<StoreActions>(
defaultState,
(state = defaultState, action) => {
switch (action.type) {
case 'LOGIN': {
const { token } = action.payload;
return {
...state,
auth: {
authenticated: true,
token,
},
};
}
case 'LOGOUT': {
return {
...state,
auth: {
authenticated: false,
token: null,
},
};
}
default:
return defaultState;
}
},
);
The issue is that the State generic of generateStore can't infer itself as the typeof the parameter defaultValue.
It always requires me to initialize it like this or else the intellisense won't work out the type:
generateStore<StoreActions, typeof defaultState>
Any idea on how I make this work and why it currently can't infer the type?
If you want TypeScript to infer your generic types. You cannot provide any type arguments to the function. TypeScript does not support partial type inference. It's all or nothing. By calling generateStore<StoreActions> you are triggering the compiler to use the predefined State = any generic argument on your function.
I would recommend having a strongly typed state to make it cleaner.
type State = {
auth: {
authenticated: boolean
}
}
type StoreActions =
| {
type: 'LOGIN';
payload: {
token: string;
};
}
| {
type: 'LOGOUT';
};
const defaultState: State = {
auth: {
authenticated: false,
},
};
const { dispatcher, provider, useStore } = generateStore<StoreActions, State>(
defaultState,
(state = defaultState, action) => {
switch (action.type) {
case 'LOGIN': {
const { token } = action.payload;
return {
...state,
auth: {
authenticated: true,
token,
},
};
}
case 'LOGOUT': {
return {
...state,
auth: {
authenticated: false,
token: null,
},
};
}
default:
return defaultState;
}
},
);
The only other option is to create a wrapper function that only needs one argument to infer (the state) and supplies the actions type directly. You'll need one for each set of actions, but it might be a good work around depending on how many times it will be used.
type StoreActions =
| {
type: 'LOGIN';
payload: {
token: string;
};
}
| {
type: 'LOGOUT';
};
const defaultState = {
auth: {
authenticated: false,
},
};
export function generateStoreWithStoreActions<State = any>(defaultValue: State, reducer: (state: State, action: StoreActions) => State) {
return generateStore<StoreActions, State>(defaultValue, reducer);
}
const { dispatcher, provider, useStore } = generateStoreWithStoreActions(
defaultState,
(state = defaultState, action) => {
switch (action.type) {
case 'LOGIN': {
const { token } = action.payload;
return {
...state,
auth: {
authenticated: true,
token,
},
};
}
case 'LOGOUT': {
return {
...state,
auth: {
authenticated: false,
token: null,
},
};
}
default:
return defaultState;
}
},
);

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