admin管理员组文章数量:1293949
I want to make a highly reusable react ponent with a unique pattern. Assume this contact list was produced by another team; we can't change the ponents, and it follows the structure shown below.
<Component>
<Child1 key="child1" />
<Child2 key="child2" />
<Child3 key="child3" />
</Component>
Sample ContactList Component:
<ContactList key="contact-list">
<ContactList.Header key="contactlist-header" />
<ContactList.Body key="contactlist-body" />
<ContactList.Footer key="contactlist-footer" />
</ContactList>
I'd like to offer choices for customising the contact-list ponent, such as
- Add any ponent anywhere in contact list
- Remove ponent based on "key" value
- Replace entire ponent
I'd like to expose some APIs similar to this.
UI.ContactList.remove("contactlist-footer")
// removed from ContactList and stored in variable for later use
UI.ContactList.add(<CustomContactListFooter/>)
// add Component to ContactList and stored in variable for later use
Where UI is some Namespace / Class
So I need a wrapper ponent that allows me to manipulate ContactList's children based on above api, let say UI.ContactList.remove("contactlist-footer")
and assume remove API store the data in this variable _removeRequest = ['contactlist-footer']
while rendering ponent I don't want to show this ponent <ContactList.Footer key="contactlist-footer">, I can able to do with in ContactList ponent by manipulate like this
High level idea:
function ContactList({children}){
const removeKey = UI.ContactList._removeRequest[0]
const newChildren = React.Children.toArray(children).filter(child => child.key !== removeKey)
return <React.Fragement>{newChildren}</React.Fragement>
}
This not possible because we are not allowed to modify ContactList ponent.
<Parent>
<ContactList/>
</Parent>
function App() {
return (
<div className="App">
<Parent>
<ContactList />
</Parent>
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
function Parent({ children }) {
console.log(children); // ????? how do we access ContactList's children to alter
return children;
}
function ContactList() {
return (
<React.Fragment>
<ContactListHeader key="contactlist-header" />
<ContactListBody key="contactlist-body" />
<ContactListFooter key="contactlist-footer" />
</React.Fragment>
);
}
function ContactListHeader() {
return <h2>Header</h2>;
}
function ContactListBody() {
return <section>Body Content</section>;
}
function ContactListFooter() {
return <footer>Contact List Footer</footer>;
}
<script src=".6.3/umd/react.production.min.js"></script>
<script src=".6.3/umd/react-dom.production.min.js"></script>
<body>
<div id="root"></div>
</body>
I want to make a highly reusable react ponent with a unique pattern. Assume this contact list was produced by another team; we can't change the ponents, and it follows the structure shown below.
<Component>
<Child1 key="child1" />
<Child2 key="child2" />
<Child3 key="child3" />
</Component>
Sample ContactList Component:
<ContactList key="contact-list">
<ContactList.Header key="contactlist-header" />
<ContactList.Body key="contactlist-body" />
<ContactList.Footer key="contactlist-footer" />
</ContactList>
I'd like to offer choices for customising the contact-list ponent, such as
- Add any ponent anywhere in contact list
- Remove ponent based on "key" value
- Replace entire ponent
I'd like to expose some APIs similar to this.
UI.ContactList.remove("contactlist-footer")
// removed from ContactList and stored in variable for later use
UI.ContactList.add(<CustomContactListFooter/>)
// add Component to ContactList and stored in variable for later use
Where UI is some Namespace / Class
So I need a wrapper ponent that allows me to manipulate ContactList's children based on above api, let say UI.ContactList.remove("contactlist-footer")
and assume remove API store the data in this variable _removeRequest = ['contactlist-footer']
while rendering ponent I don't want to show this ponent <ContactList.Footer key="contactlist-footer">, I can able to do with in ContactList ponent by manipulate like this
High level idea:
function ContactList({children}){
const removeKey = UI.ContactList._removeRequest[0]
const newChildren = React.Children.toArray(children).filter(child => child.key !== removeKey)
return <React.Fragement>{newChildren}</React.Fragement>
}
This not possible because we are not allowed to modify ContactList ponent.
<Parent>
<ContactList/>
</Parent>
function App() {
return (
<div className="App">
<Parent>
<ContactList />
</Parent>
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
function Parent({ children }) {
console.log(children); // ????? how do we access ContactList's children to alter
return children;
}
function ContactList() {
return (
<React.Fragment>
<ContactListHeader key="contactlist-header" />
<ContactListBody key="contactlist-body" />
<ContactListFooter key="contactlist-footer" />
</React.Fragment>
);
}
function ContactListHeader() {
return <h2>Header</h2>;
}
function ContactListBody() {
return <section>Body Content</section>;
}
function ContactListFooter() {
return <footer>Contact List Footer</footer>;
}
<script src="https://cdnjs.cloudflare./ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<body>
<div id="root"></div>
</body>
From parent ponent how do manipulate children of ContactList ? Any thoughts will be helpful
Share edited Aug 24, 2022 at 3:03 Okan Karadag 3,0551 gold badge13 silver badges25 bronze badges asked Aug 1, 2022 at 7:15 Raja JaganathanRaja Jaganathan 36.2k4 gold badges33 silver badges36 bronze badges 1- You could also ask the question why a team with the same skills (React) in the same pany is not allowed to suggest the proper changes for code they actually depend on. And instead preserve an insufficient ponent and work around it downstream by hacking React. Perhaps that ponent can't be changed because previously implemented workarounds would then break... – inwerpsel Commented Aug 24, 2022 at 9:23
3 Answers
Reset to default 8 +50Alright, I'd like to start with don't do this! - what you intend is not how a React application or ponent should work. You should only control your ponents via props and Context from above. This is how React is supposed to work.
The UI class or namespace you're proposing would also store some of the state of your application outside of React, which some monly used libraries like redux, zustand etc. also do but this is easy to get wrong and imho something to be avoided in React.
Nevertheless here's a working demo of the features you want (handled through props to the Parent
ponent, not an external class).
As you can see, I am not rendering the ponents exactly like React would but instead I am calling the function directly.
I am pretty certain this would be terrible to maintain and break a lot of stuff (as soon as things are not as trivial as here), but for this short demo it works.
function App() {
return (
<div className="App">
{/* remove body and header */}
<Parent removeKeys={["contactlist-body", "contactlist-header"]}>
<ContactList />
</Parent>
<hr/>
{/*add a second footer at array index 3 */}
<Parent insertChildren={{3: <ContactListFooter2 />}}>
<ContactList />
</Parent>
<hr />
{/*replace the footer with a custom one */}
<Parent removeKeys={["contactlist-footer"]} insertChildren={{2: <ContactListFooter2 />}}>
<ContactList />
</Parent>
<hr/>
{/*replace the entire ponent*/}
<Parent replaceComponent={<ContactListFooter2 />}>
<ContactList />
</Parent>
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
function Parent({ children, removeKeys=[], insertChildren={}, replaceComponent=undefined }) {
if(replaceComponent){
return replaceComponent;
}
// this is really hacky - don't do it
const renderedChildren = children["type"]();
renderedChildren.props.children = renderedChildren.props.children.filter(child=>!removeKeys.includes(child.key));
for(let [index, ponent] of Object.entries(insertChildren)){
renderedChildren.props.children.splice(index, 0, ponent["type"]())
}
return renderedChildren;
}
function ContactList() {
return (
<React.Fragment>
<ContactListHeader key="contactlist-header" />
<ContactListBody key="contactlist-body" />
<ContactListFooter key="contactlist-footer" />
</React.Fragment>
);
}
function ContactListHeader() {
return <h2>Header</h2>;
}
function ContactListBody() {
return <section>Body Content</section>;
}
function ContactListFooter() {
return <footer>Contact List Footer</footer>;
}
function ContactListFooter2() {
return <footer>Contact List Footer2</footer>;
}
<script src="https://cdnjs.cloudflare./ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<body>
<div id="root"></div>
</body>
This might not an answer for the question but I would like to write this here hope this will help someone.
A properly design ponent should be reusable & expose API to customize the ponent as client project wants. If it is not reusable, then its because, it doesn't meant to be highly reuse or the ponent design is bad. So the best thing to do is design these ponent properly. Here I'll explain one possible proper design pattern for the <ContactList>
ponent that supposed to be reusable.
In React we just describe the UI. Our UI description of a ponent change based on state and props ( due to conditions, state values etc ). We don't even call our custom ponents in our JSX tree, React is the one who call our custom ponents in tree and decide what to do with the return React element structure. We should not mess with that!
When designing reusable UI ponents ( UI libraries etc ) there are patterns that we can use. Let's think about your ponent,
function ContactList() {
return (
<React.Fragment>
<ContactListHeader />
<ContactListBody />
<ContactListFooter />
</React.Fragment>
);
}
When we check this ponent, we can see that ContactList
doesn't do anything else other than just pose all the sub ponents together. Technically this should be a responsibility of client code. A one possible design patter is Compound Component Pattern
The reusable ponent library expose, all,
const ContactListHeader = () => {}
const ContactListBody = () => {}
const ContactListFooter = () => {}
const ListContext = createContext({});
export const ContactList = ({ children }) => {
return <ListContext.Provider>{ children }</ListContext.Provider>
}
// Not required but doing this make the client code easy to understand
ContactList.ContactListHeader = ContactListHeader;
ContactList.ContactListBody = ContactListBody;
ContactList.ContactListFooter = ContactListFooter;
Then on client side,
function MyApp() {
const [somedata, setSomeData] = useState(DATA);
return (
<ContactList values={somedata} onToggle={someMethod}>
<ContactList.ContactListHeader />
<ContactList.ContactListBody />
<ContactList.ContactListFooter />
</ContactList>
)
}
The library uses a context ( ListContext ) that can access within the ponent library. So now all 3 sub ponent can access the values from context and do whatever thing.
A perfect example for this is a activeTab={2}
prop of a ponent. Now the sub ponent can access the active tab via context and do whatever
return (
<TabContainer activeTab={2}>
<Tab index={1} />
<Tab index={2} />
</TabContainer>
)
Back to the example,
Since the MyApp
is our ponent, we can now show, hide the parts of the ContactList
ponent and also we can manipulate the state of the ponent.
This is one pattern that can use when creating highly reusable ponents. You can see these patterns on 3rd party libraries such as MUI, Formik and other UI libraries. These libraries use by million of developers and doesn't have such reusability issues. The developers who created those libraries expose the necessary APIs to make them highly reusable.
Note: You don't always need to use these advanced pattern. For example, this ponent is a highly reusable one but it doesn't use any special pattern. All that ponent does is, accepting dozens of props and use those props to customize the ponent as we need.
Finally all I have to say is its better to design the reusable ponents properly before thinking about forcefully manipulate them from client applications. Use props, state to manipulate child ponents. Also, A React ponent shouldn't care about its parent or child.
There's one API that can use to call methods in child ponent from parent. Still react doesn't remend to use that either
Not going to ment on the particular use case, but you can access child methods and such using forwardRef and useImperativeHandle. There are indeed situations where it is necessary to do so.
I created a working Demo here: https://codesandbox.io/s/mui5-react-final-form-datepicker-forked-z5rp2m?file=/src/Demo.tsx
import React from "react";
import { Typography, Button } from "@mui/material";
function ContactListHeader() {
return <h2>Header</h2>;
}
const ContactListBody = React.forwardRef((props: any, ref: any) => {
const [count, setCount] = React.useState(0);
React.useImperativeHandle(ref, () => ({
increaseCount() {
return setCount(count + 1);
}
}));
return (
<>
<Typography>Body Content</Typography>
<Typography>Current count: {count}</Typography>
</>
);
});
function ContactListFooter() {
return <footer>Contact List Footer</footer>;
}
const ContactList = React.forwardRef((props: any, ref: any) => {
return (
<>
<ContactListHeader key="contactlist-header" />
<ContactListBody key="contactlist-body" ref={ref} />
<ContactListFooter key="contactlist-footer" />
</>
);
});
export default function Parent() {
const contactListRef = React.useRef<any>();
const onButtonClick = () => {
contactListRef.current?.increaseCount();
};
return (
<div className="App">
<ContactList ref={contactListRef} />
<Button onClick={onButtonClick} variant="contained" color="primary">
Increase Count
</Button>
</div>
);
}
As you can see, I put a state and method inside of the ContactListBody, and I am manipulating it from the <Parent>
, via the onButtonClick
.
本文标签:
版权声明:本文标题:javascript - In Reactjs, how do you manipulate the children of a child component from a parent component? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741591534a2387157.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论