We have a page that has a tab on the top. Both tabs show same components, except that on one tab some of the labels and other controls are different (some are hidden, some appear only on one, etc.) The changes are minimal enough that they don't warrant a different component altogether.
So what I am trying to figure out is how to embed multiple instances of the same child component into a parent, and yet be able to pass values from different instances of the child to the parent.
Here is my parent component for example
import React, { useState } from "react";
import Child1 from "./Child1";
const Parent1 = () => {
const changeNameFunc = (childName) => {
alert(childName);
};
return (
<div>
Parent
<div>
<Child1 id="1" changeName={changeNameFunc} />
<Child1 id="2" changeName={changeNameFunc} />
</div>
</div>
);
};
export default Parent1;
And here is my child component
import React, { useState } from "react";
const Child1 = (props) => {
const [name, setname] = useState();
const clicketyclick = () => {
const newName = document.getElementById("txtName").value;
setname(newName);
};
return (
<div>
<input type="text" id="txtName" name="name" />
<button onClick={clicketyclick}>Change Child Name</button>
<button onClick={() => props.changeName(name)}>Send To Parent</button>
</div>
);
};
export default Child1;
In this case the problem is obviously in my clicketyclick function because the document.getElementById picks up the first control that matches that ID in the DOM. In my code at work, we aren't picking up values using document.getElement, rather we are passing state object up to the parent via a props.function call.
So to make it random, I changed my child code in this example to following.
import React, { useState } from "react";
const Child1 = (props) => {
const [name, setname] = useState();
function getRandomInt(max) {
return Math.floor(Math.random() * max);
}
const clicketyclick = () => {
const newName = getRandomInt(100); // document.getElementById("txtName").value;
setname(newName);
};
return (
<div>
<input type="text" id="txtName" name="name" />
<button onClick={clicketyclick}>Change Child Name</button>
<button onClick={() => props.changeName(name)}>Send To Parent</button>
</div>
);
};
export default Child1;
Now this works just fine. If I click ChangeChildName on first instance of Child control, it sets a random number (say 54) to the state of that instance. If I then click SendToParent on first instance of Child control, it alerts 54. If I click ChangeChildName on second instance of Child control, it sets a different random number (say 98) to the state of that instance. If I then click SendToParent on second instance of Child control, it alerts 98. This is great. It means the state is owned by the instance of the child control.
So with this knowledge, are there any gotchas with nesting multiple instances of a child component into a parent? Is it better to just avoid it and create a separate component even if 98% of the functionality is same? I've read somewhere that in this case we should make sure different instances have different "id" property (though taking it out didn't make any difference; my code still worked). I've also read somewhere about passing in a context of some sort to prefix the field ids with (not sure how that would work).
What is the best way to do this i.e. have a parent render multiple instances of a child control, and then have each child send their respective data up to the parent without causing any collisions or other gnarly problems with state management.
Edit: My question really isn't about the getElementById part. My question is what is the proper way to have multiple instances of a control embedded inside a parent component. Are you supposed to pass some sort of prefix for the ids in the child control, and if so how? Are you supposed to have an "id" attribute on the child control definition inside the parent control to make react aware of it that they are unique? Or something else?
You can use useRef
import React, { useState, useRef } from 'react';
const Child1 = (props) => {
const [name, setname] = useState('');
const inputRef = useRef(null);
const clicketyclick = () => {
const newName = inputRef.current.value;
setname(newName);
};
return (
<div>
<input type="text" name="name" ref={inputRef} />
<button onClick={clicketyclick}>Change Child Name</button>
<button onClick={() => props.changeName(name)}>
Send To Parent
</button>
</div>
);
};
Related
I have two layouts one for mobile second for desktop view. Thier structure is different however both inject the same childrens. I would like to prevent childrens rerender when the layout switch. Here is pseudocode which reflect the case(or check live https://playcode.io/1193034):
import React, { useState } from "react";
interface ParentProps {
children: React.ReactNode;
}
const Parent = ({ children }: ParentProps): JSX.Element => {
const [value, setValue] = useState(false);
if (value) {
return (
<>
<button type="button" onClick={() => setValue((val) => !val)}>
Turn into false
</button>
{children}
</>
);
}
return (
// some additional elements for desktop view
<div>
<div>
<button type="button" onClick={() => setValue((val) => !val)}>
Turn into true
</button>
{children}
</div>
</div>
);
};
const ChildrenComponent = () => {
console.log("rerender Children...");
return <p>children component</p>;
};
export const App = ()=> {
return (
<Parent>
<ChildrenComponent />
</Parent>
);
}
What could I do?
So far I have tried to wrap children component in Rect.memo and useMemo (inside Parent) both didn't work. Maybe it's just impossible to do
You will not be able to avoid rerendering in your scenario, as you are rendering different elements in different positions
The way React knows what elements to rerender is by creating a tree structure (the virtual DOM) and comparing it to the browser DOM. When a node in the virtual DOM changes in relation to the browser DOM, it will be replaced (and therefore rerendered), along with every child element of this node. This process is called reconciliation.
Because of this, even if some of the child components are the same, once you change the position or the type of their parent elements, there is no way to avoid their rerendering.
On a sidenote, you could avoid the rerendering if you restructure your component to always return the same types of elements in the same positions, and make them responsive by using CSS media queries.
I wrote a custom input for passwords. He looks like this:
const InputPassword = ({placeholder}) => {
const inputRef = useRef()
const [isPasswordVisible, setPasswordVisible] = useState(false)
function setInputStatus() {
if (isPasswordVisible) {
inputRef.current.setAttribute('type', 'password')
} else {
inputRef.current.setAttribute('type', 'text')
}
setPasswordVisible(!isPasswordVisible)
}
return (
<div className={cl.main}>
<input
ref={inputRef}
type={"password"}
placeholder={placeholder}
className={cl.input}
/>
<div className={cl.eyeContainer} onClick={setInputStatus}>
<Eye
isPasswordVisible={isPasswordVisible}
/>
</div>
</div>
);
};
export default InputPassword;
Now I want to get the value from my input in the parent component. The best way to do this is to use a forwardRef, but my component already has an internal Ref that is needed to change the type of the input. Surely you can somehow use one Ref to solve these two problems, but I did not find how.
I tried to pass a Boolean type state from the parent, so that when this state changes, call a method in the child component, this method would change another state in the parent, which stores the value from the child. And when this parent state changes, the necessary logic would work out for me. But the code turned out to be just as cumbersome and confusing as what is written above. Naturally, such code does not work well, it shows unpredictable behavior. I'm sure the solution is much simpler.
You can also pass a state variable to the child component, I am passing a ref variable, assuming that the parent component does not need to re-render based on the changes in the value of variable that is being passed as a prop.
Your parent component should have a inputRef, and pass it as a prop to child component, something like this:
const Parent = () => {
const inputRef = useRef()
return <InputPassword inputRef={inputRef} />
}
export default Parent;
const InputPassword = ({placeholder, inputRef}) => {
const [isPasswordVisible, setPasswordVisible] = useState(false)
function setInputStatus() {
if (isPasswordVisible) {
inputRef.current.setAttribute('type', 'password')
} else {
inputRef.current.setAttribute('type', 'text')
}
setPasswordVisible(!isPasswordVisible)
}
return (
<div className={cl.main}>
<input
ref={inputRef}
type={"password"}
placeholder={placeholder}
className={cl.input}
/>
<div className={cl.eyeContainer} onClick={setInputStatus}>
<Eye
isPasswordVisible={isPasswordVisible}
/>
</div>
</div>
);
};
export default InputPassword
I am working on the project in React Typescript.
I have created hierarchy of components as per requirement.
In one scenario I have to pass data from child component to parent component and I am passing function as props and it works.
Issue :
When passing data to parent component child component gets re-render it looks like. Mean to say Dropdown selection is get reset and tree control expanded nodes get collapsed and set to the position as first time rendered.
I have used useState,useEffects hooks.
I have also tried React.memo as a part of my search on internet.
What I need :
I want to pass data to parent component from child without re-render the child component as there is no change in the props of child component.
Try this approach:
Add useCallback hook to memoize your function which lift data to <Parent />.
Then use React.memo for <Child /> to control prop changes and avoid unwanted re-renders.
I prepare an example for you here.
UPD. I have uploaded an example, you can copy it and see how it works!
Here is Child component:
const Child = ({ onChange }) => {
console.log("Child re-render");
return (
<div className="App">
<h1>Child</h1>
<button onClick={() => onChange(Math.random())}>
Lift value to Parant
</button>
</div>
);
};
const areEqual = ({ onChange: prevOnChange }, { onChange }) => {
return prevOnChange === onChange; // if true => this will avoid render
}
export default React.memo(Child, areEqual);
And the Parent:
consn App = () => {
const [value, setValue] = useState("");
const onChange = useCallback((value) => setValue(String(value)), []);
console.log("Parant re-render");
return (
<div className="App">
<h1>Parent</h1>
<div>Value is: {value}</div>
<Child onChange={onChange} />
</div>
);
}
Best regards š
I found the following answer to a question that I currently have (link). In in, he said "If you pass any props to the component from the parent component and you update that prop in children or that prop update in the parent component so both will re-render." Can anyone give me a brief code snippet that achieves the case where you pass a prop to a child component from a parent component, then you update it in the child component so that both components will re-render? I am not still sure if this is attainable...
(scenario)
Let's say I have a parent component which is a sidebar containing a list of items. When I click an edit button of one of items, a modal (child component) where I can change the item name pops up. As hitting a submit button with a new item name as an input, the previous item name gets updated with the new one behind the scenes and the modal gets closed. As this happens, I would like the parent component to re-render to reflect the change on UI.
const Parent () => {
const [isOpen, setIsOpen] = useState(true);
const [{ items }, dipatch} = useStateValue(); //Fetch items object through React Context API
return (
{items.map((item) => (
<p>{item}<p />
))}
<Child callback={() => setIsOpen(false)} />
)
}
const Child (callback) => {
const submitHandler = () => {
postRequest(data); //updates items' data + updates items in the context
callback(); //After closing the modal here, I would like the parent component re-render.
}
return (
<Modal
isOpen={isOpen}
submit={submitHandler}
onClose={callback}
>...
<Modal />
)
}
Code example, when values from the parent are passed to the react child component, and data changes come from the child component.
Note that there is no state in the child component and it takes data from the values passed to it
import {useState} from 'react'
const NumberIn = ({num, change}) => {
return (
<input type='number' onChange={(e) => {
change(e.currentTarget.value)
}} value={num} />
)
}
export default function App() {
const [a, setA] = useState(0);
const [b, setB] = useState(0);
return (
<div className="App">
<NumberIn num={a} change={setA} /> + {' '}
<NumberIn num={b} change={setB} /> = {' '}
<span>{Number(a) + Number(b)}</span>
</div>
);
}
We pass a value and a function (a kind of callback) to the child component. When an event occurs in a child component, the passed function will be called, but it will be called in the parent component, because it belongs to the parent component, but with the parameters passed by the child component.
Iām wondering how can I change a children input value from main?
Basically I have a component that Iām sending a children, and in this component I want to be able to change the input value.
Here is my main:
<FooterOptions>
<input type="text"
onChange={event=>setState(event.target.value)}
value={state}
hidden/>
</FooterOptions>
And here is my component:
export function FooterOptions(props:{children: ReactNode}){
return(
<div>
{props.children}
</div>
)
}
The children prop is something that you can only render onto the page, so there's nothing you can do with it to change the value of the input.
Instead how I would think about this is that you want to provide a mechanism for FooterOptions to change the value of another component. Here it happens to also be rendered as its children, but it would work the same even if it was rendered someplace else.
const Parent = () => {
const updateInput = (val) => setState(val)
return (
<FooterOptions handleUpdate={updateInput}>
<input type="text"
onChange={event=>setState(event.target.value)}
value={state}
hidden/>
</FooterOptions>
)
}
export function FooterOptions(props:{children: ReactNode, handleUpdate}){
// Example handler
const handleClick = () => handleUpdate('updated inside FooterOptions')
return(
<div onClick={handleClick}>
{props.children}
</div>
)
}
If you'd like to add more details of how you are hoping to update, then I can add a better example š