reducer A make reducer B undefined (React - Redux) - reactjs

I have two reducers inside a combine reducer, and when I call an action to change the state of reducerSrc, my state of reducerTherm get undefined and I receive an error. How can I fix this?
combine:
const rootReducer = combineReducers({
therm: reducerTherm,
src: reducerSrc,
});
export type RootState = ReturnType<typeof rootReducer>;
export const store = createStore(rootReducer);
reducerTherm:
interface Props {
therm: string;
}
const initialState: Props = {
therm: "",
};
export default function reducerTherm(state: Props = initialState, action: any) {
switch (action.type) {
case "SEARCH_THERM":
return action.payload;
break;
default:
return state.therm;
}
}
reducerSrc:
export interface Props {
src: any;
}
const initialState: Props = {
src: "source teste",
};
export default function reducerSrc(state: Props = initialState, action: any) {
switch (action.type) {
case "ADD_SRC":
return action.payload;
break;
default:
return state;
}
}
useEffect watching the therm changing and with this active the action to reducerSrc:
const therm = useSelector((state: RootState) => state.therm);
const src = useSelector((state: RootState) => state.src);
useEffect(() => {
if (therm) {
try {
getPhotosPlaces("shopping")
.then((res) => res.json())
.then((data) => {
store.dispatch({
type: "ADD_SRC",
payload: data,
});
});
} catch (error) {
console.log(error);
}
}
}, [therm]);
The error:
**Unhandled Rejection (Error): When called with an action of type "ADD_SRC", the slice reducer for key "therm" returned undefined. To ignore an action, you must explicitly return the previous state. If you want this reducer to hold no value, you can return null instead of undefined.**

In the default case of your therm reducer you have
return state.therm; // possibly undefined
Surely you mean to be returning the reducer state (not the key therm)
return state; // returns { therm: ... }

Related

How to get the value returned by dispatch function in the same lifecycle

I have made a custom which which implements the useReducer logic.(switch shortened here)
import { useReducer } from 'react'
const reducer = (state, action) => {
switch (action.type) {
default:
return {
...state,
[action.payload.key]: action.payload.value,
}
}
}
function useFilters(initialState) {
const [state, dispatch] = useReducer(reducer, initialState || {})
return {
filters: state,
dispatch,
}
}
export default useFilters
How Do I access the latest value where dispatch is called inside a function
const updateFilters = async ({ type, values }: { type?; values }) => {
dispatch({
type,
payload: { ...values },
})
console.log(filters) // this returns old value
// await refetchProjects({
// input: filters,
// })
}

Typing the state of useReducer

I'm currently working on a Calculator on react with typescript but i'm having some issues to type the state in my reducer function.
Only "any" works for now.
I know that's an object with strings inside, but I don't know why it doesn't works.
Thanks for your help.
import { useReducer } from "react";
import Grid from "./components/Grid";
import NumberButton from "./components/NumberButton";
import OperatorButton from "./components/OperatorButton";
// type State = {
// currentOperation?: string
// result?: string
// operator?: string
// }
export enum ACTIONS {
ADD_NUMBER = 'add-number',
ADD_OPERATOR = 'add-operator',
CALCULATE = 'calculate',
DELETE = 'delete',
RESET = 'reset'
}
export type Action = {
type: ACTIONS,
payload?: { digit?: string, operator?: string }
}
const reducer = (state: any, { type, payload }: Action) => {
console.log("State", state);
switch (type) {
case ACTIONS.ADD_NUMBER:
return {
...state,
currentOperation: `${state.currentOperation || ""}${payload!.digit}`
};
default:
break;
}
};
const App = () => {
const [{ currentOperation, result, operator }, dispatch] = useReducer(reducer, {});
return (
<Grid>
<div className="displayScreen">
<div className="currentOperation">{currentOperation} {operator}</div>
<div className="result">{result}</div>
</div>
<button onClick={() => dispatch({ type: ACTIONS.RESET })}>C</button>
</Grid>
)
}
export default App;
Your switch statement is not exhaustive. In the default case you are returning nothing.
change the reducer function like this:
const reducer = (state: State, { type, payload }: Action) => {
and then:
default:
return state;
This should work.
Another way to type actions without Enums:
type State = {
currentOperation?: string
result?: string
operator?: string
}
export type Action =
| { type: 'ADD_NUMBER', payload: {digit: number} }
| { type: 'ADD_OPERATOR', payload: string};
const reducer = (state: State, action: Action) => {
console.log("State", state);
switch (action.type) {
case 'ADD_NUMBER':
return {
...state,
currentOperation: `${state.currentOperation || ""}${action.payload.digit}`
};
case 'ADD_OPERATOR':
return {
...state,
// (payload here is a string)
}
default:
return state;
}
};

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;
}
};

How to dispatch multiple actions

I am trying to dispatch functions from reducer but it call only one function.
Reducer looks like this:
import types from "./types";
const initState = {
active: false,
myData: []
};
function toggleActive(state, action) {
return {
...state,
active: action.payload
};
}
function watchInfo(state, action) {
return {
...state,
myData: action.payload
};
}
const watchReducer = (state = initState, action) => {
switch (action.type) {
case types.TOGGLE_ACTIVE:
return toggleActive(state, action);
case types.WATCH_DATA:
return watchInfo(state, action);
default:
return state;
}
};
export default watchReducer;
and action creator is set like this:
import types from "./types";
function toggleActive(bool) {
return {
type: types.TOGGLE_ACTIVE,
payload: bool
};
}
function watchInfo(data) {
return dispatch => {
dispatch({
type: types.WATCH_DATA,
payload: data
});
};
}
export { toggleActive as default, watchInfo };
and in component in which I am importing connect and corresponding action creator, i am trying to use it like this:
const mapStateToProps = state => {
const mapDispatchToProps = dispatch => ({
watchInfo: () => dispatch(watchInfo())
});
export default connect
mapDispatchToProps
)(MyComponent);
So when I inspect in redux console it only calls toggleActive, never calls watch info.
I am not sure what I am doing wrong.
change this action creator
function watchInfo(data) {
return dispatch => {
dispatch({
type: types.WATCH_DATA,
payload: data
});
};
}
to:
function watchInfo(data) {
return {
type: types.WATCH_DATA,
payload: data
}
}
action creator is a function that return an object that representing an action. we use action creators for better code maintenance and prevent some Spelling error but this code:
dispatch(watchInfo(someData))
is equivalent to this:
dispatch({
type: types.WATCH_DATA,
payload: someData
})

Modifying state from another reducer in react

I'm pretty new in react so this might be a silly question.
I'm working on an app that manage rss feeds, so the structure of my entire app is similar to this one
<div className="App">
<Header />
<Feeds />
</div>
both components have their own reducer and actions.
the problem appears when I'm trying to create a new feed (actually managed in the feeds reducer) from my header component. so I have to access to the state of the feedsReducer from my headerReducer.
I'm not sure how to proceed at this point.
should I access the feeds reducer from the header component? ( this also implies that the feedsReducer needs to know my header actions)
I'll add some code to make the problem clear
index.js
import feedsReducer from './components/Feeds/FeedsReducer';
import headerReducer from './components/Header/HeaderReducer';
const rootReducer = {
feeds:feedsReducer,
header: headerReducer
};
const store = createStore(combineReducers(rootReducer));
Header/Header.js
import { ADD_FEED } from './actions';
class Header extends Component {
state = {
feedUrl: ""
};
addFeed = () => {
axios.post(
'/feeds/add',
{
url: 'myNewRssFeed.com'
})
.then(feed => {
//this is calling the HeaderReducer
this.props.addFeed(feed.data);
})
.catch(err => console.log(err));
}
}
const mapDispatchToProps = dispatch => {
return {
addFeed: (feed) => dispatch({ type: ADD_FEED, payload: { feed } })
};
};
export default connect(null, mapDispatchToProps)(Header);
Header/actions.js
export const ADD_FEED = "ADD_FEED";
HeaderComponent/HeaderReducer.js
const reducer = (state, action) => {
const newState = {
...state
}
switch (action.type) {
case storeActions.ADD_FEED:
// at this point newState.feeds doesn't exist because it's part from the FeedsReducer
newState.feeds = newState.feeds.push(action.payload.feed);
break;
}
return newState;
}
Feeds/FeedsReducer.js
const initialState = {
feeds: []
}
const reducer = (state = initialState, action) => {
const newState = {
...state
}
switch (action.type) {
//this action is commented because was recently moved to the headerComponent/actions.js
/* case storeActions.ADD_FEED:
newState.feeds = newState.feeds.push(action.payload.feed);
break; */
case storeActions.LOAD_FEEDS:
newState.feeds = action.payload.feeds;
break;
}
return newState;
}
Thanks in advice.
I don't really think you need to access reducer in any way. Reducer function will update store based on action it's listenning to.
Here is an example:
import * as constants from 'constantpathhere';
export function feedReducer(state = INITIAL_STATE, action) {
const { type, payload } = action;
switch(type) {
case constants.ADD_FEED: // listen to ADD_FEED action
return {... state, data: payload };
case constants.LOAD_FEEDS: // listen to LOAD_FEEDS
return {...state, loading: true }
...
default:
return state;
}
}
export function headReducer(state = INITIAL_STATE, action) {
const { type, payload } = action;
switch(type) {
case constants.ANY_ACTION: // listen to ADD_FEED action
return {... state, data: payload };
case constants.ANY_OTHER_ACTION_LOADING: // listen to LOAD_FEEDS
return {...state, loading: true }
...
default:
return state;
}
}
//ACTIONS
export function loadFeeds() {
return {
type: constants.LOAD_FEEDS
}
}
export function addFeed(payload) {
return {
type: constants.ADD_FEED,
payload
}
}
export function triggerAnyAction(payload) {
return {
type: constants.ANY_ACTION,
payload
}
}
These actions above may be dispatched from any component, be it Header or Feeds, only reducer(s) listening to that particular action will update the store.
Briefly, you only need to know which action to dispatch where and only reducer listing to that action will do whatever you instructed it to do

Resources