I intend to move out logic from my app.js. Right now, in app.js, I have a function that changes state of some variables in App.js. I want to learn what is the correct way to change the state inside app.js by letting app.js call a function (for example on click) that is in another component.
my app.js looks like the following:
const App = () => {
const [opacity, opacitySet] = React.useState(1);
const [showStep, setDisplayStep] = React.useState(true);
const [isQrActive, setQrState] = React.useState(false);
/** code **/
<ButtonContinue
onClick={() => {
stateChanger();
}}
style={{ backgroundColor: "black" }}
>
<p>cancel</p>
</ButtonContinue>
/** lots of code **/
function stateChanger() {
opacitySet(0);
setTimeout(() => {
opacitySet(1);
setDisplayStep(!showStep);
setQrState(!isQrActive);
}, 400);
}
/** and more code **/
}
I want to move the stateChanger function out from app.js, but still keep the same functionality. This in turn would allow me to call stateChanger from other classes easily.
The simplest solution (which is also the most difficult to scale) is to pass the function as a prop to its children. You could also create a custom hook. But by declaring the state on the root-level component App and asking about how you can generally modify it from another component, we can interpret that the question is essentially about how to handle global state. You may want to consider using the Context API or Redux, which are tools created specifically for that purpose.
The most important thing to remember here is that if we have the option of maintaining the state reasonably close to where it is being used and modified, we should do that instead of handling it as part of a global state.
Related
Here is the diagram. ChildComponentB has a state - stateX. In ChildComponentA, once the event occurs, it will change the stateX in ChildComponentB.
If the ChildComponentA is the child component of ChildComponentB, then it's easy, just pass the setStateX as a prop to ChildComponentA. But in this case, it's not.
The real scenario is the following. I have a canvas component, there are some static Rectangles already there, once there are mouse move over the line of the Rectangles, I'd like to add the indicator lines to another child component of the canvas component.
Hence, the rectComponent is not the descendent of the distanceIndicatorsComponent. So I can't pass the setLines to RectComponent.
What's your approach to do that?
If I use useContext approach, will it work?
Thank you, #KonradLinkowski to provide your solution. Here is his code. However, useContext is still lifing the state up to ParentComponent.
import React, { useContext, createContext, useState } from "react";
const Context = createContext();
function ChildComponentA(props) {
const { setStateX } = useContext(Context);
return (
<div>
componentA button:{" "}
<button onClick={() => setStateX((i) => i + 1)}>add</button>
</div>
);
}
function ChildComponentB(props) {
const { stateX } = useContext(Context);
return <div> stateX is {stateX} </div>;
}
export default function ParentComponent(props) {
const [stateX, setStateX] = useState(0);
return (
<>
<Context.Provider value={{ stateX, setStateX }}>
<ChildComponentA> </ChildComponentA>
<ChildComponentB> </ChildComponentB>
</Context.Provider>
</>
);
}
Regarding the reusbility of the ComponentB i.e. distanceIndicatorsComponent in this scenario, it includes the JSX and the states plus the interface in which there are logic to change the states. The are all parts which should be reusable in the furture.
From OOP perspective, the lines (state) belongs to DistanceIndicatorsComponent, and the how to change the lines (Add Line in this case) should be also reusable logic which belongs to distanceIndicatorsComponent.
However, from React perspective, to lift the setLines (this is the interface triggered under some event) is not "good enough" from OOP perspective. To lift the state - lines and state management function - setLines up to CanvasComponent is a "not good enough" in terms of the encapsulation. Put a wrap component on top of ComponentB is the same thing, the setLines still can't be passed to FrameComponent unless FrameComponent is a child-component of the wrap component.
It's very common to see there is a very heavy component holding all the state and the events at the top. It makes me feel that's a bad smell of the code. The reusability of the component should be based on a set of components, in this set of components, there is one uncontrolled component at the top, and underneath of this uncontrolled component are controlled components. This set of components is a external reusability unit.
Here, in this diagram, there should be more than one reusable unit rather than one. If lift the state up to CanvasComponent, it makes all the components underneath are un-reusable. In some extents, you still can re-use the JSX of this component, but I'd say, in terms of reusablity, it should invovle as many reusable logic as possible.
I might be wrong, please correct me. And thank you for sharing your valuable comments.
Requirements
First let us sum up the requirements.
Rect Component and Distance Indicators have not much to do with each other. Making them aware of each other or creating a dependency between them would be not desired in a good OOP design.
The interaction between both is very specific. Establishing a mechanism or a data structure just for this special sort of interaction would add an overhead to all components that don't need this sort of interaction.
General Concepts
So you must use a mechanism that is so generic that it does not add any sort of coupling. You need to establish something between these two components, which only these two components know and which for all the rest of your program is nonsense. What mechanisms serve for such a purpose?
Function pointers
Lambda functions
Events
Function pointers and lambda functions are complicated constructs. Not everybody prefers to use them. Now you see why events are so popular. They address a common requirement of connecting two components without revealing any of the details of them to anybody.
I personally recommend you to use lambda functions in this situation. Because this is one strength of JavaScript. Search in google for callback or asynchronous lambda function. This often adds the least overhead to existing code. Because a lambda functions has an important property:
With lambda functions you can do things very locally. Doing things locally is an important design principle. You don't need to define extra methods or functions or classes. You can just create them wherever you are, return them, pass them freely around to where you actually need them and store them there. You can store them even without knowing what is behind them.
I think, this is your answer. The only thing you need is a mechanism to pass lambda functions and to store your lambda functions. But this is on a very generic level and therefore adds no coupling.
With events you are on similar path. The event mechanism is already there. But therefore you already have a good answer.
Example with pure JavaScript
When applying this to JavaScript we can imagine that function pointers could be compared to function expressions in JavaScript. And lambda functions can be compared to arrow functions in JavaScript. (Note: Arrow functions also provide "closures", which is required in this case, see How do JavaScript closures work?).
A simple example illustrates this:
class DistanceIndicator {
constructor(height, width) {
this.height = height;
this.width = width;
}
resize(height){
this.height = height;
}
incorrect_resizer(height){
return this.resize;
}
resizer(){
return (height) => this.resize(height);
}
resizer_with_less_overhead(){
return (height) => this.height = height;
}
}
p = new DistanceIndicator();
p.resize(19);
// If you want to use this, you have to store p. You may see
// this as not so nice, because, you are not interested in what
// actually p is. And you don't want to expose the information
// that it has a method resize. You want to have the freedom
// of changing such details without the need of changing all
// the code where something happens with Rectangles.
console.log(p.height);
resizer = p.incorrect_resizer()
//resizer(18);
// In this example, resizer is a function pointer. It would be
// nice to store it and be able to call it whenever we want to
// inform Rectangle about something interesting. But it does not
// work because the resize method cannot be isolated from the
// class. The "this" is not there.
console.log(p.height);
resizer = p.resizer();
resizer(17);
// That works. Lambda functions do the job. They are able to
// include the "this" object.
console.log(p.height);
resizer = p.resizer_with_less_overhead();
resizer(16);
console.log(p.height);
// As you have now a resizer, you can store it wherever you want.
// You can call it without knowing what is behind it.
The idea in the example is that you can store the resizers wherever you want without knowing what they are. You shouldn't name them resizer, but give them a generic name like size_notification.
Example for React
The React concept for contexts is a typical candidate for data exchange between components. But the principle of React is a pure unidirectional data flow (top-down). This is also true for the context, which means, we cannot use a context for what we want.
React does not provide support for the implementation of the proposed idea. React is only responsible for the pure construction of the HTML page and a comfortable and performant rendering. It is not responsible for the "business" logic of our HTML page. This is done in full JavaScript. That makes sense because you want be able to develop complex web applications. Therefore you need all your favourite programming concepts. A real application does not follow the design principle of React. React is only a presentation layer. Most people like OOP progamming.
So when implementing something with React we must keep in mind that React is just a library for JavaScript. The full power of JavaScript is always available and should be used for our web application.
After realizing this, the problem becomes simple. See this code:
import React from 'react';
let sizeNotificator = (newValue) => {console.log(newValue)};
function Rect(props) {
return <button onClick={() => sizeNotificator("12")}>resize to 12</button>;
}
class DistanceIndicator extends React.Component {
state = {
size: "0",
};
setSize(newValue) {
this.setState({
size : newValue
});
};
componentDidMount(){
sizeNotificator = ((newValue) => {this.setSize(newValue);})
}
render() {
return <p>Current size: { this.state.size}</p>;
}
}
class App extends React.Component {
render() {
return(<div>
<DistanceIndicator/>
<Rect/>
</div>);
}
}
export default App;
With this code the requirement is fulfilled that none of the DistanceIndicator implementation details are revealed to the outside of DistanceIndicator.
Obviously this example code only works if there is not more than one DistanceIndicator. To solve this is a different topic with probably not only one good solution.
If keeping the shared state in the ParentComponent is the problem, you can extract the Context.Provider to a separate component and pass components as it's children, those children can access the context value via useContext hook.
function ParentContextProvider({ children }) {
const [stateX, setStateX] = useState(0);
return (
<Context.Provider value={{ stateX, setStateX }}>
{children}
</Context.Provider>
);
}
export default function ParentComponent(props) {
return (
<ParentContextProvider>
<ChildComponentA />
<ChildComponentB />
</ParentContextProvider>
);
}
Now you can add any new state/setState to the ParentContextProvider and can pass it to it's children
Have you looked at Redux stores? You could have a variable like "showLine" or "originX"/"originY", then have one child dispatch changes, and the other child useSelector for the values?
Do you know if Redux works for your use case?
I prefer to use a simple events pattern for this type of scenario. Eg using a component such as js-event-bus.
CHILD COMPONENT A
props.eventBus.emit('MouseOverRectangle', null, new MyEvent(23));
CHILD COMPONENT B
useEffect(() => {
startup();
return () => cleanup();
}, []);
function startup() {
props.eventBus.on('MouseOverRectangle', handleEvent);
}
function cleanup() {
props.eventBus.detach('MouseOverRectangle', handleEvent);
}
function handleEvent(e: MyEvent) {
// Update state of component B here
}
RESULTS
This tends to result in quite clean encapsulation and also simple code. Eg any React conponent can communicate with any other, without needing to reveal internal details.
Using useEffect() properly is sometimes not that easy. Imagine we have the following simple app using the Counter component:
import { useState, useEffect } from 'react';
const Counter = ({ onOdd, onEven }) => {
const [count, setCount] = useState(0);
useEffect(
() => {
console.log('Inside useEffect()');
if (count % 2 === 0) {
onEven(count);
} else {
onOdd(count);
}
},
[count, onOdd, onEven]
);
return (
<button
type="button"
onClick={() => setCount(count => count + 1)}
>
{count}
</button>
);
}
const App = () => {
const [isDarkMode, setIsDarkMode] = useState(false);
return (
<div style={{
backgroundColor: isDarkMode ? 'black' : 'white',
}}>
<Counter
onOdd={count => console.log(`Odd count: ${count}`)}
onEven={count => console.log(`Even count: ${count}`)}
/>
<button
type="button"
onClick={() => setIsDarkMode(isDarkMode => !isDarkMode)}
>
Toggle dark mode
</button>
</div>
);
}
export default App;
The app does two things:
It includes a Count button that increments its counter by 1. This components allows to inject two functions: onOdd and onEven. Whenever the counter changes, either onOdd or onEven is called, depending on the counter... being odd or even.
There is also a dark mode toggle. The only purpose I added it is to have something that causes the Counter to re-render for other reason than changing the count.
Now, the app works with one quirk - whenever we toggle the dark/light mode, the onOdd or onEven is being called. That's wrong, but understandable - we're creating new functions on each render, so useEffect() is being called.
I can think of 4 ways to fix this behavior:
Remove onOdd and onEven from useEffect() dependencies. It will fix the behavior, but it's considered a problem. The linter would complain about it, as we're losing data integrity. In theory, if we really change these callbacks, they should be re-run, right? That would be "the React way".
Move the callback functions outside of the App component:
const onOdd = count => console.log(`Odd count: ${count}`);
const onEven = count => console.log(`Even count: ${count}`);
const App = () => {
// ...
return (
// ...
<Counter
onOdd={onOdd}
onEven={onEven}
/>
// ...
);
}
This is a good and fast solution, but it's only possible because we don't use hooks or state inside these callbacks. What if we did?
Using useCallback() in App component:
const App = () => {
// ...
const onOdd = useCallback(
count => console.log(`Odd count: ${count}`),
[]
);
const onEven = useCallback(
count => console.log(`Even count: ${count}`),
[]
);
return (
// ...
<Counter
onOdd={onOdd}
onEven={onEven}
/>
// ...
);
}
Memoizing the callback functions in Counter component. If we had thousands of components using Counter component, it would still mean only one place to memoize these functions. I'm not sure if that makes sense though.
How do React gurus approach this problem? I wanted to keep the example as simple as possible, so option #2 will work perfectly and would probably be preferable. But what if we needed to keep these callbacks inside the App component?
Is it always the parent component responsible to memoize all callbacks it passes to the child? If so, is it a recognized pattern to always memoize all functions passed as props (and perhaps any other objects) with useCallback() or useMemo()?
I'm not properly a React Guru, but I consider all first three approaches to have their sweet spot, the 4th does not make sense. The only one to be careful with is the first one, since removing functions from deps, might lead to stale state issues, so if you know what you are doing, you may suppress lint warn ( I do that sometimes and know many others do that as it has been discussed extensiveley here https://github.com/facebook/react/issues/14920 ), otherwise it's better you avoid this approach.
The point number 2 is preferred everytime you have pure functions, always try to place your pure functions out of React components, inside some other folder like utils, misc, etc...
As per point number 3 that's the preferred way to handle functions declared inside React components, always memoize them with *useCallback* ( or useMemo if you need to perform calculations before to return a function ) , and there's nothing bad with doing that in the parent component. If you find yourself having dozens or hundreds of them and fear code pollution, consider that custom hooks let you to organize your code smartly, you could make a custom hook like useMemoizedHandlers inside your App component, where you create and memoize all your handlers and use it like:
const {
handler1,
handler2,
handler3
} = useMemoizedHandlers()
Options 2 and 3 are both absolutely valid and common, used interchangeably depending on whether the function has render cycle dependencies. Option 1 is a big no no. Option 4 is not really memoization at all - you can create stable references from functions passed as props but you cannot memoize the functions themselves as they've already been created anew.
Is it always the parent component responsible to memoize all callbacks it passes to the child?
In an application context I would say yes as this is the only way to enable React.memo on the consuming component's props. However, libraries will often convert functions to stable refs in the child, in case users forget to memoize themselves (or just as improved DX). Again, this is not the same as memoization, but it does mean that you can avoid the dependency issues highlighted in your question.
Is it a recognized pattern to always memoize all functions passed as props (and perhaps any other objects) with useCallback() or useMemo()?
You will find both memoization maxis and minimalists in the React community so it's hard to say that there's an accepted standard. Generally, you can get away with not doing it until you need it, like your example. However, purely from personal experience, once you do it a few times out of necessity it starts to become a habit as it reduces the possibility that bugs like this can occur.
Hi I am learning and new to react and I want to know how to pass state from one component to other,
I have one component as
const [paneCount, setPaneCount]= useState(1);
const openPane = (paneKey) => {
setOpeningPaneKeys(oldState => {
if (!oldState.includes(paneKey)) {
return [...oldState, paneKey]
}
return oldState
})
setPaneCount(paneCount+1);
console.log(paneCount);
setFocusingPaneKey(paneKey)
}
where I want to use paneCount in App.js file
function App(props) {
const [inactive, setInactive] = useState(false);
return (
<div className="App">
<Header />
<Navbar
onCollapse={(inactive) => {
setInactive(!inactive);
}}
/>
<div class="landing-card">
<div>
<h4 class="headingStyle">Recorder Box</h4>
<h4>Count:{props.paneCount}</h4>
<img src="landing.jpg" alt="Forest" width="775" height="500"></img>
</div>
</div>
How to pass paneCount to App.js
You can't pass data upwards, only downwards. The reason for that is how the application and data flow is built. Luckily there are 3 ways to get it done.
1) Initialize in App.js
If you want to use state value in App.js but want to work with it somewhere else:
export default function App() {
const [myState, setMyState] = useState();
return <Component state={ myState } setState={ setMyState } />
}
This way you can keep all your state in 1 place and use it everywhere, but it also means that you have to pass component by component to do so.
2) Context
This improves the previous option, because you no longer need to pass data around. Instead you can keep it in a provider and use it throughout your application.
There are multiple ways to define a provider, so I will just link you the docs for that one.
Note: Provider definitions does not differ in any way. They are not practical or impractical, simply one's preference over another!
3) Redux
This is arguably the BEST option for state management (eventhough I don't like it..). I haven't used it yet and don't want to either, because the previous option does the same with less effort IMO.
Here's the docs for that one.
Instead of passing paneCount to App.js which I suppose is the parent component. You can create paneCount in App.js and then pass setPaneCount to the openPane component like this.
<openPane setPaneCount = {setPanecount} paneCount = {setPaneCount}/>
If you are not calling openPane in the App.js for some reason then go to the parent component that is calling both App and openPane and create and pass the setPaneCount and paneCount from there. If you making something complex instead of drilling the value down like this you might want to look at some stateManagement tools like Redux or Context API.
From what I understand you use useCallback to prevent rerendering so I've been using it in every function and my spider senses are telling me it already sounds bad.
But the story doesn't ends there, since I've been using it everywhere I'm now passing dependencies to all my child components that they shouldn't need to worry about like in the following example :
EDIT // SANDBOX: https://codesandbox.io/s/bold-noether-0wdnp?file=/src/App.js
Parent component (needs colorButtons and currentColor)
const ColorPicker = ({onChange}) => {
const [currentColor, setCurrentColor] = useState({r: 255, g:0, b: 0})
const [colorButtons, setColorButtons] = useState({0: null})
const handleColorButtons = useCallback((isToggled, id) => {
/* code that uses colorButtons and currentColor */
}, [colorButtons, currentColor])
return <div className="color-picker">
<RgbColorPicker color={currentColor} onChange={setCurrentColor} />
<div className="color-buttons">
{
Object.entries(colorButtons).map(button => <ColorButton
//...
currentColor={currentColor}
onClick={handleColorButtons}
colorButtons={colorButtons}
/>)
}
</div>
</div>
}
1st child (needs style and currentColor but gets colorButtons for free from its parent)
const ColorButton = ({currentColor, onClick, id, colorButtons}) => {
const [style, setStyle] = useState({})
const handleClick = useCallback((isToggled) => {
/* code that uses setStyle and currentColor */
}, [style, currentColor, colorButtons])
return <ToggleButton
//...
onClick={handleClick}
style={style}
dependency1={style}
dependency2={currentColor}
dependency3={colorButtons}
>
</ToggleButton>
}
2nd child (only needs its own variables but gets the whole package)
const ToggleButton = ({children, className, onClick, style, data, id, onRef, ...dependencies}) => {
const [isToggled, setIsToggled] = useState(false)
const [buttonStyle, setButtonStyle] = useState(style)
const handleClick = useCallback(() => {
/* code that uses isToggled, data, id and setButtonStyle */
}, [isToggled, data, id, ...Object.values(dependencies)])
return <button
className={className || "toggle-button"}
onClick={handleClick}
style={buttonStyle || {}}
ref={onRef}
>
{children}
</button>
}
Am I doing an anti-pattern and if so, what is it and how to fix it ? Thanks for helping !
React hook useCallback
useCallback is a hook that can be used in functional React components. A functional component is a function that returns a React component and that runs on every render, which means that everything defined in its body get new referential identities every time. An exception to this can be accomplished with React hooks which may be used inside functional components to interconnect different renders and maintain state. This means that if you save a reference to a regular function defined in a functional component using a ref, and then compare it to the same function in a later render, they will not be the same (the function changes referential identity between renderings):
// Render 1
...
const fnInBody = () => {}
const fn = useRef(null)
console.log(fn.current === fnInBody) // false since fn.current is null
fn.current = fnInBody
...
// Render 2
...
const fnInBody = () => {}
const fn = useRef(null)
console.log(fn.current === fnInBody) // false due to different identity
fn.current = fnInBody
...
As per the docs, useCallback returns "a memoized version of the callback that only changes if one of the dependencies has changed" which is useful "when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders".
To sum up, useCallback will return a function that maintains its referential identity (e.g. is memoized) as long as the dependencies don't change. The returned function contains a closure with the used dependencies and must thus be updated once the dependencies change.
This results in this updated version of the previous example
// Render 1
...
const fnInBody = useCallback(() => {}, [])
const fn = useRef(null)
console.log(fn.current === fnInBody) // false since fn.current is null
fn.current = fnInBody
...
// Render 2
...
const fnInBody = useCallback(() => {}, [])
const fn = useRef(null)
console.log(fn.current === fnInBody) // true
fn.current = fnInBody
...
Your use case
Keeping the above description in mind, let's have a look at your use of useCallback.
Case 1: ColorPicker
const handleColorButtons = useCallback((isToggled, id) => {
/* code that uses colorButtons and currentColor */
}, [colorButtons, currentColor])
This function will get a new identity every time colorButtons or currentColor changes. ColorPicker itself rerenders either when one of these two are set or when its prop onChange changes. Both handleColorButtons and the children should be updated when currentColor or colorButtons change. The only time the children benefit from the use of useCallback is when only onChange changes. Given that ColorButton is a lightweight component, and that ColorPicker rerenders mostly due to changes to currentColor and colorButtons, the use of useCallback here seems redundant.
Case 2: ColorButton
const handleClick = useCallback((isToggled) => {
/* code that uses setStyle and currentColor */
}, [style, currentColor, colorButtons])
This is a situation similar to the first case. ColorButton rerenders when currentColor, onClick, id or colorButtons change and the children rerender when handleClick, style, colorButtons or currentColor change. With useCallback in place, the props id and onClick may change without rerendering the children (according to the above visible code at least), all other rerenders of ColorButton will lead to its children rerendering. Again, the child ToggleButton is lightweight and id or onClick are not likely to change more often than any other prop so the use of useCallback seems redundant here as well.
Case 3: ToggleButton
const handleClick = useCallback(() => {
/* code that uses isToggled, data, id and setButtonStyle */
}, [isToggled, data, id, ...Object.values(dependencies)])
This case is elaborate with a lot of dependencies but from what I see, one way or the other, most of the component props will lead to a "new version" of handleClick and with the children being lightweight components, the argument to use useCallback seems weak.
So when should I use useCallback?
As the docs say, use it in the very specific cases when you need a function to have referential equality between renders ...
You have a component with a subset of children that are expensive to rerender and that should rerender much less often than the parent component but rerender due to a function prop changing identity whenever the parent rerenders. To me, this use case also signals bad design and I would attempt to divide the parent component into smaller components but what do I know, maybe this is not always possible.
You have a function in the body of the functional component which is used in another hook (listed as a dependency) which is triggered every time due to the function changing identity whenever the component rerenders. Typically, you can omit such a function from the dependency array by ignoring the lint rule even if this is not by the book. Other suggestions are to place such a function outside the body of the component or inside the hook that uses it, but there might be scenarios where none of this works out as intended.
Good to know connected to this is ...
A function living outside a functional component will always have referential equality between renders.
The setters returned by useState will always have referential equality between renders.
I said in the comments that you can use useCallback when there is function doing expensive calculations in a component that rerenders often but I was a bit off there. Let's say you have a function that does heavy calculations based on some prop that changes less often than a component rerenders. Then you COULD use useCallback and run a function inside it that returns a function with a closure with some computed value
const fn = useCallback(
(
() => {
const a = ... // heavy calculation based on prop c
const b = ... // heavy calculation based on prop c
return () => { console.log(a + b) }
}
)()
, [c])
...
/* fn is used for something, either as a prop OR for something else */
This would effectively avoid calculating a and b every time the component rerenders without c changing, but the more straightforward way to do this would be to instead
const a = useMemo(() => /* calculate and return a */, [c])
const b = useMemo(() => /* calculate and return b */, [c])
const fn = () => console.log(a + b)
so here the use of useCallback just complicates things in a bad way.
Conclusion
It's good to understand more complicated concepts in programming and to be able to use them, but part of the virtue is also to know when to use them. Adding code, and especially code that involves complicated concepts, comes at the price of reduced readability and code that is harder to debug with a lot of different mechanisms that interplay. Therefore, make sure you understand the hooks, but always try to not use them if you can. Especially useCallback, useMemo and React.memo (not a hook but a similar optimization) should, in my opinion, only be introduced when they are absolutely needed. useRef has its very own use cases but should also not be introduced when you can solve your problem without it.
Good work on the sandbox! Always makes it easier to reason about code. I took the liberty of forking your sandbox and refactoring it a bit: sandbox link. You can study the changes yourself if you want. Here is a summary:
It's good that you know and use useRef and useCallback but I was able to remove all the uses, making the code much easier to understand (not only by removing these uses but by also removing the contexts where they are used).
Try to work with React to simplify things. I know this is not a hands-on suggestion but the more you get into React, the more you will realize that you can do things co-operating with React, or you can do things your own way. Both will work but the latter will result in more headache for you and everybody else.
Try to isolate the scope of a component; only delegate data that is necessary to child components and constantly question where you keep your state. Earlier you had click handlers in all three components and the flow was so complicated I didn't even bother to fully understand it. In my version, there is just one click handler in ColorPicker that is being delegated down. The buttons don't have to know what happens when you click them as long as the click handler takes care of that. Closures and the ability to pass functions as arguments are strong advantages of React and Javascript.
Keys are important in React and it's good to see that you use them. Typically, the key should correspond to something that uniquely identifies a specific item. Good to use here would be ${r}_${g}_${b} but then we would only be able to have one sample of each color in the button array. This is a natural limitation but if we don't want it, the only way to assign keys is to assign a unique identifier, which you did. I prefer using Date.now() but some would probably advise against it for some reason. You could use a global variable outside the functional component too if you don't want to use a ref.
Try to do things the functional (immutable) way, and not the "old" Javascript way. For example, when adding to an array, use [...oldArray, newValue] and when assigning to an object, use {...oldObject, newKey: newValue }.
There are more things to say but I think it's better for you to study the refactored version and you can let me know if you wonder about anything.
It's a known React behavior that code runs twice.
However, I'm creating a form builder in which I need to be able to give each form input a dynamic Id and use that Id for a lot of other purposes later. Here's a simple code of an input:
const Text = ({placeholder}) => {
const [id, setId] = useState(Math.random());
eventEmitter.on('global-event', () => {
var field = document.querySelector(`#${id}`); // here, id is changed
});
}
But since Math.random() is a side-effect, it's called twice and I can't create dynamic ids for my form fields.
The reason I'm using document.querySelector can be read here.
My question is, how can I create consistent dynamic ids for my inputs?
It seems you think that useState(Math.random()); is the side-effect causing you issue, but only functions passed to useState are double-invoked.
I think the issue you have is that the eventEmitter.on call is the unintentional side-effect since the function component body is also double invoked.
Strict mode can’t automatically detect side effects for you, but it
can help you spot them by making them a little more deterministic.
This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies <-- this
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer <-- not this
To remedy this I believe you should place the eventEmitter.on logic into an useEffect hook with a dependency on the id state. You should also probably use id values that are guaranteed a lot more uniqueness. Don't forget to return a cleanup function from the effect to remove any active event "listeners", either when id updates, or when the component unmounts. This is to help clear out any resource leaks (memory, sockets, etc...).
Example:
import { v4 as uuidV4 } from 'uuid';
const Text = ({placeholder}) => {
const [id, setId] = useState(uuidV4());
useEffect(() => {
const handler = () => {
let field = document.querySelector(`#${id}`);
};
eventEmitter.on('global-event', handler);
return () => {
eventEmitter.removeListener('global-event', handler);
};
}, [id]);
...
}