New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Does dispatch
function identity change when passed in context? [Re: eslint-plugin-react-hooks]
#1889
Comments
@duncanleung any luck on this? I'm having the same issue |
If you consume |
@rbreahna I believe I may have manually turned off the eslint check for this one hook
|
@alexkrolick that's very inconvenient. Many people are building their own redux analog with context and useReducer and consuming dispatch from context in that way. Disabling the eslint rule on every occurrence should not be the standard approach. I hope that this gets a fix or some guidance. Maybe a config to ignore functions of a specific name in .eslintrc. |
I came here to ask a similar question or perhaps the same exact question. The following is my experimental code with This is in my parent component: const [accounts, dispatch] = React.useReducer((state, action) => {
const { type, id, account } = action;
if (type === "update") {
return {
...state,
[id]: account,
};
}
}, {});
const updateAccount = React.useCallback(
(id, account) => {
dispatch({
type: "update",
id,
account,
});
},
[dispatch],
); Here's how my child component calls React.useEffect(() => {
updateAccount(account.id, {
id: account.id,
fieldX,
fieldY,
});
}, [account, updateAccount, fieldX, fieldY]); After creating this code and upon remembering the documentation referenced here - I naively thought "OK, I can remove But after trying it out, obviously my child components final So, maybe the docs should remove |
@waynebloss you can omit class Foo extends React.Component {
state = { count: 0 };
// this function has a stable identity when re-rendering
handleClick = () => { this.setState(({count}) => ({ count: count + 1 })) };
render() {
return <MemoizedButtonOrSomething onClick={this.handleClick} />;
}
} |
@alexkrolick Thanks. That works unless I wrap my call to So, I guess that's why we're here on this issue :) The docs definitely need to be clarified or the eslint rule fixed to detect where /**
* Calls `React.useReducer` with the given action handler mapping, returning
* state and a dispatch method.
* @template R
* @template I
* @param {{ [action:string]: React.ReducerAction<R> }} handlers
* @param {(I & React.ReducerState<R>)} [initialState]
* @param {((arg: I & React.ReducerState<R>) => React.ReducerState<R>)} [initializer]
*/
export function useReducerMap(handlers, initialState, initializer) {
/**
* @param {(I & React.ReducerState<R>)} state
* @param {React.ReducerAction<R>} action
*/
function reduceWithHandler(state, action) {
const { type } = action;
const handler = handlers[type];
if (typeof handler !== "function") {
throw new Error("Unknown action type: " + type);
}
return handler(state, action);
}
return React.useReducer(reduceWithHandler, initialState, initializer);
} |
@alexkrolick Actually, it doesn't work because when I do that, it caused the following error:
|
@alexkrolick OK, I omitted the entire array |
Another solution that will probably work here is to wrap /**
* Hook to run a handler once on mount and never again.
* @param {() => void} handler Function to run on mount.
* See https://reactjs.org/docs/hooks-reference.html#useeffect
* See https://github.com/facebook/create-react-app/issues/6880
* This function is mainly to provide a better signal to the developer than
* knowing how `useEffect` works when you pass an empty array. It also helps to
* get around warnings raised by `react-hooks/exhaustive-deps` and we use it
* instead of `// eslint-disable-next-line react-hooks/exhaustive-deps`.
*/
export function useOnMount(handler) {
// Passing an empty array to `useEffect` causes the handler to only run once.
// See the final API notes for `useEffect` in the docs (link above).
return React.useEffect(handler, []);
} |
Here it is: /**
* Hook to create a callback function with `react-hooks/exhaustive-deps` disabled,
* such as for making a call with `dispatch` from `React.useReducer`.
* @param {() => void} handler The function that probably uses `dispatch`.
* See https://reactjs.org/docs/hooks-reference.html#usecallback
* See https://github.com/reactjs/reactjs.org/issues/1889
* This function is mainly to provide a better signal to the developer than
* knowing how `useDispatch` works when you pass an empty array. It also helps
* get around warnings raised by `react-hooks/exhaustive-deps` and we use it
* instead of `// eslint-disable-next-line react-hooks/exhaustive-deps`.
*/
export function useFunction(handler) {
return React.useCallback(handler, []);
}
// Used here:
/**
* State reducer hook for editing objects by id.
* @template R
* @template I
* @param {{ [action:string]: React.ReducerAction<R> }} [handlers]
* @param {(I & React.ReducerState<R>)} [initialState]
* @param {((arg: I & React.ReducerState<R>) => React.ReducerState<R>)} [initializer]
* @returns {([ I, { [action:string]:(...args:any[])=>any }, React.Dispatch<any> ])}
*/
export function useEditByIdState(handlers, initialState = {}, initializer) {
// #region Handlers
const defaultHandlers = {
update(state, { id, value }) {
return {
...state,
[id]: value,
};
},
updateAll(state, { values }) {
return {
...state,
...values,
};
},
updateField(state, { id, field, value }) {
return {
...state,
[id]: {
...state[id],
[field]: value,
},
};
},
};
if (!handlers) {
handlers = defaultHandlers;
} else {
handlers = {
...handlers,
...defaultHandlers,
};
}
// #endregion
const [state, dispatch] = useReducerMap(handlers, initialState, initializer);
// #region Action Dispatchers
const actions = {
update: useFunction((id, value) => dispatch({ type: "update", id, value })),
updateField: useFunction((id, field, value) =>
dispatch({ type: "updateField", id, field, value }),
),
updateAll: useFunction(values => dispatch({ type: "updateAll", values })),
};
// Could probably use something like `bindActionCreators` from `redux` on these
// dispatchers, but let's not go crazy!
// #endregion
return [state, actions, dispatch];
} My new parent component code: const [accounts, { update: updateAccount }] = useEditByIdState(); Child component code stayed the same. Perhaps I could name these reducers better than |
I'd rather add eslint-disable-line. code will be too complicated to keep the eslint rules for this. |
Hi, any update on this? I beleive adding eslint-disable-line is worse than just adding the unnecessary dependencies, because in that case you wouldn't get a warning if later you modified the code and used some state value or something, but forgot to add it to the dependency list. Would it be possible to add an extra parameter to eslint-disable-line (like |
Issue:
The docs for useReducer suggests omitting
dispatch
from the dependencies list ofuseEffect
anduseReducer
.The docs also recommend passing
dispatch
function fromuseReducer
via context to avoid passing callbacks down a large component tree:eslint-plugin-react-hooks warns if
dispatch
(that's been passed from context) is not added touseEffect
dependencies list.This conflicts with the first point above (docs suggesting omitting
dispatch
from the dependencies list)Is this:
dispatch
through context - so the docs should have a small amendment for this scenario?eslint-plugin-react-hooks
?Thanks for the help!!
Working Sample:
The text was updated successfully, but these errors were encountered: