Redux toolkit actionCreator warn: Invalid number of arguments, expected 0 - redux

it's action:
export const someAction = createAction('SOME_ACTION')
Is there a way to fix it?
Invalid number of arguments, expected 0
dispatch(someAction({key: 'val'}))
Okay, I found how, you just need to add function prepare by second argument like this
export const someAction = createAction('SOME_ACTION', prepare)
well, after this the next warn in saga
Unresolved variable type someAction.type
function* watch() {
yield takeLatest(someAction.type, getOneClientWork);
}
OMG!

As for your second problem: Try
function* watch() {
yield takeLatest(someAction, getOneClientWork);
}
But in general, those are TypeScript warnings that come from your IDE applying TypeScript to pure, untyped JavaScript code - in the hopes of giving you better autocompletion and hints. Unfortunatley, stuff like that goes wrong a lot of times.
So if you were writing TypeScript, you'd write
export const someAction = createAction<PayloadType>('SOME_ACTION')
and if you don't do that, it goes back to the default behaviour:
export const someAction = createAction('SOME_ACTION')
// defaults to
export const someAction = createAction<void>('SOME_ACTION')
and would mean "this takes no payload".
Now your IDE blindly applies TypeScript there, uses the default void and you end up with warnings that don't really concern you.

Related

Redux Toolkit says this snippet(19 lines) is shorter code (vs. original 12) confusion

I am reading this page getting into react-redux https://redux.js.org/introduction/getting-started
I am very confused looking at the Basic Example which has 12 lines of code(excluding usage, imports, and comments)
Then I read this line on the "Redux Toolkit Example" which below the code states "Redux Toolkit allows us to write shorter logic that's easier to read, while still following the same Redux behavior and data flow." However, this example is 19 lines of code(excluding usage, imports, and comments)
The usage in both examples is 3 lines of code. Could someone explain this to me?
Perhaps when it scales, the redux toolkit example does save more code? Honestly, I find the Basic Example MUCH easier to read and maintain. NOTE: I am a complete newb which leads me to believe the basic example may be better as we ramp up and hire developers. This allows them to ramp up more quickly(but I am only a single data point amongst newbs)
thanks,
Dean
You're right about the lines of code in the example. Perhaps that simple counter example doesn't do justice to how much code Redux Toolkit can save because they aren't adding all the "bells and whistles" in their non-toolkit version.
This section is called "getting started with Redux" rather than "migrating to Redux Toolkit" so I suspect they don't want to overwhelm the user by introducing best practices like action creator functions which aren't strictly necessary. But you're not seeing the "write less code" benefit because most of the code that you can remove with the Toolkit is coming from things that weren't in the example in first place.
Action Creators
One of the main benefits of the createSlice function is that it automatically creates the action name constants and action creator functions to go along with each case in the reducer.
This example is just dispatching raw actions directly with string names store.dispatch({ type: 'counter/incremented' }). Most devs would not do this because of how fragile and inflexible it is.
An alternate version of the non-toolkit example, what you would see in most code, looks more like this:
// action name constants
const INCREMENTED = 'counter/incremented';
const DECREMENTED = 'counter/decremented';
// action creator functions
// usually most take some arguments
const incremented = () => ({
type: INCREMENTED,
})
const decremented = () => ({
type: DECREMENTED,
})
// reducer function
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case INCREMENTED:
return { value: state.value + 1 }
case DECREMENTED:
return { value: state.value - 1 }
default:
return state
}
}
If you want to include typescript types it gets even worse.
Immutability
The reducer itself could get really lengthy if you are trying to do immutable updates on deeply nested data.
Here's an example copied from those docs on how to safely update the property state.first.second[someId].fourth
Without Toolkit
function updateVeryNestedField(state, action) {
return {
...state,
first: {
...state.first,
second: {
...state.first.second,
[action.someId]: {
...state.first.second[action.someId],
fourth: action.someValue
}
}
}
}
}
With Toolkit:
const reducer = createReducer(initialState, {
UPDATE_ITEM: (state, action) => {
state.first.second[action.someId].fourth = action.someValue
}
})
configureStore
The Toolkit configureStore actually does save a step vs the Redux createStore function when you are combining more than one reducer. But again this example fails to show it. Instead the Toolkit version is longer because we set a reducer property rather than just passing the reducer.
A typical Redux app uses the combineReducers utility to combine multiple reducers as properties on an object:
import {createStore, combineReducers} from "redux";
const rootReducer = combineReducers({
counter: counterReducer,
other: otherReducer
});
const vanillaStore = createStore(rootReducer);
With the Toolkit you can just pass your reducers map directly without calling combineReducers.
import {configureStore} from "#reduxjs/toolkit";
const toolkitStore = configureStore({
reducer: {
counter: counterReducer,
other: otherReducer
}
});
Which is roughly the same amount of code. But it also includes some default middleware which would be extra lines in the non-toolkit example.

Wrap all saga actions in a common generator function

I am using redux-sage in my application and below is my code
export default function* () {
yield takeLatest(ActionTypes.VALIDATE_INPUT, checkForInputValidity);
yield takeLatest(ActionTypes.ON_REFRESH, onRefresh);
yield takeLatest(ActionTypes.ON_SUBMIT, onSubmit);
}
Is there a way to make sure I call a common generator function before any action is handled. For example,
whenever I dispatch some action, I want to update a variable in redux-state. this is common across all actions. What I am trying to avoid here is to duplicate some piece of common code in every action handler
You can use simple reducer function which skips action type check and track all the actions, and i.e. return the current timestamp:
export default () => + new Date()
You can call take with no arguments or with '*' in order to match all actions, per the docs.
The part that might get tricky (though still totally doable!) is getting the correct order of generator functions. Is it important the universal generator is run before the other generators, or is it ok if they are all run at once asynchronously? The docs on concurrency, fork vs spawn, and running tasks in parallel might illustrate the difference.
Untested, but I think this is what you want:
import {all, takeLatest, takeEvery } from "redux-saga/effects";
function* universal(action) {
// do something
// if dispatching anything here, make sure that the dispatched action doesn't get picked up again and create an infinite loop
}
export default function* () {
// univeral generator is called with a blocking yield
yield takeEvery( '*', universal);
// run individual generators in parallel to each other
yield all ([
takeLatest(ActionTypes.VALIDATE_INPUT, checkForInputValidity),
takeLatest(ActionTypes.ON_REFRESH, onRefresh),
takeLatest(ActionTypes.ON_SUBMIT, onSubmit),
]);
}
Alternatively, you could write some sort of custom middleware.

Calling an external function with redux-saga yield call appears to give context errors

I have a redux-saga watcher/saga setup as below:
function* parseSaga(action) {
let result = corewar.parser.parse(action.redcode)
yield put({ type: PARSE, result })
}
export function* parseWatcher() {
yield takeLatest(PARSE_REQUESTED, parseSaga);
}
Where corewar is an imported npm package I've written.
When the function is written as above, it works as expected, but I'd like to wrap the call to parse in a yield call so that I can better test things as described in the docs here: https://redux-saga.js.org/docs/basics/DispatchingActions.html
However, when I wrap up the function call like so:
let result = yield call(corewar.parser.parse, action.redcode)
I get an error which appears to come from my npm package as follows:
uncaught at parseWatcher at parseWatcher
at takeLatest
at parseSaga
TypeError: Cannot read property 'scanner' of null
at Parser.parse (http://localhost:3000/static/js/bundle.js:2017:28)
at runCallEffect (http://localhost:3000/static/js/bundle.js:43679:19)
at runEffect (http://localhost:3000/static/js/bundle.js:43601:648)
... and so on
Scanner in this case is an internal property to the Parser class which is called in the parse method as shown below (in typescript):
public parse(document: string, options?: IParseOptions): IParseResult {
options = Object.assign({}, Parser.DefaultOptions, options || {});
var context = this.scanner.scan(document, options);
... other stuff
}
So it appears like somehow through using this saga it's got inside my npm package and messed up the this reference?
It seems like I need to somehow ensure the previous context is retained but I wasn't sure how to achieve this as I'm not aware of how it's become lost by just wrapping the external call up in the redux-saga call function.
EDIT: The plot thickens
So it's definitely a context issue, but it seems related to calling nested function calls. I've tweaked the npm package so that parse is also exposed from the root object and now see the following results:
Works
let result = yield call(corewar.parse.bind(corewar), action.redcode)
let result = yield call([corewar, corewar.parse], action.redcode)
but the original nested method does not
Does not work
let result = yield call(corewar.parser.parse.bind(corewar), action.redcode)
let result = yield call([corewar, corewar.parser.parse], action.redcode)
I'm willing to expose the public interface from the root object (as it was on my todo list anyway) but is this the expected result? or some quirk?
Isn't this working
let result = yield call(corewar.parser.parse.bind(corewar.parser), action.redcode)
Since parse is a method of coreware.parser and not just coreware.
If this is not working then why not:
const parse = code => corewar.parser.parse(code);
let result = yield call(parse, action.redcode)

Redux Saga - Take Every is never Called

I have this simple saga:
export function* priceComparisonSaga() {
yield takeEvery(RECORD_PRICE, priceComparison);
}
But whenever I dispatch the event, it is never called. What's going on?
It turns out the action in question had an extra "type" property.
When using the shortcut to create actions like this,
export const addBalance = makeActionCreator(ADD_BALANCE,"balance","type");
This action won't work, because the second type property overwrites "ADD_BALANCE". Instead, this works:
export const addBalance = makeActionCreator(ADD_BALANCE,"balance","balancetype");

Passing in redux-devtools to a redux store with middleware

How is this code processed in relation to the way it is written in the redux-devtools documentation?
https://github.com/auth0-blog/redux-auth/blob/master/index.js#L10-L12
let createStoreWithMiddleware = applyMiddleware(thunkMiddleware, api)(createStore)
let store = createStoreWithMiddleware(quotesApp)
I'm not sure how to rewrite this to include DevTools but I did find this GitHub link including a pull request to include DevTools, which I've since gotten working. However, I still do not understand how it is being applied and what's going on with the let something = function(param1,param2)(function). I know that with that syntax the return value of applyMiddleware is being sent to createStore, but the createStore syntax takes a reducer, initialState, and an enhancer. How is this being applied here?
import { createDevTools } from 'redux-devtools'
import LogMonitor from 'redux-devtools-log-monitor'
import DockMonitor from 'redux-devtools-dock-monitor'
const DevTools = createDevTools(
<DockMonitor toggleVisibilityKey="ctrl-h" changePositionKey="ctrl-q">
<LogMonitor theme="tomorrow" preserveScrollTop={false} />
</DockMonitor>
)
let createStoreWithMiddleware = applyMiddleware(thunkMiddleware, api)(createStore)
let store = createStoreWithMiddleware(quotesApp, DevTools.instrument())
The syntax confuses me as opposed to the following syntax from the redux-devtools documentation.
What happens to initialState? In the example there is no reference to initialState anywhere.
The store enhancer definition signature looks roughly like this (snipped from the definition of `applyMiddleware):
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
// snip actual enhancer logic
return {
...store,
dispatch
}
}
}
So, the enhancer definition actually returns a function that takes a reference to the createStore function itself.
Unfortunately, somehow people seem to have copied that very functional-oriented calling pattern from somewhere, which is really hard to understand. Not sure if it was in an earlier version of the docs, or what. Note that that particular usage pattern doesn't allow defining initialState (or, as it's about to be renamed, preloadedState).
So yes, the current definition pattern, and the one that I think is much more readable, is:
const middlewares = [thunk, myMiddleware];
const middlewareEnhancer = applyMiddleware(...middlewares);
const enhancers = compose(middlewareEnhancer, someOtherEnhancer);
const store = createStore(reducer, preloadedState, enhancers);

Resources