Dom contains or compareDocumentPosition analog in react - reactjs

I have some component that contains another portaled component like
<Parent>
<Portal>
<Child>Lorem Ipsum</Child>
</Portal>
</Parent>
But on real dom it look's like:
<div class="parent"></div>
<div class="child">Lorem Ipsum</div>
I need to know, that native node of Child component is child of Parent.

Well, i had a slightly different problem. And that the Parent containes Child, we can know from event bubbling.
Portal:
class Portal extends React.Component {
el: any;
constructor(props: any) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
document.body.appendChild(this.el);
}
componentWillUnmount() {
document.body.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(
this.props.children,
this.el,
);
}
}
Child:
class Child extends React.Component {
render() {
return <div className="child">Lorem Ipsum</div>;
}
}
Parent:
class Parent extends React.Component {
target: any;
componentDidMount () {
document.addEventListener('mousedown', this.onDocMouseDown);
}
componentWillUnmount () {
document.removeEventListener('mousedown', this.onDocMouseDown);
}
onDocMouseDown = (e: any) => {
const target = e.target;
const isChild = this.target === target;
if (isChild) {
// do something
}
this.target = null;
};
onElemMouseDown = (e: any) => {
this.target = e.target;
};
render() {
return (
<div className="parent" onMouseDown={this.onElemMouseDown}>
<Portal>
<Child />
</Portal>
</div>
);
}
}
From React Portal documentation:
An event fired from inside a portal will propagate to ancestors in the
containing React tree, even if those elements are not ancestors in the
DOM tree
In Parent class we have handle for event 'mousedown' on document and on the wrapper div. When mousedown is fired, first will work wrapper handle. We save it to some variable like 'this.target'. Then event bubbling to document and in handle for document we can equal 'e.target' with 'this.target'. Accordingly we can to know is this target in Parent or not.

Related

Learner question: How to pass data from one class to another

I am learning spfx dev. I am creating a form with several different classes to learn how they can interact and pass data between each other.
I have two separate classes. One Parent class has a submit button which uses the Parents state to submit to a SharePoint list.
The other class component has it's own set of states and fields. I want whatever is entered by the user in the child component, to be submittable(!) by the parent class.
Here's my submit function:
private _onSubmit() {
this.setState({
FormStatus: 'Submitted',
SubmittedLblVis: true,
}, () => {
pnp.sp.web.lists.getByTitle("JobEvaluationItems").items.add({
JobTitle: this.state.JobTitle,
Faculty: this.state.Faculty,
Department: this.state.SelectedDept,
SubDepartment: this.state.SubDepartment,
DeptContactId: this.state.DeptContact,
FormStatus: this.state.FormStatus,
EvalType: this.state.EvalType,
JobTitReportTo: this.state.JobTitReportTo
}).then((iar: ItemAddResult) => {
let list = pnp.sp.web.lists.getByTitle("JobEvaluationItems");
list.items.getById(iar.data.Id).update({
JobRef: 'JE'+iar.data.Id
});
this.setState({
JobRef: iar.data.Id
});
});
});
}
Here is a function from the child component which handles whatever is typed into a field:
private _onJobTitReportToChange = (ev: React.FormEvent<HTMLInputElement>, newValue?: string) => {
this.setState({
JobTitReportTo: newValue
});
}
How would I pass the state function above (which is held within the child component) to the Parent component?
class Child extends React.Component {
state = {
childValue: 1
}
onChange = e => {
this.setState({childValue: e.target.value}, () => {
this.props.onChange(this.state);
})
}
render () {
return <input value={this.state.childValue} onChange={this.onChange} />
}
}
class Parent extends React.Component {
state = {
parentValue: 123,
dataFromChild: null
}
handleChildChange = childData => {
this.setState({dataFromChild: childData});
}
render () {
return (
<div>
<Child onChange={this.handleChildChange} />
<pre>{JSON.stringify(this.state, null, 2)}</pre>
</div>
)
}
}
ReactDOM.render(<Parent />, document.querySelector("#root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
In React world are two common used ways to transfer data:
If you want to pass it down to the child component - use props;
If you want to pass it up to the parent component - use callback;
There is another way - Context, but it's a whole different story.
if you want to pass data from one component to other.Follow the below steps.
1.PARENT --> CHILD
In parent component's render
render(){
return (
<ChildComponent data1={} data2={}/>
)
}
2.CHILD-->PARENT
make a handler in your submit function which is received to this child component from props
//CHILD COMPONENT
onSubmit=()=>{
...
//some data
...
this.props.onSubmit(data)
}
//Parent component
render(){
return(
....
<ChildComponent onSubmit={this.onSubmit}/>
....
)
}
How would I pass the state function above (which is held within the child component) to the Parent component?
It's one of React's concepts called lifting state up.
class Parent extends React.Component {
const someFunction = () => {} // move the function to the parent
render() {
return (
<>
<ChildComponent someFunction={someFunction} /> // pass function down to child
</>
)
}
}
const ChildComponent = (props) => {
return <Button onClick={props.someFunction} /> // use parent function
}

Sending onMouseDown from parent to child

Is there a way to send the clickevent from the parent to the child?
This is my parent component:
<Component {...props}>
<Child />
{props.children}
</Component>
This is the child component:
<Component onMouseDown={e => this.handleClick(e, props)}></Component>
Whenever the parent component is clicked I want to trigger the handleclick component of my child.
Thanks in advance!
You can use a reference to your child component:
// parent.js
constructor(props) {
super(props);
this.child = React.createRef();
}
handleMouseDown = e => {
this.child.current.handleClick(e, this.props);
}
render() {
return (
<Component onMouseDown={this.handleMouseDown} {...props}>
<Child ref={this.child}/>
{props.children}
</Component>
)
}
You can do this using rxjs with Observable and Subscriptions. Here is a working example and I'll explain what's going on https://codesandbox.io/s/7wjwnznk3j
Relevant reading:
fromEvent: https://rxjs-dev.firebaseapp.com/api/index/function/fromEvent
subscription: https://rxjs-dev.firebaseapp.com/api/index/class/Subscription
I used Typescript since I prefer it, but is absolutely not a requirement. You parent class will look like this:
interface State {
obs$?: Observable;
}
class App extends React.Component<null, State> {
public readonly state: State = {};
public ref: React.Ref<React.ReactHTMLElement>;
componentDidMount() {
this.setState({
obs$: fromEvent(this.ref, 'click')
});
}
#Bind()
setParentRef(el: HTMLElement) {
this.ref = el;
}
render() {
return (
<div style={parentStyles} ref={this.setParentRef}>
<Child parentClick={this.state.obs$} />
</div>
);
}
}
We have our ref this.ref and set it through the function, we need this since it is the target of a fromEvent and click is the event. This automatically creates an observable that will emit to any subscribers when it is clicked. You will want to pass this as a prop to your child component. Then in that component you can subscribe to it and do whatever you want when there is a click in the parent.
interface Props {
parentClick?: Observable;
}
interface State {
onClick$?: Subscription;
numClicks: number;
}
class Child extends React.Component<Props, State> {
public readonly state: State = { numClicks: 0 };
componentDidMount() {
if (this.props.parentclick) {
this.handle();
}
}
componentDidUpdate(prevProps: Props) {
if (
this.props.parentClick !== undefined &&
this.state.onClick$ === undefined
) {
this.handleSubscribe();
}
}
componentWillUnmount() {
if (this.state.onClick$) {
this.state.onClick$.unsubscribe();
}
}
handleSubscribe() {
this.setState({
onClick$: this.props.parentClick.subscribe(this.onParentClick)
});
}
#Bind()
onParentClick() {
this.setState((prevState: State) => ({
numClicks: prevState.numClicks + 1
}));
}
render() {
return (
<div style={childStyles}>
Parent clicked {this.state.numClicks} time(s)
</div>
);
}
}
So in this instance, when the parent is clicked the subscription invokes the onParentClick method. Then in that method we implement a simple counter and display it in the HTML.
One thing important thing is to ALWAYS make sure you unsubscribe from subscriptions. If you don't this will create a memory leak and will be really tricky to track down, since it is easy to overlook.

ReactJS render element by ref

I am trying to render an parent-component which has two children. The rendering of the children will switch, so one time there will be only the first child rendered, another time it will be the last and finally it will switch back to the first child (which then should contain all the values shown before).
I thought this would be simple but it turned out that it is not.
Now to the problem: Whenever the method switchContainer is called, it will switch the container and render the other. However all member-variables, props and states are getting lost and it basically reinstanciated the child-component from scratch.
Is there a way to save the child-components "as-is" and once it is getting re-rendered, it will hold all the member-variables, props and states again?
I know that you can send props and states to the element like this:
<Child props={<data>} states={<data>}>
but this doesn't solve the issue with the missing membervariables and in my opinion it isn't a smooth solution.
My attempt so far is (this is just a mockup):
class Parent extends React.Component<any,any> {
private firstContainer:any;
private secondContainer:any;
private currentContainer:any;
constructor(props:any) {
super(props);
this.firstContainer = <Child>;
this.secondContainer = <Child>;
}
public render() {
return (
<div>
{this.currentContainer}
</div>
);
}
public switchContainer() {
if(this.currentContainer === this.firstContainer) {
this.currentContainer = this.secondContainer;
}
else {
this.currentContainer = this.firstContainer;
}
this.forceUpdate();
}
}
class Child extends React.Component<any,any> {
private anyValue:string;
constructor(props) {
this.change = this.change.bind(this);
}
public render() {
return (
<input onChange={this.change} value={this.anyValue}/>
);
}
private change(e:any) {
this.anyValue = e.target.value;
}
}
You can try maintaining a state and update children in render instead of saving child as firstContainer and secondContainer
class Parent extends React.Component<any, any> {
constructor(props) {
super(props);
this.state = {
firstChild: true
};
}
public render() {
const { firstChild } = this.state;
<div>
<Child1 show={firstChild}/>
<Child2 show={!firstChild} />
</div>
}
public switchContainer() {
this.setState(({ firstChild }) => ({ firstChild: !firstChild }));
};
}
And in child component, handle show to showContent otherwise render null. If you want to retain state, you should not unmount the component.

How to share a property with React components?

I'm new to React and I have a question about sharing properties from one component to another. For example, I want a parent component that has a "visible" function that I can pass to other child components.
Example:
CustomInput visible="true";
CustomDropDown visible="false"
I'd like to know the best way to do this, respecting good practices. Thank you for your help!
Real simple. You can pass methods as props. Suppose you have a parent, or Higher Order Component (HOC), you could do something like this:
class Parent extends React.Component {
logWord = (word) => {
console.log(word);
}
render () {
return <ChildComponent handleLogging={ this.logWord } />
}
}
Then, in the ChildComponent, you simply access the method from props. For instance:
class ChildComponent extends React.Component {
render () {
return (
<div onClick={ this.props.handleLog.bind(null, 'Logged!') }>Click me to log a word!</div>
}
}
}
So, in your example, if you wanted a method that existed on the parent that updated a visibility attribute on your state, you could write:
class Parent extends React.Component {
constructor () {
this.state = {
visible: false
}
}
setVisible = (bool) => {
this.setState({ visible: bool });
}
render () {
return <ChildComponent updateVisible={ this.setVisible } visible={ this.state.visible } />;
}
}
ChildComponent:
class ChildComponent extends React.Component {
render () {
return (
<div>
<div onClick={ this.props.updateVisible.bind(null, true) }>Set me to visible!</div>
<div onClick={ this.props.updateVisible.bind(null, false) }>Set me to invisible!</div>
{ this.props.visible && <div>I'm visible right now!</div> }
</div>
}
}
}

Getting DOM node from React child element

Using the React.findDOMNode method that was introduced in v0.13.0 I am able to get the DOM node of each child component that was passed into a parent by mapping over this.props.children.
However, if some of the children happen to be React Elements rather than Components (e.g. one of the children is a <div> created via JSX) React throws an invariant violation error.
Is there a way to get the correct DOM node of each child after mount regardless of what class the child is?
this.props.children should either be a ReactElement or an array of ReactElement, but not components.
To get the DOM nodes of the children elements, you need to clone them and assign them a new ref.
render() {
return (
<div>
{React.Children.map(this.props.children, (element, idx) => {
return React.cloneElement(element, { ref: idx });
})}
</div>
);
}
You can then access the child components via this.refs[childIdx], and retrieve their DOM nodes via ReactDOM.findDOMNode(this.refs[childIdx]).
If you want to access any DOM element simply add ref attribute and you can directly access that element.
<input type="text" ref="myinput">
And then you can directly:
componentDidMount: function()
{
this.refs.myinput.select();
},
Their is no need of using ReactDOM.findDOMNode(), if you have added a ref to any element.
This may be possible by using the refs attribute.
In the example of wanting to to reach a <div> what you would want to do is use is <div ref="myExample">. Then you would be able to get that DOM node by using React.findDOMNode(this.refs.myExample).
From there getting the correct DOM node of each child may be as simple as mapping over this.refs.myExample.children(I haven't tested that yet) but you'll at least be able to grab any specific mounted child node by using the ref attribute.
Here's the official react documentation on refs for more info.
You can do this using the new React ref api.
function ChildComponent({ childRef }) {
return <div ref={childRef} />;
}
class Parent extends React.Component {
myRef = React.createRef();
get doSomethingWithChildRef() {
console.log(this.myRef); // Will access child DOM node.
}
render() {
return <ChildComponent childRef={this.myRef} />;
}
}
React.findDOMNode(this.refs.myExample) mentioned in another answer has been deprectaed.
use ReactDOM.findDOMNode from 'react-dom' instead
import ReactDOM from 'react-dom'
let myExample = ReactDOM.findDOMNode(this.refs.myExample)
I found an easy way using the new callback refs. You can just pass a callback as a prop to the child component. Like this:
class Container extends React.Component {
constructor(props) {
super(props)
this.setRef = this.setRef.bind(this)
}
setRef(node) {
this.childRef = node
}
render() {
return <Child setRef={ this.setRef }/>
}
}
const Child = ({ setRef }) => (
<div ref={ setRef }>
</div>
)
Here's an example of doing this with a modal:
class Container extends React.Component {
constructor(props) {
super(props)
this.state = {
modalOpen: false
}
this.open = this.open.bind(this)
this.close = this.close.bind(this)
this.setModal = this.setModal.bind(this)
}
open() {
this.setState({ open: true })
}
close(event) {
if (!this.modal.contains(event.target)) {
this.setState({ open: false })
}
}
setModal(node) {
this.modal = node
}
render() {
let { modalOpen } = this.state
return (
<div>
<button onClick={ this.open }>Open</button>
{
modalOpen ? <Modal close={ this.close } setModal={ this.setModal }/> : null
}
</div>
)
}
}
const Modal = ({ close, setModal }) => (
<div className='modal' onClick={ close }>
<div className='modal-window' ref={ setModal }>
</div>
</div>
)

Resources