React: communicate between parent and child component - javascript

I want to communicate from my Child component (a input form) to my parent component (popup) weather or not there is any data in the child.
The issue im facing is that the child component isn't a set child in the code it gets there with the {props.children} tag:
App.js structure:
<div>
<Popup>
</Child>
</Popup>
</div>
Popup.js structure:
<div>
{this.props.children}
</div>
Is there a way to do this without using a window.* variable or frankenstein-ing a stateSet/stateRead function in my App.js?

I have done some risky stuff here, but it gets the job done:
import React, { useEffect, useState } from "react";
export default function NewApp() {
return (
<Parent pProps="boss">
<Child text="Hello1" />
<Child text="Hello2" />
<Child text="Hello3" />
</Parent>
);
}
function Parent(props) {
const [children, setChildren] = useState([]);
function communicateWithMe(val) {
console.log("I am called", val);
}
useEffect(() => {
let _children = React.Children.map(props.children, (child) => {
console.log("Parent child", child);
return {
...child,
props: {
...child.props,
callBack: communicateWithMe
}
};
});
console.log("_children", _children);
setChildren(_children);
}, []);
return (
<div
style={{
background: "black",
color: "white"
}}
>
{children}
</div>
);
}
function Child(props) {
console.log(props);
return (
<div>
<p>{props.text}</p>
{props.callBack && (
<button
onClick={() => {
props.callBack("children baby");
}}
>
invoke Parent function
</button>
)}
</div>
);
}
here is the sandbox version to see it in action:
https://codesandbox.io/s/sad-wood-5kuit3?file=/NewApp.js
Explaination:
What I aimed to do was to append the props on the children from the parent component. To do that, I casted the children (received in props by Parent) in to local state.
Appended the prop to each child to have a callback function to communicate with the parent.
The Parent component now returns the state variable that has the modified / appended props, instead of returns prop.children as it received!
EDIT:
As suggested, I have used React.Children to iterate over the children recieved by the parent in props.

Related

Adding a prop to a specific React element type in any level of the DOM tree

I'm trying to add a "position" prop to the 'Child' components and use it inside the 'Parent' component, So the only purpose of the 'PositionWrapper' is to add one prop and return the new children.
The problem is that when I call props.children inside 'Parent' I get a 'PositionWrapper' component instead of 'Child' components as I want.
I know that I can call props.children.props.children and I will get the 'Child' components but this solution doesn't look like a dynamic one (What if I remove the 'PositionWrraper'? Or what if add more wrappers?)
Does anyone know an optimal/ a better solution?
(or Am I implementing the 'PositionWraaper' correctly? )
Thanks!
The code:
Child.js:
const Child = (props) => {
return (
<>
<p>my id is :{ props.id}</p>
<p>my position is : {props.position} </p>
</>
)
}
export default Child;
PositionWrapper.js :
import React from "react"
const PositionWrapper = (props) => {
return (
React.Children.toArray(props.children).map((child)=> React.cloneElement(child, { position: [0,0,0]}))
)
}
export default PositionWrapper;
Parent.js:
import React from "react";
const Parent = ( props) => {
// here I want to do things with children components of type 'Child' but props.children consists 'Wrapper' Component.
return (
props.children
)
}
export default Parent;
App.js :
import './App.css';
import PositionWrapper from './Wrapper'
import Child from './Child';
import Parent from './Parent'
function App() {
return (
<Parent>
<PositionWrapper>
<Child id ={1} />
<Child id ={2} />
</PositionWrapper>
</Parent>
);
}
export default App;
Why dont you pass position prop to Parent - since you want to use it in Parent ?
Anyway, uou should use High-Order-Component for this scenario.
Check: https://reactjs.org/docs/higher-order-components.html
You can do a complete virtual DOM tree search to pass the props only to a specific type like below. This is a simple recursive function doing depth-first traversal.
We process the children of the child and pass it as the third argument of React.cloneElement.
React.cloneElement( element, [config], [...children] )
const Child = (props) => {
return (
<React.Fragment>
<p>my id is :{props.id}</p>
<p>my position is : {props.position} </p>
</React.Fragment>
);
};
const PositionWrapper = (props) => {
return React.Children.toArray(props.children).map((child) =>
React.cloneElement(child)
);
};
const Parent = (props) => {
// here I want to do things with children components of type 'Child' but props.children consists 'Wrapper' Component.
return passPositionPropToChildren(props.children, [1, 2, 3]);
};
const passPositionPropToChildren = (children, position) => {
return React.Children.toArray(children).map((child) => {
// Process the childrens first - depth first traversal
const childrenOfChildren =
child.props && child.props.children
? passPositionPropToChildren(child.props.children, position)
: null;
return React.isValidElement(child)
? React.cloneElement(
child,
child.type.name === "Child"
? {
...child.props,
position: position
}
: child.props,
childrenOfChildren
)
: child;
});
};
function App() {
return (
<Parent>
abc
<h2>hey</h2>
<PositionWrapper>
<Child id={1} />
<Child id={2} />
<div>
<Child id={3} />
</div>
</PositionWrapper>
<Child id={4} />
</Parent>
);
}
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>

How to mount child component outside parent using React?

I know that there is React portals that may solves the problem, but portals mount child components outside of DOM tree if I understand it correctly. But I need to render child component inside DOM tree but just outside the parent. Here the example.
I have a page:
const Page = () => {
return (
<>
// -> the place to mount <Child_2/> <--
<Parent/>
</>)
I have the Parent:
const Parent = () => {
return (
<>
<Child_1/>
<Child_2/> //<- I need it NOT to mount here but outside the parent
// in the <Page> component and not outside the DOM.
</>
)
How can I do it? And yet can I make it by portal?
In React doc I found an example just for case:
<html>
<body>
<div id="app-root"></div>
<div id="modal-root"></div>
</body>
</html>
But it is not my case...
const Page = ()=> {
const [el, setEl] = React.useState(null);
return (
<>
<div ref={setEl}></div>
{el && <Parent el={el} />}
</>
)
}
const Parent = ({el})=> {
React.useEffect();
return (
<>
<Child_1/>
{ReactDOM.createPortal(<Child2 />,el)}
</>
)
}

React on click event order array of data passing in the component

I'm new to React and I'd like some help please. I'm having a button and a component inside my app.js which is the main file
import React from 'react'
const App = () => {
const {data, loading, error} = useQuery(GET_DATA, {
variables: {...}
})
console.log(data)
state = {
clickSort: false
}
let clickSort = () => {
this.setState({
clickSort: true
})
}
return (
<div className="myApp">
<button onClick="{this.clickSort}">Click Me</button>
<div className="myClass">
<FooComponent fooData={data} clickSort={this.state.clickSort} />
</div>
</div>
)
}
What I want to do is when I click the button to sort the array of data I'm rendering in my component in a desc order. I was thinking of passing another parameter like a flag in the component, but I'm not sure how can I do this
If both of your components (<Button /> and <List />) are wrapped within common parent (<Parent />) you may employ the concept, known as lifting state up
Essentially, it is binding event handler within one of the child component's props (onSort() of <Button />) to the callback within parent (handleSort() of <Parent />), as well as binding dependent child prop (isSorted of <List />) to the state variable of common parent (sorted of <Parent />).
With that, you simply keep track of sorted flag within parent state (using useState() hook) and once handleSort() is triggered, it modifies that flag and consequent re-render of dependent components (<List />) takes place:
const { render } = ReactDOM,
{ useState } = React
const sampleData = ['itemC', 'itemA', 'itemD', 'itemB']
const Button = ({onSort}) => <button onClick={onSort}>Sort it</button>
const List = ({listData, isSorted}) => {
const listToRender = isSorted ? listData.sort((a,b) => b > a ? 1 : -1) : listData
return (
<ul>
{listToRender.map((li,key) => <li {...{key}}>{li}</li>)}
</ul>
)
}
const Parent = () => {
const [sorted, setSorted] = useState(false),
handleSort = () => setSorted(true)
return (
<div>
<Button onSort={handleSort} />
<List listData={sampleData} isSorted={sorted} />
</div>
)
}
render (
<Parent />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
It looks from your question that you want to let a child component (FooComponent) know that the button has been clicked so that it can process (sort) the data it has received.
There are a lot of approaches to this. For instance, you could pass a boolean property to the child component that is a flag for it to do the sorting. So the parent component tracks when the button has been clicked, and the child component just observes this (perhaps in componentDidUpdate).
This would change slightly if you are using functional components, rather than class based components, but it gives you an idea.
state = {
requestSort: false
}
requestSort = () => {
this.setState({
requestSort: true
}
}
render() {
return (
<>
<button id="myBtn" onClick={this.requestSort}>Click Me</button>
<div className="myClass">
<FooComponent requestSort={this.state.requestSort} fooData={data} />
</div>
</>
)
}
Alternatively, since the data is being passed to the child component as well, you could have the parent sort it when it is clicked. It depends on if you are doing anything else with the data (i.e. is only FooComponent the one that should have the sorted copy of the data or not).
Pass the data from the state into FooComponent and write a function that sorts the data in that state. The data will instantly be updated in the child component once the state has updated in the parent component because the child component will rerender once it's noticed that the data in the parent component doesn't match the data that it previously received. Below is an example.
import React from 'react'
const FooComponent = ({ fooData }) => (
<div>
{fooData}
</div>
)
export default class Home extends React.Component {
constructor(props){
super(props);
this.state = {
data: [1, 4, 2, 3]
}
}
sortData() {
const { data } = this.state;
this.setState({
data: data.sort((a, b) => b - a),
})
}
render(){
const { data } = this.state;
return (
<div>
<button id="myBtn" onClick={() => this.sortData()}>Click Me</button>
<div className="myClass">
<FooComponent fooData={data} />
</div>
</div>
)
}
}

Parent update causes remount of context consumer?

I have a wrapper component that creates a context consumer and passes the context value as a prop to a handler component. When the parent of the wrapper component updates, it's causing my handler component to remount instead of just update.
const Wrapper = forwardRef((props, ref) => {
class ContextHandler extends Component {
componentDidMount() {
// handle the context as a side effect
}
render() {
const { data, children } = this.props;
return (
<div ref={ref} {...data}>{children}</div>
);
}
}
return (
<Context.Consumer>
{
context => (
<ContextHandler
data={props}
context={context}
>
{props.children}
</ContextHandler>
)
}
</Context.Consumer>
);
});
I put the wrapper inside a parent component:
class Parent extends Component {
state = {
toggle: false
}
updateMe = () => {
this.setState(({toggle}) => ({toggle: !toggle}))
}
render() {
const { children, data } = this.props;
return (
<Wrapper
onClick={this.updateMe}
{...data}
ref={me => this.node = me}
>
{children}
</Wrapper>
)
}
}
When I click on the Wrapper and cause an update in Parent, the ContextHandler component remounts, which causes its state to reset. It should just update/reconcile and maintain state.
What am I doing wrong here?
Your ContextHandler class is implemented within the render function of the Wrapper component which means that an entirely new instance will be created on each render. To fix your issue, pull the implementation of ContextHandler out of the render function for Wrapper.

(ReactJS) Function not passing to child component

I am trying to pass props and functions from a parent to a child component in React. However, when I try to call the function in the child component, I receive the error: "Uncaught TypeError: Cannot read property 'bind' of undefined".
This would suggest that the function created in the parent element is not being accessed by the child component. Am I missing a step in my code that would allow me to reference a parent component function from a child component?
I am defining states as props in my parent component, for use in conditional rendering in my child component. I am also defining functions in the parent component, which I am trying to call in my child component.
Provided below is my code:
Note: The flow is supposed to work as follows: Click Sign Up button > Call SignUpClick function > render "SignUp" component (based on the conditional rendering logic outlined in the child component)
The same flow concept would apply if someone clicked the Sign In button.
Parent Component
export default class Parent extends Component{
constructor(props) {
super(props);
this.state = {
SignUpClicked: false,
SignInClicked: false,
};
this.SignUpClick = this.SignUpClick.bind(this);
this.SignInClick = this.SignInClick.bind(this);
}
SignUpClick() {
this.setState({
SignUpClicked: true,
});
}
SignInClick() {
this.setState({
SignInClicked: true,
});
}
render () {
return (
<div>
<Child />
</div>
)
}
}
Child Component
export default class Child extends Component {
render () {
if (this.props.SignUpClicked) {
return(
<SignUp />
) } else if (this.props.SignInClicked) {
return (
<SignIn />
)
} else {
return (
<div>
<div>
<Button onClick={this.SignUpClick.bind(this)}> Sign Up </Button>
</div>
<div>
<Button onClick={this.SignInClick.bind(this)}>Sign In</Button>
</div>
</div>
)
}
}
}
Change the render method in parent class to pass SignUpClick and SignInClick to child.
return (
<div>
<Child SignInClick={this.SignInClick} SignUpClick={this.SignUpClick}/>
</div>
)
Also, in the child class, access the methods as this.props.SignUpClick and this.props.SignInClick
If you think about it, where would the child component grab the references for those functions ? The parent needs to give the child access to those methods, and how does a parent pass data to a child, with props! In your case you are not passing any prop at all to the child, so here is how your parent render method should look like:
render () {
return (
<div>
<Child
signUpClicked={this.state.SignUpClicked}
signInClicked={this.state.SignInClicked}
onSignUpClick={this.onSignUpClick}
onSignInClick={this.onSignInClick}
/>
</div>
);
}
This is how a parent communicates with a child passing down props that are now accessible with this.props. At this point your Child component render method would look like this:
render () {
const {
signUpClicked,
signInClicked,
onSignUpClick,
onSignInClick,
} = this.props;
if (signUpClicked) {
return(<SignUp />);
} else if (signInClicked) {
return (<SignIn />);
} else {
return (
<div>
<Button onClick={onSignUpClick}> Sign Up </Button>
<Button onClick={onSignInClick}>Sign In</Button>
</div>
)
}
}
I used destructuring to help with readability. Hope this helps!

Categories

Resources