Using Redux Toolkit, I'm trying to dispatch an action without context of event or etc., so I get the following error:
error TS2554: Expected 2 arguments, but got 1. An argument for 'action' was not provided.
With following code:
const App = () => {
const dispatch = useDispatch();
useEffect(() => {
(async () => {
const result = await fetchConfig();
dispatch(setConfig({ ConfigReducerState: result })); // ---> Error is here <---
})();
}, [dispatch]);
};
The reducer:
export const configSlice = createSlice({
name: 'config',
initialState,
reducers: {
setConfig(state, action) {
const { server, map } = action.payload;
state.server = server;
state.map = map;
},
},
});
Usually I give one parameter to action creator functions - object representing the payload, no need to refer the state. But here I can't. What am I doing wrong?
I've seen this before and every time, it was... a bug in IntelliJ/WebStorm.
See https://youtrack.jetbrains.com/issue/WEB-46527 and https://youtrack.jetbrains.com/issue/WEB-42559 - essentially, WebStorm has their own "quick TypeScript interpolation that does not use the official tsserver for type checking, but something self-cooked, that guesses types just based on things having similar names - and regularly gets things wrong.
If I understand their system correctly, you should be able to see the correct types by hovering over while holding Ctrl down.
In the end, I can't really tell you how to fix this other than switching to an IDE that does not randomly guess, but actually uses TypeScript to evaluate TypeScript types.
How did you import setConfig? I had the same issue and it turned out that by mistake I used
import setConfig from './configSlice'
instead of
import { setConfig } from './configSlice'
It was importing the default export (whole slice reducer, aliased as setConfig) instead of just this one function...
Related
I'm replacing getState with an enhancer as follows:
interface ArtificialStateEnhancerProps {
getArtificialStateGetter: StateGetterFn;
}
export const getArtificialStateEnhancer = ({
getArtificialStateGetter
}: ArtificialStateEnhancerProps) => {
return (createStore: typeof createStoreOriginal) => {
return (rootReducer, preloadedState, enhancers) => {
const store = createStore(rootReducer, preloadedState, enhancers);
const { getState } = store;
if (getArtificialStateGetter) {
return { ...store, getState: getArtificialStateGetter(getState) };
}
return { ...store };
};
};
};
When using store.getState() somewhere in my code it works an my custom getStage method is used. However within an Action or Sage (using select()) the original, native getState from redux is used.
Example action:
export function myAction(
slug: string
): ReduxAction<any> {
return async (
dispatch: Dispatch,
getState: () => any // Here getState is used but the native version of redux
): Promise<any> => {
const state = getState();
const foo = ...do something with `state`
};
}
Is this intended behavior?
It most likely depends on the ordering of where the middleware enhancer is added vs your custom enhancer.
If the middleware enhancer is added after the custom enhancer, then it is dealing with the base-level store methods. Each enhancer kind of "stacks" on top of each other.
My guess is that you've got something like:
compose(myCustomEnhancer, applyMiddleware(saga))
meaning that the middleware enhancer is after the custom one.
You'd need to flip those if that's the case.
(As a side question: could you give more details as to why you're writing a custom enhancer? It's a valid technique, but very rarely used, and it's even more rare that you would ever need or want to override getState.)
Also, note that we generally recommend against using sagas in almost all use cases, and especially if you're looking at using sagas for basic data fetching. Today, Redux Toolkit's "RTK Query" data fetching and caching API does a vastly better job of handling data fetching for you, and the RTK "listener" middleware handles the same reactive logic use case as sagas but with a simpler API, smaller bundle size, and better TS support.
See my talk The Evolution of Redux Async Logic for comparisons and explanations of why we recommend against sagas.
I am studying Redux-Toolkit studio, and in particular, I am approaching custom middleware.
Below I report the definition of my store within my classic application example:
import { combineReducers, configureStore} from '#reduxjs/toolkit';
import {todosSlice} from '.. /features/todos/todosSlice';
import { filterStore } from '.. /features/todos/filterSlice';
import { myLog } from '.. /reduxtoolkitmiddlewarecustom/myLog';
const rootReducer = combineReducers({
todos: todosSlice.reducer,
filter: filterStore.reducer
});
export const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware(). concat(myLog),
});
As you see in the store amount, the middleware "custom" defined in '.. /reduxtoolkitmiddlewarecustom/myLog';
The definition of middleware is as follows:
export const myLog = (store) => {
console.log('Init myLog');
return function myDispatch(next) {
return function myAction(action) { //action is the action received from the store.
store.Dispatch({ type: 'INIT_MYLOG', payload: null });
console.log(action);
//return next(action);
}
}
};
Well, if I emit a Dispatch, I do not return next(action), is that the return I still get an error of "Uncaught Rangeerror: Maximum call stack size exceeded".
I thought I understood, but evidently, I don't. The only way to make the code work is, for example, to make a console.log
What am I tripping about?
If found the error, in general, if the middleware makes a Dispatch in the store, must a corresponding reducer/action creator exist?
Thank you so much
your middleware dispatches INIT_MYLOG on every action. and that dispatched INIT_MYLOG then triggers your middleware. and that then dispatches. an endless circle.
Solution? Don't dispatch in a middleware except if you do it as a reaction to one specific action.
I tried everything like spread operator but nothing works.
Here is my reducer
//state is an array of objects.
const initialState = [
{taskName: "kkkkk", isEdit: false},
]
export const todoReducer = (state=initialState, action) =>{
switch(action.type){
case 'add' :
const temp=
{
taskName: action.payload.taskName,
isEdit: action.payload.isEdit
}
state.push(temp);
return {state}
default: return state
}
}
The error message indicates that you are using Redux Toolkit - that is very good. The problem is that you are not using createSlice or createReducer and outside of those, in Redux you are never allowed to assign something to old state properties with = or call something like .push as it would modify the existing state.
Use createSlice instead:
const initialState = [
{taskName: "kkkkk", isEdit: false},
]
const slice = createSlice({
name: 'todos',
reducers: {
add(state, action) {
state.push(action.payload)
}
}
})
export const todoReducer = slice.reducer;
// this exports the auto-generated `add` action creator.
export const { add } = slice.actions;
Since the tutorial you are currently following seems to be incorporating both modern and completely outdated practices, I would highly recommend you to read the official Redux Tutorial instead, which shows modern concepts.
Fairly new to redux, react-redux, and redux toolkit, but not new to React, though I am shaky on hooks. I am attempting to dispatch an action from the click of a button, which will update the store with the clicked button's value. I have searched for how to do this high and low, but now I am suspecting I am thinking about the problem in React, without understanding typical redux patterns, because what I expect to be possible is just not done in the examples I have found. What should I be doing instead? The onclick does seem to capture the selection, but it is not being passed to the action. My goal is to show a dynamic list of buttons from data collected from an axios get call to a list of routes. Once a button is clicked, there should be a separate call to an api for data specific to that clicked button's route. Here is an example of what I currently have set up:
reducersRoutes.js
import { createSlice } from "#reduxjs/toolkit";
import { routesApiCallBegan } from "./createActionRoutes";
const slice = createSlice({
name: "routes",
initialState: {
selected: ''
},
{... some more reducers...}
routeSelected: (routes, action) => {
routes.selected = action.payload;
}
},
});
export default slice.reducer;
const { routeSelected } = slice.actions;
const url = '';
export const loadroutes = () => (dispatch) => {
return dispatch(
routesApiCallBegan({
url,
{...}
selected: routeSelected.type,
})
);
};
createActionRoutes.js
import { createAction } from "#reduxjs/toolkit";
{...some other actions...}
export const routeSelected = createAction("routeSelection");
components/routes.js:
import { useDispatch, useSelector } from "react-redux";
import { loadroutes } from "../store/reducersRoutes";
import { useEffect } from "react";
import { routeSelected } from "../store/createActionRoutes";
import Generic from "./generic";
const Routes = () => {
const dispatch = useDispatch();
const routes = useSelector((state) => state.list);
const selected = useSelector((state) => state.selected);
useEffect(() => {
dispatch(loadroutes());
}, [dispatch]);
const sendRouteSelection = (selection) => {
dispatch(routeSelected(selection))
}
return (
<div>
<h1>Available Information:</h1>
<ul>
{routes.map((route, index) => (
<button key={route[index]} className="routeNav" onClick={() => sendRouteSelection(route[0])}>{route[1]}</button>
))}
</ul>
{selected !== '' ? <Generic /> : <span>Data should go here...</span>}
</div>
);
};
export default Routes;
Would be happy to provide additional code if required, thanks!
ETA: To clarify the problem - when the button is clicked, the action is not dispatched and the value does not appear to be passed to the action, even. I would like the selection value on the button to become the routeSelected state value, and then make an api call using the routeSelected value. For the purpose of this question, just getting the action dispatched would be plenty help!
After writing that last comment, I may actually see a couple potential issues:
First, you're currently defining two different action types named routeSelected:
One is in the routes slice, generated by the key routeSelected
The other is in createActionRoutes.js, generated by the call to createAction("routeSelection").
You're importing the second one into the component and dispatching it. However, that is a different action type string name than the one from the slice - it's just 'routeSelection', whereas the one in the slice file is 'routes/routeSelected'. Because of that, the reducer logic in the slice file will never run in response to that action.
I don't think you want to have that separate createAction() call at all. Do export const { routeSelected } = slice.actions in the slice file, and dispatch that action in the component.
I'm also a little concerned about the loadroutes thunk that you have there. I see that you might have omitted some code from the middle, so I don't know all of what it's doing, but it doesn't look like it's actually dispatching actions when the fetched data is retrieved.
I'd recommend looking into using RTK's createAsyncThunk API to generate and dispatch actions as part of data fetching - see Redux Essentials, Part 5: Async Logic and Data Fetching for examples of that.
New to Jest and Redux and I'm having trouble with testing functions that are dispatching to the store but don't yield a return value. I'm trying to follow the example from the Redux website does this
return store.dispatch(actions.fetchTodos()).then(() => {
// return of async actions
expect(store.getActions()).toEqual(expectedActions)
})
however I have several "fetchtodos" functions that don't return anything which causes the error TypeError:
Cannot read property 'then' of undefined due to returning undefined
I'm wondering what I can do to test that my mock store is correctly updating. Is there a way to dispatch the function, wait for it to finish and then compare the mock store with expected results?
Thanks
Edit: We're using typescript
action from tsx
export function selectTopic(topic: Topic | undefined): (dispatch: Redux.Dispatch<TopicState>) => void {
return (dispatch: Redux.Dispatch<TopicState>): void => {
dispatch({
type: SELECT_Topic,
payload: topic,
});
dispatch(reset(topic));
};
}
test.tsx
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('Select Topic action', () => {
it('should create an action to select .', () => {
const topic: Topic = mockdata.example[0];
const expectedAction = {
type: actions.SELECT_TOPIC,
payload: topic,
};
const store = mockStore(mockdata.defaultState);
return store.dispatch(actions.selectTopic(topic)).then(() => {
expect(store.getState()).toEqual(expectedAction);
});
});
});
The action is what I'm given to test(and there are many other functions similar to it. I'm getting that undefined error when running the test code, as the function isn't returning anything.
In Redux, the store's dispatch method is synchronous unless you attach middleware that changes that behavior, ie: returns a promise.
So this is likely a redux configuration problem. Be sure you are setting up your test store with the same middleware that allows you to use the promise pattern in production.
And as always, be sure to mock any network requests to avoid making api calls in test.