How to access parent ref from child - javascript

I have some problem to pass the ref to child element in JSX.
Please, see the following:
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
render() {
return (
<div id="parent" ref={element => (this.parentRef = element)}>
<canvas id="child" width={this.parentRef.offsetWidth} />
</div>
);
}
}
ReactDOM.render(document.getElementById("app"), <App />);
I want to access #parent width from #child. How it is possible?

This is very late, but I have a solution using the latest React hooks and functional components for future viewers. Due to the way how refs work, they do not cause a re-render on value change. However, mozilla added something called ResizeObserver: https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver , which watches components for resize.
import React, { useRef, useEffect, useState } from "react";
import ResizeObserver from "resize-observer-polyfill";
export default function App() {
const parentRef = useRef(null);
const [width, setWidth] = useState(0);
useEffect(() => {
const ro = new ResizeObserver((entries) => {
entries.forEach((entry) => setWidth(entry.contentRect.width));
});
ro.observe(parentRef.current);
return () => ro.disconnect();
}, []);
return (
<div ref={parentRef}>
<div> {width} </div>
</div>
);
}
code in action: https://codesandbox.io/s/reverent-galileo-q7np5?file=/src/App.js

In your particular example you're just getting width of an element and passing it to another element.
If you're using latest react version you should checkout new ref's api (https://reactjs.org/docs/refs-and-the-dom.html)
And your example will look something like that
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
width: 0
};
this.parentRef = React.createRef();
}
componentDidMount() {
window.addEventListener("resize", this.onResize);
this.onResize();
}
componentWillUnmount() {
window.removeEventListener("resize", this.onResize);
}
onResize = () => {
this.setState({
width: this.getParentSize()
});
};
getParentSize() {
return (this.parentRef.current && this.parentRef.current.offsetWidth) || 0;
}
render() {
return (
<div id="parent" ref={this.parentRef}>
<canvas
id="child"
width={this.getParentSize()}
style={{ background: "red" }}
/>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));

Related

How to trigger child component function

I am trying to reducing my code complexity to express by defining just skeleton code bellow. have to trigger the toggleModel of the child component
import React, { useState } from "react";
import "./styles.css";
const ChildComponent = (props) => {
// .... some useStates
const toggleModel = () => {
// have to trigger this methoud once user clicks on button
// have to change some states here
};
return (
<div>
{props.children}
...... other things .......
</div>
);
};
export default function ParentComponet() {
return (
<div className="App">
Hello
<ChildComponent>
<button
type="button"
onClick={() => {
// here i have to trigger the toggleModel function of ChildComponent
}}
>
Toggle Model
</button>
</ChildComponent>
</div>
);
}
i am rendering child component by sending children elements, have to trigger the toggleModel of the child component it will reduce my 70 % redundant code at our application. is there any way to achieve the same codesandbox. Thank you in advance
You can use useState and useEffect to pass state down and react to it.
import React, { useState } from "react";
import "./styles.css";
const ChildComponent = ({visible, children, setVisible}) => {
React.useEffect(() => {
const toggleModel = () => {
alert('Visible changes to ' + visible )
};
toggleModel()
}, [visible])
return <div>{children}</div>;
};
export default function ParentComponet() {
const [visible, setVisible] = React.useState(false)
return (
<div className="App">
Hello
<ChildComponent visible={visible} setVisible={setVisible}>
<button
type="button"
onClick={()=> setVisible(!visible)}
>
Toggle Model
</button>
</ChildComponent>
</div>
);
}
https://codesandbox.io/s/objective-ramanujan-j3eqg
The alternative is use #yaiks answer.
You can take a look at this question here, it can help you.
But I would say it's not a good practice to call a child function from the parent. Usually what I would do is to "lift up" the method to the parent, and pass down to the child if possible.
Here is another way to call your ChilComponent's function - using forwardRef:
import React, { useState, useImperativeHandle, forwardRef } from "react";
import "./styles.css";
const ChildComponent = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
toggleModel() {
alert("alert from ChildComponent");
}
}));
return <div>{props.children}</div>;
});
export default function ParentComponet() {
return (
<div className="App">
Hello
<ChildComponent ref={ChildComponent}>
<button
type="button"
onClick={() => ChildComponent.current.toggleModel()}
>
Toggle Model
</button>
</ChildComponent>
</div>
);
}
Sandbox: https://codesandbox.io/s/pensive-jones-lw0pf?file=/src/App.js
My answer is courtesy of #rossipedia: https://stackoverflow.com/a/37950970/1927991

A string ref, "grid", has been found within a strict mode tree

I am facing given warning in console:
Warning: A string ref, "grid", has been found within a
strict mode tree. String refs are a source of potential bugs and
should be avoided. We recommend using useRef() or createRef() instead.
Here's my line of codes:
render() {
return (
<div className="notes-app">
<h2 className="app-header">NotesApp</h2>
<NoteSearch onSearch={text => this.handleSearch(text)} />
<NoteEditor onNoteAdd={this.handleNoteAdd} />
<NotesGrid
notes={this.state.filteredNotes}
onNoteDelete={this.handleNoteDelete}
/>
</div>
);
}
The snippet of the code editor shows the lines where the warning is pointed
The NotesGrid component is as follows:
import Masonry from "masonry-layout";
import React from 'react';
import Note from "./Note";
export default class NotesGrid extends React.Component {
componentDidMount() {
var grid = this.refs.grid;
this.msnry = new Masonry(grid, {
itemSelector: ".note",
columnWidth: 200,
gutter: 10,
isFitWidth: true
});
}
componentDidUpdate(prevProps) {
if (this.props.notes.length !== prevProps.notes.length) {
this.msnry.reloadItems();
this.msnry.layout();
}
}
render() {
var onNoteDelete = this.props.onNoteDelete;
return (
<div className="notes-grid" ref="grid"> //here I Have used the ref
{this.props.notes.map(function(note) {
return (
<Note
key={note.id}
onDelete={onNoteDelete.bind(null, note)}
color={note.color}
>
{note.text}
</Note>
);
})}
</div>
);
}
}
What's the best alternative to resolve it?
Rewrite the NotesGrid as below
import Masonry from "masonry-layout";
import React from 'react';
import Note from "./Note";
export default class NotesGrid extends React.Component {
constructor(props) {
super(props);
this.gridRef = React.createRef();
}
componentDidMount() {
this.msnry = new Masonry(this.gridRef.current, {
itemSelector: ".note",
columnWidth: 200,
gutter: 10,
isFitWidth: true
});
}
componentDidUpdate(prevProps) {
if (this.props.notes.length !== prevProps.notes.length) {
this.msnry.reloadItems();
this.msnry.layout();
}
}
render() {
var onNoteDelete = this.props.onNoteDelete;
return (
<div className="notes-grid" ref={this.gridRef}>
{this.props.notes.map((note) => (
<Note
key={note.id}
onDelete={onNoteDelete.bind(null, note)}
color={note.color}
>
{note.text}
</Note>
))}
</div>
);
}
}
The issue is with the component NotesGrid. Check whether the component has usage of 'ref' or not.
If ref is using in that component. create the ref using React.createRef() (if its a class component) or using useRef(if its a functional component)

Call Method that inside Another Component - React Js

i need to know how to fetch state of component from other component by calling the seconed component method inside of first component ?
like :
class General extends Component {
state = {
input:"
}
fetchState() {
return this.state;
}
handleChange () {
this.setState({[e.target.name]: e.traget.value});
}
render() {
return <input type="text" name="input" onChange={this.handleChange.bind(this}>
}
}
class Car extends Component {
render() {
console.log( General.fetchState() );
return null;
}
}
i know i can use static method but i don't have access to this keyword.
The recommended way of doing that kind of things is by composing components and passing the parent's states as props
class General extends Component {
state = { ... }
render () {
return (
<Car {...this.state} />
)
}
}
class Car extends Component {
render () {
console.log(this.props)
return (...)
}
}
Now if you want to share a global state between components could be a good idea to use context api with hooks.
import React, { createContext, useContext } from "react";
import ReactDom from "react-dom";
const initialState = { sharedValue: "Simple is better" };
const StateContext = createContext({});
const General = () => {
const globalState = useContext(StateContext);
return <h1>General: {globalState.sharedValue}</h1>;
};
const Car = () => {
const globalState = useContext(StateContext);
return <h1>Car: {globalState.sharedValue}</h1>;
};
const App = () => {
return (
<StateContext.Provider value={initialState}>
<General />
<Car />
</StateContext.Provider>
);
};
ReactDom.render(
<App />,
document.getElementById("root")
);
Here is the example link.
And here I have a repo with a more elaborated example managing global state with just hooks.
There are many approaches, I suggest using a general state accessible from both components.
Check ReactN for simplicity or Redux for a more robust solution. Note Redux has a big learning curve and quite some boilerplate that, depending on the size of your App, it could not be necessary.
Using globals is not advisable on many situations, but to answer your question, you could also do this:
General component:
class General extends Component {
constructor(){
global.fetchGeneralState = this.fetchState;
}
fetchState = () => {
return this.state;
}
}
Then from the Car component, you can just call: global.fetchGeneralState(); and you will get the state from the General component.
In your current code, the only way to do it is to use new General.
console.log(new General().fetchState());
If you expect to use Car component as a parent of General component, then you can simply use ref. Here is the modified code of yours that I have tested :
import React from "react";
class General extends React.Component {
constructor () {
super();
this.state = {input: ""}
this.handleChange = this.handleChange.bind(this);
}
fetchState() {
return this.state;
}
handleChange(e) {
this.setState({[e.target.name]: e.target.value});
}
render() {
return <input type="text" name="input" onChange={this.handleChange} />
}
}
export default class Car extends React.Component {
constructor () {
super();
this.refToGeneral = React.createRef()
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
console.log(this.refToGeneral.current.fetchState())
}
render() {
return (
<>
<General ref={this.refToGeneral} />
<button type="button" onClick={this.handleClick}>Show State</button>
</>
)
}
}

get the value from a text input after it is updated in react function

I'm learning React and I need help understanding how to create functions for values that are updated asynchronously in the DOM. For instance, I have a text input within a component called header that looks like this:
export default class Header extends React.Component {
constructor(props){
super(props);
}
render(){
return (
<div className="Header">
<div><input onKeyDown={this.props.onEnter} id="filter-results" className="full" type="text" placeholder="search kks"></input></div>
<div><button className="full">SEARCH</button></div>
</div>
);
}
}
, which is used to filter search results. The onEnter function tries to use the value updated in the input:
class App extends React.Component {
constructor(props){
super(props);
this.state = {
categories: [],
searchResults: [],
};
this.filterSearch = this.filterSearch.bind(this);
}
filterSearch(){
var el = document.getElementById('filter-results').value
console.log(el)
var result = this.state.categories.filter(row => {
var rx = new RegExp(el)
return rx.test(row['id'])
});
console.log(result)
}
render(){
return (
<div className="App">
<Header onEnter={this.filterSearch}/>
</div>
);
}
}
When I type something into the input, the element's value is logged to the console. The problem is, what is logged is always one character less than what I expect to see. If I type 'a', I get '', 'ab' => 'a', etc. I can understand conceptually that when the function is triggered and the logging occurs the value hasn't yet been updated, but I don't know how to wait for the value to be updated and then work with it. Can anyone help me?
Use onChange instead.
//change handler
handler(e) {
console.log(e.target.value)
}
//input's onChange event
onChange={ this.handler.bind(this) }
1) You should not be using native javascript to get value by id. This is not react way of doing.
App.js
import React from "react";
import ReactDOM from "react-dom";
import Header from "./Header";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
categories: [],
searchResults: []
};
this.filterSearch = this.filterSearch.bind(this);
}
filterSearch(value) {
console.log(value);
var result = this.state.categories.filter(row => {
var rx = new RegExp(value);
return rx.test(row["id"]);
});
console.log(result);
}
render() {
return (
<div className="App">
<Header onEnter={this.filterSearch} />
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
// Header.js
import React from "react";
export default class Header extends React.Component {
constructor(props) {
super(props);
}
handleChange = ({ target }) => {
this.setState({
[target.name]: target.value
});
this.props.onEnter(target.value);
};
render() {
return (
<div className="Header">
<div>
<input
onChange={this.handleChange}
name="filter-results"
className="full"
type="text"
placeholder="search kks"
/>
</div>
<div>
<button className="full">SEARCH</button>
</div>
</div>
);
}
}

How to use leader-line (an external javascript library) in react?

I want to use leader-line in my React web project. It is an external javascript library, but I don't know how to integrate it into the project with the JSX syntax.
For example, its documentation tells us the general implementation:
Html
<div id="start">start</div>
<div id="end">end</div>
Javascript
// Add new leader line from `start` to `end` (HTML/SVG elements, basically).
new LeaderLine(
document.getElementById('start'),
document.getElementById('end')
);
How should I write in JSX file?
I try to write below, but failed.
import React, { Component } from 'react';
import LeaderLine from 'leader-line'
class Page extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
new LeaderLine(document.getElementById('start'),
document.getElementById('end'));
}
render() {
return (
<div className="Page">
<div id="start"></div>
<div id="end"></div>
</div>
)
}
}
export default Page;
This is the npm package page of leader-line.
Depending on what you are trying to achieve with leader-line, you may find that you can achieve it just as well with react-xarrows.
https://www.npmjs.com/package/react-xarrows
React-xarrows can be integrated into a React app much more easily (even using DOM identifiers rather than React Refs, if you prefer).
See this example code (taken directly from the link above), showing usage.
import React, { useRef } from "react";
import Xarrow from "react-xarrows";
const boxStyle = {
border: "grey solid 2px",
borderRadius: "10px",
padding: "5px",
};
function SimpleExample() {
const box1Ref = useRef(null);
return (
<div
style={{ display: "flex", justifyContent: "space-evenly", width: "100%" }}
>
<div ref={box1Ref} style={boxStyle}>
hey
</div>
<p id="elem2" style={boxStyle}>
hey2
</p>
<Xarrow
start={box1Ref} //can be react ref
end="elem2" //or an id
/>
</div>
);
}
I've made a small prototype to illustrate how it could be achieved.
class Line extends React.Component {
componentDidMount () {
this.waitWhenRefIsReady();
// scroll and resize listeners could be assigned here
}
componentWillUnmount () {
if(this.timer) {
clearInterval(this.timer);
}
}
shouldComponentUpdate () {
setTimeout(() => {
// skip current even loop and wait
// the end of parent's render call
if(this.line) {
this.line.position();
}
}, 0);
// you should disable react render at all
return false;
}
waitWhenRefIsReady () {
// refs are generated via mutations - wait for them
this.timer = setInterval(() => {
if(this.props.start.current) {
clearInterval(this.timer);
this.drawLine();
}
}, 5);
}
drawLine () {
const {start, end} = this.props;
this.line = new LeaderLine(start.current, end.current);
}
render () {
return null;
}
}
class App extends React.Component {
constructor (props) {
super(props);
this.state = {
left: 0,
};
this.myRef1 = React.createRef();
this.myRef2 = React.createRef();
}
componentDidMount() {
this.animateLine();
}
animateLine() {
setInterval(() => {
const limit = 200;
const {left} = this.state;
const x = ((left % limit) + limit) % limit;
this.setState({left: x + 10});
}, 1000);
}
render () {
const {left} = this.state;
const {myRef1, myRef2} = this;
return <div className="container">
<Line
start={this.myRef1}
end={this.myRef2} />
<div
id="start"
ref={this.myRef1}
style={{
left: `${left}px`
}}></div>
<div
id="end"
ref={this.myRef2}></div>
</div>
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
Leader Line + React JSX simple prototype
import LeaderLine from 'leader-line';
Add in leader-line.min.js (at end)
if (module && module.exports) { module.exports = LeaderLine }
Refer to this thread for how to integrate Leaderline into your react project :
https://github.com/anseki/leader-line/issues/8#issuecomment-370147614
in summary,
you cant just do
import LeaderLine from 'leader-line';
to import LeaderLine, because its not an ES2015 module yet!
similar to how #shilpa pointed out,
you can tweak the webpack config to include -
rules: [
{
test: require('path').resolve(__dirname, 'node_modules/leader-line/'),
use: [{
loader: 'skeleton-loader',
options: {procedure: content => `${content}export default LeaderLine`}
}]
}
and then inside componentDidMount, you could do
new LeaderLine(document.getElementById('start'),
document.getElementById('end'));

Categories