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;
Related
I have a dispatch updating the state correctly when i was in this scenario with 2 methods ( onClick )
const sizesMeasureChoise = useSelector(getSizesMeasureChoise);
const chooseMeasure = (measure) => {
dispatch(setSizeMeasureChoise(measure));
};
const chooseType = (type) => {
const sizeChoise = sizesMeasureChoise.description;
}
Then i have changed and i have put everything in one method ( because i have removed the chooseType step ) and the state is not updated after the dispatch
const sizesMeasureChoise = useSelector(getSizesMeasureChoise);
const chooseMeasure = (measure) => {
dispatch(setSizeMeasureChoise(measure));
const sizeChoise = sizesMeasureChoise.description;
}
From what is depending, how can i resolve it?
Once an action has been dispatched, updated values from the store will become available only on the next render.
This behavior is the same for both functional components with hooks as well as with classes that use the connect HOC.
You'll just have to modify your code so as not to expect changes immediately.
If you could describe what you want to get (rather than "how") with a minimal sample with only the relevant hooks & how data is rendered then it will be
easier to suggest a solution.
Ok, I've looked extensively at useState set method not reflecting change immediately and I understand that React.useState variables are async.
This is a problem given that I'm working with a component structured React Native app, and need to set and use a variable immediately upon releasing drag of a component. I am doing this within a callback function of a Draggable from https://github.com/tongyy/react-native-draggable#readme , so the callback hook solution from useState set method not reflecting change immediately won't work.
Here's what I mean. I start with these React.useState
export default function App() {
const [firstColor, setFirstColor] = React.useState('');
const [secondColor, setSecondColor] = React.useState('');
Then I update them based on the position of the Draggable on the OnDragRelease callback function:
<Draggable onDragRelease={(event, gestureState) => { ToggleComboColor1(event, gestureState); ToggleComboPage(); } } onPressIn={() => {setDidDrag1(true);}} />
Here's where there is trouble. I have to set the useState variable based on gestureState, then if both Draggables have been dragged, toggle a result with ToggleComboPage() :
const ToggleComboColor1 = (event, gestureState) => {
setFirstColor(determineDraggableColor(gestureState.moveX, gestureState.moveY));
console.log(firstColor); //This does NOT print an updated value
}
const ToggleComboPage = () =>
{
if(didDrag1 && didDrag2)
{
console.log(firstColor + " | "+secondColor); //This, consequently, does NOT print an updated value for the most recently dragged
// setCurrentColorCombo(
// getColorComboItemArray(firstColor, secondColor)[0]
// );
// setCurrentKey("Combo");
// setDidDrag1(false);
// setDidDrag2(false);
}
}
I tried using a plain var to circumvent this, but those are re rendered / re created every time there's a re render.
How can I update and use these variables here?
I have read A Complete Guide to useEffect - Swimming Against the Tide at Overreacted.
The example shows that if we want to get the latest count, we can use useRef to save the mutable variable, and get it in async function laster:
function Example() {
const [count, setCount] = useState(0);
const latestCount = useRef(count);
useEffect(() => {
// Set the mutable latest value
latestCount.current = count;
setTimeout(() => {
// Read the mutable latest value
console.log(`You clicked ${latestCount.current} times`);
}, 3000);
});
// ...
}
However, I can do the same thing by creating a variable outside the component function, such as:
import React, { useState, useEffect, useRef } from 'react';
// defined a variable outside function component
let countCache = 0;
function Counter() {
const [count, setCount] = useState(0);
countCache = count; // set default value
useEffect(() => {
setTimeout(() => {
// We can get the latest count here
console.log(`You clicked ${countCache} times (countCache)`);
}, 3000);
});
// ...
}
export default Counter;
Are both ways practical, or is there anything bad if I define the variable outside function component?
useRef will assign a reference for each component, while a variable defined outside a function component scope will only assigned once.
useRef reference life span is component's life span (it "dies" when the component unmounts, while JS variables are scope-blocked).
Hence, define constant purpose variables outside of the component scope:
// This statement will only called once
const DEFAULT_VALUE = 5;
function Component() {
// use DEFAULT_VALUE.
}
Defining the same statement inside component's scope, will redefine it on every render:
// We can do better
function Component() {
// Redefined on every render
const DEFAULT_VALUE = 5;
}
Now for the question:
First, we can't actually reflect UI changed with outer scoped variables since changing them does not trigger render (only React API does).
Therefore the reflected value is its closure value.
let countCache = 0;
function Counter() {
...
countCache = 0;
useEffect(() => {
countCache = count;
});
...
// closure value of countCache
return <div>{countCache}</div>
}
Now, whats special with outer scope variables that they are global to the module itself, so using its value is global to all components referencing it (in the module).
For example if you want to count how many times the component mounted in your whole application life span, increase the variable inside useEffect on mount (couldn't find any other possible use-case).
let howMuchMounted = 0;
function Component() {
useEffect(() => { howMuchMounted += 1, [] };
}
To reflect the differences of outer variable and useRef reference, in the next example, on button click, you may notice that the variable is global for both of the components, while the reference is always updated to current state value.
import React, { useEffect, useRef, useReducer } from "react";
import ReactDOM from "react-dom";
// defined a variable outside function component
let countCache = 0;
function Counter() {
const [num, count] = useReducer((num) => num + 1, 0);
const countRef = useRef(count);
useEffect(() => {
// set count value on count change
countCache = num;
countRef.current = num;
}, [num]);
return (
<>
<button onClick={count}>Count</button>
<h3>state {num}</h3>
<h3>variable {countCache}</h3>
<h3>reference {countRef.current}</h3>
</>
);
}
export default function App() {
return (
<>
<Counter />
<hr />
See what happens when you click on the other counter
<hr />
<Counter />
</>
);
}
Please see a follow up question on useEffect use cases, there are many common mistakes when working with useRef references inside useEffect.
There are differences between the following four possible variations:
A variable inside the function component
A variable outside the function component
A state variable returned by useState()
A variable ( an object with property 'current' ) returned by useRef()
The last 2 can only be used within the component.
Let's take each case:
1. A variable inside the function component
Initialization: Variables are always re-initialized both on every render and consequently in multiple component instances
Variable Updates: Can be updated except const
Render: No render is triggered by React, and hence updated values are not reflected
2. A variable outside the function component
Initialization: Variables are initialized only when the file is loaded. And the file is loaded only once, irrespective of how many components consume the exports from the file. This is the main difference compared to using useRef(). If there is only one component instantiation, it would be same as useRef().
Variable updates: Can be updated except const
Render: No render is triggered by React, and hence updated values are not reflected
3. A state variable returned by useState()
Initialization: Variables are initialized only when the component is mounted, irrespective of the number of renders. However, each component instance gets it's own copy of state variables.
Variable updates: Can be updated using the state updater function.
Render: Render is triggered by React after the state variable is updated ( multiple updates may be batched into a single render )
4. A variable ( an object with property 'current' ) returned by useRef()
Initialization: Variables are initialized only when the component is mounted, irrespective of the number of renders. However, this will independently initialize the variable for each component instance in contrast to when it occurs only once when using a variable outside the component
Variable updates: Can be updated using the 'current' property
Render: No render is triggered by React, and hence updated values are not reflected
Choosing any of the above will largely depend on the requirement, but we have the necessary tools in our toolbox to get almost all the use cases covered.
I encountered error in react wherein this is undefined. This is my first time developing a react application.
In UI, it says Unhandled Rejection (TypeError): Cannot read property 'setState' of undefined while in console the value of this is undefined.
Thank you for your help.
Here is the existing code:
import React, { useState, useEffect, useRef } from "react";
//import makeData from "../makeData";
import { useTableState } from "react-table";
import { Button } from "../Styles";
import Table from "../TransactionPanelTable";
// Simulate a server
const getServerData = async ({ filters, sortBy, pageSize, pageIndex }) => {
await new Promise(resolve => setTimeout(resolve, 500));
// Ideally, you would pass this info to the server, but we'll do it here for convenience
const filtersArr = Object.entries(filters);
// Get our base data
let rows = [];
for (let i = 0; i < 1000; i++) {
rows.push({
transaction_seq: 1234,
rec_count: 1234,
user_id: "test",
updated_at: "",
duration: 1.23
});
}
// Apply Filters
if (filtersArr.length) {
rows = rows.filter(row =>
filtersArr.every(([key, value]) => row[key].includes(value))
);
}
// Apply Sorting
if (sortBy.length) {
const [{ id, desc }] = sortBy;
rows = [...rows].sort(
(a, b) => (a[id] > b[id] ? 1 : a[id] === b[id] ? 0 : -1) * (desc ? -1 : 1)
);
}
// Get page counts
const pageCount = Math.ceil(rows.length / pageSize);
const rowStart = pageSize * pageIndex;
const rowEnd = rowStart + pageSize;
// Get the current page
rows = rows.slice(rowStart, rowEnd);
let checkedMap = new Map();
rows.forEach(row => checkedMap.set(row, false)); //preset each to false, ideally w/ a key instead of the entire row
this.setState({ checkedMap: checkedMap });
//handleCheckedChange(row) {
// let modifiedMap = this.state.checkedMap;
// modifiedMap.set(row, !this.state.checkedMap.get(row));
// this.setState({ checkedMap: modifiedMap });
//}
return {
rows,
pageCount
};
};
'this' is undefined because you haven't bounded the context of the component to the method.
As you are new to react, i would suggest you to go through concepts like bind,es6 and other js lingos so that you can code better and avoid such errors.
In this case you need to bind the context either by using bind method or by using es6 arrow functions which are class functions.
Add below code in constructor function where you are calling this getServerData function.
getServerData = getServerData.bind(this);
Here bind will return new method which will set context (this) to your calling class.
It is preferable to set state in class with in that promise resolved function.
First, let's answer your question. this is undefined because you are using ES6 modules and you are using an arrow function const getServerData = async (...) => {.
Inside arrow functions, the this binding is not dynamic, but is instead lexical. That means that this actually refers to the originating context (outside your arrow function).
In a basic js script, that would point to the Window global context. But you are using ES6 module, with import statements. One of the goals of ES6 modules is to isolate code. So inside such a module, there is no default access to a global Window context, so your this end up being undefined.
Now, several misconceptions are recognizable in your code.
writing an ES6 modules, you should have an export statement somewhere inside in order to consume it outside.
The request to a server should preferably be isolated in another function, so that the structure of your component will be more easy to apprehend, especially if you are learning React.
Read the very good React doc to understand the basics of it.
You should not use async when defining a React functional component
Since you have no state defined in your component (importing useState is not enough, you must use it: State Hooks doc), you cannot use setState.
this.setState refer to using React class syntax to define your component, useState refer to using State Hooks. You cannot do both in a same component. You have to choose.
So to sum-up, your getServerData function should be outside of your Component and should not have to do anything with state management. And you should build a React component that will call your function in a lifecycle method.
Stay confident, you will get it sooner than you think! ;-)
this.getServerData = this.getServerData.bind(this);
I had a class component named <BasicForm> that I used to build forms with. It handles validation and all the form state. It provides all the necessary functions (onChange, onSubmit, etc) to the inputs (rendered as children of BasicForm) via React context.
It works just as intended. The problem is that now that I'm converting it to use React Hooks, I'm having doubts when trying to replicate the following behavior that I did when it was a class:
class BasicForm extends React.Component {
...other code...
touchAllInputsValidateAndSubmit() {
// CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
let inputs = {};
for (let inputName in this.state.inputs) {
inputs = Object.assign(inputs, {[inputName]:{...this.state.inputs[inputName]}});
}
// TOUCH ALL INPUTS
for (let inputName in inputs) {
inputs[inputName].touched = true;
}
// UPDATE STATE AND CALL VALIDATION
this.setState({
inputs
}, () => this.validateAllFields()); // <---- SECOND CALLBACK ARGUMENT
}
... more code ...
}
When the user clicks the submit button, BasicForm should 'touch' all inputs and only then call validateAllFields(), because validation errors will only show if an input has been touched. So if the user hasn't touched any, BasicForm needs to make sure to 'touch' every input before calling the validateAllFields() function.
And when I was using classes, the way I did this, was by using the second callback argument on the setState() function as you can see from the code above. And that made sure that validateAllField() only got called after the state update (the one that touches all fields).
But when I try to use that second callback parameter with state hooks useState(), I get this error:
const [inputs, setInputs] = useState({});
... some other code ...
setInputs(auxInputs, () => console.log('Inputs updated!'));
Warning: State updates from the useState() and useReducer() Hooks
don't support the second callback argument. To execute a side effect
after rendering, declare it in the component body with useEffect().
So, according to the error message above, I'm trying to do this with the useEffect() hook. But this makes me a little bit confused, because as far as I know, useEffect() is not based on state updates, but in render execution. It executes after every render. And I know React can queue some state updates before re-rendering, so I feel like I don't have full control of exactly when my useEffect() hook will be executed as I did have when I was using classes and the setState() second callback argument.
What I got so far is (it seems to be working):
function BasicForm(props) {
const [inputs, setInputs] = useState({});
const [submitted, setSubmitted] = useState(false);
... other code ...
function touchAllInputsValidateAndSubmit() {
const shouldSubmit = true;
// CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
let auxInputs = {};
for (let inputName in inputs) {
auxInputs = Object.assign(auxInputs, {[inputName]:{...inputs[inputName]}});
}
// TOUCH ALL INPUTS
for (let inputName in auxInputs) {
auxInputs[inputName].touched = true;
}
// UPDATE STATE
setInputs(auxInputs);
setSubmitted(true);
}
// EFFECT HOOK TO CALL VALIDATE ALL WHEN SUBMITTED = 'TRUE'
useEffect(() => {
if (submitted) {
validateAllFields();
}
setSubmitted(false);
});
... some more code ...
}
I'm using the useEffect() hook to call the validateAllFields() function. And since useEffect() is executed on every render I needed a way to know when to call validateAllFields() since I don't want it on every render. Thus, I created the submitted state variable so I can know when I need that effect.
Is this a good solution? What other possible solutions you might think of? It just feels really weird.
Imagine that validateAllFields() is a function that CANNOT be called twice under no circunstances. How do I know that on the next render my submitted state will be already 'false' 100% sure?
Can I rely on React performing every queued state update before the next render? Is this guaranteed?
I encountered something like this recently (SO question here), and it seems like what you've come up with is a decent approach.
You can add an arg to useEffect() that should do what you want:
e.g.
useEffect(() => { ... }, [submitted])
to watch for changes in submitted.
Another approach could be to modify hooks to use a callback, something like:
import React, { useState, useCallback } from 'react';
const useStateful = initial => {
const [value, setValue] = useState(initial);
return {
value,
setValue
};
};
const useSetState = initialValue => {
const { value, setValue } = useStateful(initialValue);
return {
setState: useCallback(v => {
return setValue(oldValue => ({
...oldValue,
...(typeof v === 'function' ? v(oldValue) : v)
}));
}, []),
state: value
};
};
In this way you can emulate the behavior of the 'classic' setState().
I have tried to solve it using the useEffect() hook but it didn't quite solve my problem. It kind of worked, but I ended up finding it a little too complicated for a simple task like that and I also wasn't feeling sure enough about how many times my function was being executed, and if it was being executed after the state change of not.
The docs on useEffect() mention some use cases for the effect hook and none of them are the use that I was trying to do.
useEffect API reference
Using the effect hook
I got rid of the useEffect() hook completely and made use of the functional form of the setState((prevState) => {...}) function that assures that you'll get a current version of your state when you use it like that. So the code sequence became the following:
// ==========================================================================
// FUNCTION TO HANDLE ON SUBMIT
// ==========================================================================
function onSubmit(event){
event.preventDefault();
touchAllInputsValidateAndSubmit();
return;
}
// ==========================================================================
// FUNCTION TO TOUCH ALL INPUTS WHEN BEGIN SUBMITING
// ==========================================================================
function touchAllInputsValidateAndSubmit() {
let auxInputs = {};
const shouldSubmit = true;
setInputs((prevState) => {
// CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
for (let inputName in prevState) {
auxInputs = Object.assign(auxInputs, {[inputName]:{...prevState[inputName]}});
}
// TOUCH ALL INPUTS
for (let inputName in auxInputs) {
auxInputs[inputName].touched = true;
}
return({
...auxInputs
});
});
validateAllFields(shouldSubmit);
}
// ==========================================================================
// FUNCTION TO VALIDATE ALL INPUT FIELDS
// ==========================================================================
function validateAllFields(shouldSubmit = false) {
// CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
let auxInputs = {};
setInputs((prevState) => {
// CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
for (let inputName in prevState) {
auxInputs =
Object.assign(auxInputs, {[inputName]:{...prevState[inputName]}});
}
// ... all the validation code goes here
return auxInputs; // RETURNS THE UPDATED STATE
}); // END OF SETINPUTS
if (shouldSubmit) {
checkValidationAndSubmit();
}
}
See from the validationAllFields() declaration that I'm performing all my code for that function inside a call of setInputs( (prevState) => {...}) and that makes sure that I'll be working on an updated current version of my inputs state, i.e: I'm sure that all inputs have been touched by the touchAllInputsValidateAndSubmit() because I'm inside the setInputs() with the functional argument form.
// ==========================================================================
// FUNCTION TO CHECK VALIDATION BEFORE CALLING SUBMITACTION
// ==========================================================================
function checkValidationAndSubmit() {
let valid = true;
// THIS IS JUST TO MAKE SURE IT GETS THE MOST RECENT STATE VERSION
setInputs((prevState) => {
for (let inputName in prevState) {
if (inputs[inputName].valid === false) {
valid = false;
}
}
if (valid) {
props.submitAction(prevState);
}
return prevState;
});
}
See that I use that same pattern of the setState() with functional argument call inside the checkValidationAndSubmit() function. In there, I also need to make sure that I'm get the current, validated state before I can submit.
This is working without issues so far.