I'm trying to use the second parameter of setState to pass a callback function, but it appears (from what I can gather) that the server-side renderer ignores this parameter completely. I'm using Gatsby which utilizes server-side rendering to build a static React-based site. My call is in an onChange handler, and looks like this:
this.setState({ [event.target.name]: event.target.value }, () => { console.log('setState callback') })
The state is updated as expected, but the callback is never called. Note: The same issue applies whether I pass an object or a function for the first parameter. The component function looks like this:
ReactComponent.prototype.setState = function (partialState, callback) {
[...]
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
That updater's method, which lives in ReactUpdateQueue.js (according to the call stack) looks like this:
enqueueSetState: function (publicInstance, partialState)
I don't fully understand the build process for React, but I believe that method/file is coming from this file in the source:
/src/renderers/shared/server/ReactPartialRenderer.js
The only other place I can find this function defined is here:
/src/isomorphic/modern/class/ReactNoopUpdateQueue.js
enqueueSetState: function(
publicInstance,
partialState,
callback,
callerName,
) {
warnNoop(publicInstance, 'setState');
}
which looks like the correct method signature, but doesn't appear anywhere in the call stack when I debug the setState call in my code. This doesn't appear to be a problem with client-side rendered React components (I'll try to set up a simple repo to show this issue, but it doesn't appear replicable on CodePen etc.) I know I could use componentDidUpdate to accomplish what I need to do, but the callback is much more convenient in my instance, and I hate leaving a mystery like this unsolved. :)
Well, I figured it out, and turns out, as is too often the case, this was a self-inflicted error. The default Gatsby install uses React v15 but we wanted to use 16, so we added a direct dependency to it in package.json, which got built in to the resulting package. I still don't quite understand why the above mentioned version of enqueueSetState was called instead of the proper one, but removing the reference to react (and adding gatsby-plugin-react-next, which does what we want by simply pointing webpack to the newer version) fixed the issue.
At least this was a good excuse to get a little more familiar with the guts of React. Perhaps this will save somebody else some time in the future.
Related
I simulated my Context + DND problem in https://codesandbox.io/s/adoring-booth-33vqo . I have other components which will be added to this example and I will use a Context hook to share values across the page.
After the initial render, everything looks fine. The idea of the list is to change the order within itself and when ones changes the order with drag-drop, it throws an "Invalid Hook" error.
So the (first) real question is, what is triggering this error which is linked to the line
const { lang1Library, updateLang1Library } = useContext(LangContext)
;
Thanks in advance for your help.
Geo
It's not a good approach to provide a link for the whole project even if it is small. But I had a quick look and there's at least one thing you're doing wrong:
// DragEndFct.js
export default function DragEndFct(result, libName) {
const { lang1Library, updateLang1Library } = useContext(LangContext);
This is not React component, but it uses a hook - and it is wrong. Hooks have a special meaning in React and should be used properly (Rules of Hooks).
You can't use hooks inside regular functions and expect them to work. That is why you are getting that error.
So, there are many ways you can try to fix this. For instance, DragEndFct is a regular function, you can declare more arguments and pass stuff you get from context:
// you are using it in components right ?
function DragEndFct(result, libName, param3, param4) {}
// so you probably have access to the context there
// and can pass data from the context when you call it.
// something like this
onDragEnd={function (result) {
console.log();
DragEndFct(result, StaticVars.LANG1_LIBRARY_NAME, lang1Library, updateLang1Library);
}}
You could even make DragEndFct to be a React component - it can just return null (which means no UI will be rendered) but in that case you will have hooks and all other stuff there. It really depends on what you need and how you will use it.
I need some help wrapping my head around eslint react-hooks/exhaustive derps.
I ONLY want the effect to run when listenToMe has changed, but react-hooks/exhaustive derps is yelling at me to add history. This causes recursion. useEffect was React gold for me until this rule.
ESLint: React Hook useEffect has missing dependencies: 'history'. Either include them or remove the dependency array.(react-hooks/exhaustive-deps)
Can someone help me understand:
Why is it bad practice to only listen for changes you care about in useEffect?
What is the "right" way to only listen for specific changes on state change?
useEffect(() => {
if (listenToMe) {
const search = new URLSearchParams(location.search);
search.delete('q')
history.replace({ ...location, search: search.toString() });
}
}
}, [listenToMe]);
I've read through github and react, but I haven't read anything that clicks.
https://github.com/facebook/create-react-app/issues/6880
https://reactjs.org/docs/hooks-rules.html
Many others...
From the docs
It is only safe to omit a function from the dependency list if nothing in it (or the functions called by it) references props, state, or values derived from them.
The problem arises when you're using passed props or in your case, the history prop coming from a HOC or hook from react-router.
First of all, its passed as a prop, and props can inherently change.
Second of all, you're calling a history.push function. The linter doesn't know that push will always be the same function of the history class and that this function will not use any state or props itself.
The "right" way according to facebook is to move the function inside the effect, but that doesn't make sense if you're just reusing code from some file or using a function like history.push. So I think in your case the solution would be to wrap it in a useCallback with history in its own dependancy array. This is a last resort according to the devs.
Essentially, useCallback will simply return a memoized value instead of actually accessing the value and whenever the value in it's dependancy changes, there is a new callback with new memoized values.
History.push will of course always have the same identity so this somewhat of an anti-pattern.
Personally I have never had any problems passing in values like this. So I think writing a useCallback when you're just dealing with functions declared elsewhere (or any fully stable variable) is pointless and I find it reasonable to skip linting that line.
I keep the rule enabled as others have pointed out however, as its good to keep you on your toes about writing effects.
This eslint rule is only a hint useful in most situations, but in some situations you have to ignore it.
React.useEffect(()=> {
// I have some code with dependencies here but I only want to run it on mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
React disabled the auto fixer
https://github.com/facebook/react/issues/15204
I installed the new devDependancies (eslint#latest and eslint-plugin-react-hooks)
npm install eslint#latest eslint-plugin-react-hooks#latest --save-dev
I tested in IntelliJ IDEA 2020.1 (EAP Build #IU-201.5985.32) and it shows the warning, but ESLint Fix does not auto add dependancies to useEffect
ESLint Warning in IntelliJ
ESLint: React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array. If 'dispatch' changes too often, find the parent component that defines it and wrap that definition in useCallback.(react-hooks/exhaustive-deps)
VS Code has the "fix" in pre release 2.1.0-next.1, but I have not tested it.
https://github.com/microsoft/vscode-eslint/pull/814#issuecomment-587020489
https://github.com/microsoft/vscode-eslint/releases/tag/release%2F2.1.0-next.1
This does NOT answer the question, but helpfull to anyone who runs into react-hooks/exhaustive-deps auto "fix" issues.
Assume you are using React and you are writing a custom hook useSomething that returns the identical same thing each time it is invoked for the same component.
const something = useSomething()
// useSomething() at time X === useSomething() at time Y
If you now use this something value inside of a useEffect(() => ...) and you do not pass something as a dependency to the array of the second argument of useEffect then the linter will warn you:
React Hook useEffect has a missing dependency: 'something'. Either include it or remove the dependency array. (react-hooks/exhaustive-deps)
Of course ESLint cannot know that something will always stay identical (per component), but adding not-changing things like something to the dependency array of useEffect each time they are used is really annoying. Just deactivating react-hooks/exhaustive-deps does also not seem to be a good solution (nor using // eslint-disable-next-line react-hooks/exhaustive-deps).
Is there a better solution than to add things like that unnecessarily to the dependency array of useEffect just to make the Linter happy?
Please find a simple demo here:
https://codesandbox.io/s/sad-kowalevski-yfxcn [Edit: Please be aware that the problem is about the general pattern described above and not about this stupid little demo - the purpose of this demo is just to show the ESLint warning, nothing else]
[Edit] Please find an additional demo here:
https://codesandbox.io/s/vibrant-tree-0cyn1
Here
https://github.com/facebook/react/issues/14920#issuecomment-471070149
for example you can read this:
If it truly is constant then specifying it in deps doesn't hurt. Such as the case where a setState function inside a custom Hook gets returned to your component, and then you call it from an effect. The lint rule isn't smart enough to understand indirection like this. But on the other hand, anyone can wrap that callback later before returning, and possibly reference another prop or state inside it. Then it won’t be constant! And if you fail to handle those changes, you’ll have nasty stale prop/state bugs. So specifying it is a better default.
So maybe just adding that never-changing values to the dependency array of useEffect may yet be the best solution. Nevertheless I hoped there would be something like a ESLint react-hooks configuration possibility to define a list of hook names which whose return values should be considered as static.
The example is a little contrived but I suspect you may wish to create a new useEffect block without this dependency.
If the store is not changing though I'd question why you'd wish to console log it time. If you wish to log it only on change then you'd add someStore to your dependency array. It really depends on what you're trying to achieve and your seperation of concerns.
I'd argue that if someStore is used as part of whatever logic is handled in this effect then it does belong in your dependency array.
You could also alternatively move const something = useSomething() into your effect and extract it as a custom hook link
useEffect(() => {
console.log("Current state (may change)", someState);
}, [someState]);
useEffect(() => {
console.log("Current store (will never change)", someStore);
});
As I have understood it the following way to call an event handler is
+ compact and simple to read
-, but causes a new myWrapperFunc function to be created on every render
However, creating functions is cheap right? That that minus is insignificant, right?
Am I correct in my understanding that this way to pass an event handler with a parameter will not cause a new handler instance to be created on each render?
handler(event, val) {
...
}
<Component onClick={myWrapperFunc = (e) => handler(e, "myVal")}>
First of all, you are passing the function wrong. It should be:
<Component onClick={(e) => handler(e, "myVal")}>
If we talk about the main question, I'm not an expert but there is still not an agreement on this subject. There is a reality that your callback function is created in every render and this causes a performance loss. But how significant is this loss? This depends on your app probably.
Is it a big app which includes so many components that create callbacks like that. So, you should consider an optimization then. Some people say if you don't need optimization then don't bother with it :) Some says follow the best practices.
You can pass parameters to your functions without using them like that but somehow you should get these parameters in your component. If it is a prop then use it directly instead of passing it like that for example. Then use a separate function and its reference. You don't need to pass e for a callback, it is passed by automatically with callbacks.
handler () => {
use(event);
use(props.val);
use(val_variable_in_component;
...
}
..
<Component onClick={handler}>
I'm using jQuery to create event bindings in a ReactJS component's componentDidMount function, which seems like the right place to do this.
$('body').on('defaultSearchContext.registerQueryEditor', (function(_this) {
return function(event, component) {
_this.setState({
queryEditors: _this.state.queryEditors.concat([component])
});
};
})(this));
This code isn't actually run on componentDidMount, it's simply setting up the binding that later calls setState when the event fires. However, this generates the following warning every time this event triggers, which pollutes my console with dozens of warnings:
Warning: setState(...): Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
I have tried moving the setState code to a separate function like onEvent and calling that from the binding in componentDidMount but the warning is still produced.
Ideally, I'd like to create the binding in the proper place, indeed, there is some issue with doing it in componentDidMount. If not, I'd like to know if it's possible to silence the warning, or whether I should perhaps file a bug for ReactJS itself. If it helps, I'm using ReactJS 0.14.3 (latest at this time).
This is similar to, but not the same as React Js onClick inside render. In that case, the solution is to return an anonymous function to onClick, but that doesn't seem applicable to my situation.
You are trying to coordinate events between independent components. This is a fairly standard thing to do, and it doesn't require DOM events. The standard practice for doing this in React is to use a store/dispatcher pattern like Redux or Flux (I personally prefer redux). However, if this is part of a larger, not-completely-React application, then this may not be possible. If it is just for a small piece of an React app, it may still be overkill.
All you need is an object to coordinate events. An event is just a collection of callbacks, possibly typed or keyed. This requires nothing more than an object shared between two places. DOM Events are overkill; jQuery is overkill. You just need to trigger a callback.
This is a VERY SIMPLE event coordinator.
let simpleEventCoordinator = {
callbacks: new Map(),
getHandler(eventKey) {
let handler = this.callbacks.get(eventKey);
if (!handler) {
handler = new Set();
this.callbacks.set(eventKey, handler);
}
return handler;
},
registerCallback(eventKey, callback) {
this.getHandler(eventKey).add(callback);
},
removeCallback(eventKey, callback) {
this.getHandler(eventKey).delete(callback);
},
trigger(eventKey, data) {
this.getHandler(eventKey).forEach(c => c(data));
}
Keep a map of callbacks, which will be nameOfEvent => callback(). Call them when asked. Pretty straightforward.
I know nothing about how your components are structured, but you said they are independent. Let's say they look like this:
React.render((
<div>
<QueryManager />
<button onClick={() => simpleEvent.trigger('event')}>{'Update'}</button>
</div>
), document.body);
This is all your component needs to handle this event
componentDidMount() {
simpleEvent.registerCallback('event', this.update);
}
componentWillUnmount() {
simpleEvent.removeCallback('event', this.update);
}
update() {
//do some stuff
}
I've put together a very simple codepen demonstrating this.
Looking at the source code of where that warning is coming from, it appears that if some reference is maintained before an update is about to happen, it throws that warning. So maybe the way your mixing the jQuery events and react is creating a memory leak? Its hard to say exactly because of the lack of surrounding code to your snippet what else could be going on.