React useEffect in function - javascript

I've been trying to find a way to use useEffect like this, but I don't think I'm doing it properly:
const [searching, setSearching] = useState(false);
function OnSearch(e) {
const searchValue = e.target.value;
useEffect(() => {
setSearching(true);
}, []);
clearTimeout(timer);
timer = setTimeout(() => {
setSearching(false);
window.location.href = '/block/' + searchValue;
}, 2000);
}
Any kind of help or direction will be appreciated.

I assume from the useState you've used there that this code is inside a functional component. You don't need or want useEffect there, just remove it and do setSearching(true) directly:
const [searching, setSearching] = useState(false);
function OnSearch(e) {
const searchValue = e.target.value;
setSearching(true);
clearTimeout(timer);
timer = setTimeout(() => {
setSearching(false);
window.location.href = '/block/' + searchValue;
}, 2000);
}
(In fact, not only do you not need it, but you can't use hooks anywhere other than the top level of a component function or hook function. So not in an event handler.)
That leaves the question of the timer variable. It can't just be a local variable in the component function because that will get recreated on every render. For non-state instance data like that, you can use an object via useRef, then use properties on that object, which will be consistent for the lifetime of the component:
const [instance] = useRef({});
So that gives us:
const [instance] = useRef({});
const [searching, setSearching] = useState(false);
function OnSearch(e) {
const searchValue = e.target.value;
setSearching(true);
clearTimeout(instance.timer);
instance.timer = setTimeout(() => {
setSearching(false);
window.location.href = '/block/' + searchValue;
}, 2000);
}
Side note: In JavaScript, the overwhelmingly-common convention is that only constructor functions and (in React) component functions are capitalized, not other kinds of functions. So onSearch rather than OnSearch. You can do what you like in your own code of course, but when working with others or asking for help, sticking to conventions helps keep communication clear.

Related

Passing element to composable function in Vue composition API?

I'll preface this question by saying I'm very new to the composition API so I'm still trying to figure out the best practices. I'm writing a useResizeObserver composable function using Vue's composition API in order to track dimension changes on a target element. Because I have to wait for the element to be mounted, I can't call my composable from top-level in the setup() method on my component as most examples show, but have to call it from onMounted() instead, where I have access to my element's ref.
onMounted(async () => {
elementDimensions.value = useResizeObserver(modalContent.value);
}
This forced me to have another ref that I named elementDimensions actually declared at the top level of the setup() function, which I initialise to null.
setup(props) {
const elementDimensions = ref(null);
const modalContent = ref(null);
...
}
Here is the useResizeObserver function itself:
export default function useResizeObserver(element) {
const elementHeight = ref(0);
const elementWidth = ref(0);
const resizeObserver = new ResizeObserver(entries => {
elementHeight.value = entries[0].contentRect.height;
elementWidth.value = entries[0].contentRect.width;
});
if (element) {
resizeObserver.observe(element);
}
onUnmounted(() => {
resizeObserver.disconnect();
});
return { elementHeight, elementWidth };
}
This actually works well to give me what I want but I'm wondering if there's a better way to achieve this. The way things are implemented right now, I end up with "nested" refs, since I end up wrapping elementHeight and elementWidth (which are already refs) into another ref inside the component (elementDimensions). Is there a better recommended way of doing this when you need to pass an element to a composable from onMounted()?
Composition functions are supposed to be used directly in setup, any other uses depend on the implementation and need to be verified.
Refs are basically objects that allow to pass a value by reference rather than by value. One of their uses is to pass a ref early and access when a value is up-to-date.
It should be:
setup(props) {
const modalContent = ref(null);
const elementDimensions = useResizeObserver(modalContent);
...
}
and
export default function useResizeObserver(element) {
const elementHeight = ref(0);
const elementWidth = ref(0);
let resizeObserver;
onMounted(() => {
if (element.value) {
resizeObserver = new ResizeObserver(...);
resizeObserver.observe(element.value);
}
}
onUnmounted(() => {
if (resizeObserver)
resizeObserver.disconnect();
});
return { elementHeight, elementWidth };
}

What is the scoping logic behind React states being used inside functions in custom hooks?

So say you have a custom hook:
useCustomHook()=>{
const [state, setState] = React.useState(0);
const modifyState = ({state, n}) => {setState(state + n);}
/*Does state need to be included as an argument/parameter here in modifyState? If, alternatively,
const modifyState = ({n}) => {setState(state + n)};
Will state always be 0 in the scope of modifyState, since that was its value when the function was
created originally. So everytime modifyState is called, it is equivalent to (n)=>setState(0+n) ?
*/
return [state, modifyState];
}
const FunctionalComponent = () => {
const [state, modifyState] = useCustomHook();
const n = 5;
modifyState({state,n}) /* Does state need to be passed here? (since useCustomHook already has its own
copy of state) */
//... logic ....
return <div></div>
}
From doing some testing in the console, it appears that state doesn't need to be passed as an argument to modifyState. But, I'm confused as to the scoping logic behind this, and am unsure if the behavior hooks would change it. Could someone explain the logic behind this?
Instead of passing the state you can make use of function implementation of setState. Something like this:
const modifyState = (n) => {
setState(state => state + n);
}
Ref: https://reactjs.org/docs/hooks-reference.html#functional-updates

Is it possible to remove and readd React eventListener within the handler function?

I am working with something like fullpage.js with React, and I need to remove the eventListener while the transition is ongoing.
Is it possible?
React code
function App() {
const wheelHandler = (event) => {
// I need to remove wheelHandler here
setTimeout(() => {
// I need to readd wheelHandler here
}, 1000); // Assume that the transition ends after 1000ms
};
return (
<div className="App" onWheel={wheelHandler} />
);
}
Vanilla JS equivalent
const wheelHandler = (event) => {
window.removeEventListener(wheelHandler);
setTimeout(() => {
window.addEventListener(wheelHandler);
}, 1000);
};
window.addEventListener(wheelHandler);
P.S. I tried the Vanilla JS solution on React but the event handler got triggered multiple times on one wheel scroll. Therefore I got no choice but React's SyntheticEvent.
With the way you're hooking it up, you can't without using a piece of state that tells you whether to hook up the handler and re-rendering, which is probably overkill.
Instead, I'd set a flag (perhaps on an object via a ref) telling the handler to ignore calls during the time you want calls ignored.
Something long these lines:
function App() {
const {current: scrolling} = useRef({flag: false});
const wheelHandler = (event) => {
// I need to remove wheelHandler here
if (scrolling.flag) {
// Bail out
return;
}
scrolling.flag = true;
// ...other logic if any...
setTimeout(() => {
// I need to readd wheelHandler here
scrolling.flag = false;
}, 1000); // Assume that the transition ends after 1000ms
};
return (
<div className="App" onWheel={wheelHandler} />
);
}
Or you can also do it like this, you don't need an extra object (I tend to prefer to use a single ref that holds all of my non-state instance data, but you don't have to):
function App() {
const scrolling = useRef(false);
const wheelHandler = (event) => {
// I need to remove wheelHandler here
if (scrolling.current) {
// Bail out
return;
}
scrolling.current = true;
// ...other logic if any...
setTimeout(() => {
// I need to readd wheelHandler here
scrolling.current = false;
}, 1000); // Assume that the transition ends after 1000ms
};
return (
<div className="App" onWheel={wheelHandler} />
);
}
As they say in the useRef documentation, refs are useful for non-state instance information:
However, useRef() is useful for more than the ref attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.

Is there anyway to set a variable equal to function that is called everytime the variable is accessed

I am trying to re-create the 'useState' hook from React as a silly fun personal exercise, but am encountering trouble when accessing variables from my 'global state'. I know that this has no practical use outside of react, but I just thought it would be something to attempt regardless.
Currently I have the following implementation below, but because the destructured variable is set only that first time and is not updated when using the corresponding setter, it always will return the same variable. I fully understand why this is happening but I am unsure if there is a way to get this working at all or if this is a lost cause. The destructured setter does update the global state, but the variable is of course as previously mentioned not accessing the global state again since it is only set that initial time.
let PROP_ID = 0;
let GLOBAL_STATE = {};
const useState = prop => {
const id = PROP_ID++;
GLOBAL_STATE[id] = prop;
return [
(() => {
return GLOBAL_STATE[id];
})(),
function(nv) {
GLOBAL_STATE[id] = nv;
}
];
};
const [userName, setUserName] = useState("Chris");
const [favCol, setFavCol] = useState("red");
console.log(GLOBAL_STATE);
console.log(userName);
setUserName("Bob");
console.dir(GLOBAL_STATE);
console.log(userName);
All I want to know if there is a way to set the destructured reference variable equal to some sort of function that will always be called to get the new variable from the global state when that variable is referenced.
I think you're missing a piece of the puzzle here.
React hooks depend on the position of their call within a given functional component. Without the encapsulating function, you remove the usefulness of the state being provided by the hook, because they're only being called once in your example, thus the reference in the destructuring syntax never gets updated as you observed.
Let's get them working in the context of functions.
const { component, useState } = (function () {
const functions = new WeakMap()
const stack = []
let hooks
let index
function component (fn) {
return function (...args) {
try {
stack.push({ hooks, index })
hooks = functions.get(fn)
index = 0
if (!hooks) {
functions.set(fn, hooks = [])
}
return fn.apply(this, args)
} finally {
({ hooks, index } = stack.pop())
}
}
}
function useState (initialValue) {
const hook = index++
if (hook === hooks.length) {
hooks.push(initialValue)
}
return [
hooks[hook],
function setState (action) {
if (typeof action === 'function') {
hooks[hook] = action(hooks[hook])
} else {
hooks[hook] = action
}
}
]
}
return { component, useState }
})()
const fibonacci = component(function () {
const [a, setA] = useState(1)
const [b, setB] = useState(1)
setA(b)
setB(a + b)
return a
})
const sequence = component(function () {
const [text, setText] = useState('')
setText(
text.length === 0
? fibonacci().toString()
: [text, fibonacci()].join()
)
return text
})
for (let i = 0; i < 20; i++) {
console.log(sequence())
}
The stack variable here allows us to nest our stateful function calls, and the hooks variable keeps track of the existing hook states by position within the currently executing component of the stack.
This implementation might seem overly-complicated, but the point of component() and stack is to partially mimic how the React framework treats functional components. This is still much simpler than how React works, because we're treating all calls of the same function as if it's the same instance of a functional component.
On the other hand, in React, a particular function could be used for several different instances, distinguishable from each other based on a number of factors such as the position in the hierarchy of the virtual DOM, the key and ref props, etc., so it's much more complicated than this.
It occurs to me you just want to get your example working. For that, all you need to do is change your variable to a getter function:
const useState = state => [
() => state,
value => { state = value }
];
const [getUserName, setUserName] = useState('Chris');
const [getFavCol, setFavCol] = useState('red');
console.log(getUserName());
setUserName('Bob');
console.log(getUserName());
Much simpler than what you had and doesn't require any globals to work.
If the manual getter seems too inconvenient, then you can't destructure, but you can implement an approach that's almost as easy to use:
const useState = state => ({
get state () { return state },
set (value) { state = value }
});
const userName = useState('Chris');
const favCol = useState('red');
console.log(userName.state);
userName.set('Bob');
console.log(userName.state);
This is a very interesting question.
The short answer is: no, that’s not possible*.
The long answer
The long answer is how JavaScript handles primitives and objects. Primitives values are copied during assignment (userName here: const [userName, setUserName] = useState("Chris"); which is a string), while in case of object a reference would be copied.
In order to play with it, I came with something like that (mind you, that is not solution to your challenge, rather explanation to my answer):
let PROP_ID = 0;
let GLOBAL_STATE = {};
const useState = prop => {
const id = PROP_ID++;
GLOBAL_STATE[id] = {
_value: prop,
[Symbol.toPrimitive]() { return this._value },
}
const tuple = [
GLOBAL_STATE[id],
(nv) => GLOBAL_STATE[id]._value = nv,
];
return tuple;
};
const [userName, setUserName] = useState("Chris");
const [favCol, setFavCol] = useState("red");
console.log(GLOBAL_STATE);
console.log(userName);
console.log('set user name to:', setUserName("Bob"));
console.dir(GLOBAL_STATE);
console.log('' + userName);
GLOBAL_STATE entry is now object, so when you destructure it after calling useState only a reference is changed. Then update changes data inside this object but what we assigned in the first place is still there.
I added Symbo.toPrimitive property which coerses object to a primitive value but sadly, this will not work on it’s own. Only when run as '' + userName. Which means it behaves differently than you expected. At this point I stopped experimenting.
React
I went to Facebook’s Github and tried to trace what they are doing but gave up due to imports of imports of imports. Hence, I will take an educated guess here, based on Hooks API behaviour. I think that your implementation is rather faithful to the original. We use useState in a function and the value doesn’t change there. Only when state is changed and then the component is re-rendered with a new value, which again is assigned and won’t change.
*I will gladly welcome anyone who proves this notion wrong.
How about something along the following lines...
let PROP_ID = 0;
let GLOBAL_STATE = {};
const useState = (varName, prop) => {
const id = PROP_ID++;
GLOBAL_STATE[id] = prop;
Object.defineProperty(window, varName, {
get: function(){
return GLOBAL_STATE[id];
}
});
return ((nv) => {
GLOBAL_STATE[id] = nv;
});
};
const setUserName = useState("userName", "Chris");
const setFavCol = useState("favCol", "red");
console.log(GLOBAL_STATE);
console.log(userName);
setUserName("Bob");
console.dir(GLOBAL_STATE);
console.log(userName);
Note that I've changed your interface a bit such that you have to pass the name of the variable to the useState function. Seems a bit kludgey, but allows a getter to be configured, in this case, following your example, on the global scope (ie, "window"), which might not be the best practice.
There is a solution, but only if you're okay using a dirty, dirty hack.
The following approach uses a with statement and a Proxy containing a custom get handler, and requires object destructuring syntax in order to determine the variable name from the property key of the setter function:
// initialize useState() hook with independent scope
function createHook (scope = Object.create(null)) {
const setter = /^set([A-Z][^\W_]*)$/;
function useState (initialValue) {
// return proxy from useState() so that object destructuring syntax
// can be used to get variable name and initialize setter function
return new Proxy(scope, {
get (target, propertyKey) {
if (!setter.test(propertyKey)) {
throw new TypeError(`Invalid setter name '${propertyKey}'`);
}
// get variable name from property key of setter function
const [, name] = propertyKey.match(setter);
const key = name[0].toLowerCase() + name.slice(1);
// support updater callback
const setState = value => {
target[key] = (
typeof value === 'function'
? value(target[key])
: value
);
};
// initialize state
setState(initialValue);
// return setter as property value
return setState;
}
});
}
return { scope, useState };
}
// example usage with a little magic
{
const { scope, useState } = createHook();
const { setFoo } = useState('bar');
console.log(scope.foo);
setFoo(42);
console.log(scope.foo);
}
// example use with more magic
const { scope, useState } = createHook();
with (scope) {
const { setUserName } = useState('Chris');
const { setFavCol } = useState('red');
console.log(userName, favCol);
setUserName('Bob');
setFavCol(color => `dark${color}`);
console.log(userName, favCol);
}
The following usage ends up being very similar to Jon Trent's answer by abusing implicit globals:
function createHook(e=Object.create(null)){var t=/^set([A-Z][^\W_]*)$/;return{scope:e,useState:n=>new Proxy(e,{get(e,r){if(!t.test(r))throw new TypeError(`Invalid setter name '${r}'`);var[,o]=r.match(t),c=o[0].toLowerCase()+o.slice(1),s=t=>{e[c]="function"==typeof t?t(e[c]):t};return s(n),s}})}}
const { useState } = createHook(window);
const { setUserName } = useState('Chris');
const { setFavCol } = useState('red');
console.log(userName, favCol);
setUserName('Bob');
setFavCol(color => `dark${color}`);
console.log(userName, favCol);

How does React.useState triggers re-render?

import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
In the above example whenever setCount(count + 1) is invoked a re-render happens. I am curious to learn the flow.
I tried looking into the source code. I could not find any reference of useState or other hooks at github.com/facebook/react.
I installed react#next via npm i react#next and found the following at node_modules/react/cjs/react.development.js
function useState(initialState) {
var dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
On tracing back for dispatcher.useState(), I could only find the following ...
function resolveDispatcher() {
var dispatcher = ReactCurrentOwner.currentDispatcher;
!(dispatcher !== null) ? invariant(false, 'Hooks can only be called inside the body of a function component.') : void 0;
return dispatcher;
}
var ReactCurrentOwner = {
/**
* #internal
* #type {ReactComponent}
*/
current: null,
currentDispatcher: null
};
I wonder where can I find dispatcher.useState() implementation and learn how it triggers re-render when setState setCount is invoked.
Any pointer would be helpful.
Thanks!
The key in understanding this is the following paragraph from the Hooks FAQ
How does React associate Hook calls with components?
React keeps track of the currently rendering component. Thanks to the Rules of Hooks, we know that Hooks are only called from React components (or custom Hooks — which are also only called from React components).
There is an internal list of “memory cells” associated with each component. They’re just JavaScript objects where we can put some data. When you call a Hook like useState(), it reads the current cell (or initializes it during the first render), and then moves the pointer to the next one. This is how multiple useState() calls each get independent local state.
(This also explains the Rules of Hooks. Hooks need to be called unconditionally in the same order, otherwise the association of memory cell and hook is messed up.)
Let's walk through your counter example, and see what happens. For simplicity I will refer to the compiled development React source code and React DOM source code, both version 16.13.1.
The example starts when the component mounts and useState() (defined on line 1581) is called for the first time.
function useState(initialState) {
var dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
As you have noticed, this calls resolveDispatcher() (defined on line 1546). The dispatcher refers internally to the component that's currently being rendered. Within a component you can (if you dare to get fired), have a look at the dispatcher, e.g. via
console.log(React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher.current)
If you apply this in case of the counter example, you will notice that the dispatcher.useState() refers to the react-dom code. When the component is first mounted, useState refers to the one defined on line 15986 which calls mountState(). Upon re-rendering, the dispatcher has changed and the function useState() on line 16077 is triggered, which calls updateState(). Both methods, mountState() on line 15352 and updateState() on line 15371, return the count, setCount pair.
Tracing ReactCurrentDispatcher gets quite messy. However, the fact of its existence is already enough to understand how the re-rendering happens. The magic happens behind the scene. As the FAQ states, React keeps track of the currently rendered component. This means, useState() knows which component it is attached to, how to find the state information and how to trigger the re-rendering.
setState is a method on the Component/PureComponent class, so it will do whatever is implemented in the Component class (including calling the render method).
setState offloads the state update to enqueueSetState so the fact that it's bound to this is really only a consequence of using classes and extending from Component. Once, you realize that the state update isn't actually being handled by the component itself and the this is just a convenient way to access the state update functionality, then useState not being explicitly bound to your component makes much more sense.
I also tried to understand the logic behind useState in a very simplified and basic manner, if we just look into its basic functionalities, excluding optimizations and async behavior, then we found that it is basically doing 4 things in common,
maintaining of State, primary work to do
re-rendering of the component through which it get called so that caller component can get the latest value for state
as it caused the re-rendering of the caller component it means it must maintain the instance or context of that component too, which also allows us to use useState for multiple component at once.
as we are free to use as many useState as we want inside our component that means it must maintain some identity for each useState inside the same component.
keeping these things in mind I come up with the below snippet
const Demo = (function React() {
let workInProgress = false;
let context = null;
const internalRendering = (callingContext) => {
context = callingContext;
context();
};
const intialRender = (component) => {
context = component;
workInProgress = true;
context.state = [];
context.TotalcallerId = -1; // to store the count of total number of useState within a component
context.count = -1; // counter to keep track of useStates within component
internalRendering(context);
workInProgress = false;
context.TotalcallerId = context.count;
context = null;
};
const useState = (initState) => {
if (!context) throw new Error("Can only be called inside function");
// resetting the count so that it can maintain the order of useState being called
context.count =
context.count === context.TotalcallerId ? -1 : context.count;
let callId = ++context.count;
// will only initialize the value of setState on initial render
const setState =
!workInProgress ||
(() => {
const instanceCallerId = callId;
const memoizedContext = context;
return (updatedState) => {
memoizedContext.state[instanceCallerId].value = updatedState;
internalRendering(memoizedContext);
};
})();
context.state[callId] = context.state[callId] || {
value: initState,
setValue: setState,
};
return [context.state[callId].value, context.state[callId].setValue];
};
return { useState, intialRender };
})();
const { useState, intialRender } = Demo;
const Component = () => {
const [count, setCount] = useState(1);
const [greeting, setGreeting] = useState("hello");
const changeCount = () => setCount(100);
const changeGreeting = () => setGreeting("hi");
setTimeout(() => {
changeCount();
changeGreeting();
}, 5000);
return console.log(`count ${count} name ${greeting}`);
};
const anotherComponent = () => {
const [count, setCount] = useState(50);
const [value, setValue] = useState("World");
const changeCount = () => setCount(500);
const changeValue = () => setValue("React");
setTimeout(() => {
changeCount();
changeValue();
}, 10000);
return console.log(`count ${count} name ${value}`);
};
intialRender(Component);
intialRender(anotherComponent);
here useState and initialRender are taken from Demo. intialRender is use to call the components initially, it will initialize the context first and then on that context set the state as an empty array (there are multiple useState on each component so we need array to maintain it) and also we need counter to make count for each useState, and TotalCounter to store total number of useState being called for each component.
FunctionComponent is different. In the past, they are pure, simple. But now they have their own state.
It's easy to forget that react use createElement wrap all the JSX node, also includes FunctionComponent.
function FunctionComponent(){
return <div>123</div>;
}
const a=<FunctionComponent/>
//after babel transform
function FunctionComponent() {
return React.createElement("div", null, "123");
}
var a = React.createElement(FunctionComponent, null);
The FunctionComponent was passed to react. When setState is called, it's easy to re-render;

Categories

Resources