Using dynamic onClick with ref - javascript

Passing a dynamic property of onClick= do something by the use of ref gives me back: TypeError: _this.listReference is null listReference is defined in one of my components that i will show below.
In Component #1
class Component1 extends Component {
constructor(props){
super(props)
this.listReference= null;
}
//Returns
<div>
<SomeComponent list={(ref) => this.listReference= ref} />
<Component2 onMarkerClick = {(index) => {
this.listReference.scrollTop = 48 * index
}}/>
In Component #2
render() {
const {classes, driversStore, onMarkerCLick} = this.props
...
{driversStore.sortedSelectedOrders.map((order , index) => {
return (
<Component3
onClick={ () => onMarkerClick(index)} />
In Component #3
render() {
const { onClick } = this.props;
return (
<div
onClick={onClick}>
I expect upon click to trigger the scroll functionality (as Stated in Component #1).
Thanks in advance!

Check this example. Hope it can help you!
const Component2 = (props) =>(
<button onClick={props.onClick}>click me</button>
);
const SomeCompo = (props) =>(
<div>SomeComponent</div>
);
class Component1 extends React.Component{
listReference = React.createRef();
render(){
return(
<div>
<SomeCompo list={this.listReference}>reference</SomeCompo>
<Component2 onClick={this.handleClick} />
</div>
);
}
handleClick = () => {
if(this.listReference){
this.listReference={scrollTop:100};
}
console.log(this.listReference)
}
}
ReactDOM.render(<Component1/>,document.getElementById("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>
You should do the following in constructor,
this.listReference = React.createRef()

Related

React: Render and link toggle button outside the class

I have the following example where the toggleComponent.js is working perfectly.
The problem here is that I don't want to render the <ContentComponent/> inside the toggle, rather I want the opposite, I want to toggle the <ContentComponent/> that will be called in another component depending on the state of the toggle.
So the <ContentComponent/> is outside the toggleComponent.js, but they are linked together. So I can display it externally using the toggle.
An image to give you an idea:
Link to funtional code:
https://stackblitz.com/edit/react-fwn3rn?file=src/App.js
import React, { Component } from "react";
import ToggleComponent from "./toggleComponent";
import ContentComponent from "./content";
export default class App extends React.Component {
render() {
return (
<div>
<ToggleComponent
render={({ isShowBody, checkbox }) => (
<div>
{isShowBody && <h1>test</h1>}
<button onClick={checkbox}>Show</button>
</div>
)}
/>
<ToggleComponent
render={({ isShowBody, checkbox }) => (
<div>
{isShowBody && (
<h1>
<ContentComponent />
</h1>
)}
<button onClick={checkbox}>Show</button>
</div>
)}
/>
</div>
);
}
}
Bit tweaked your source.
Modified ToggleComponent
import React from "react";
export default class ToggleComponent extends React.Component {
constructor() {
super();
this.state = {
checked: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick = () => {
this.setState({ checked: !this.state.checked });
this.props.toggled(!this.state.checked);
};
checkbox = () => {
return (
<div>
<label>Toggle</label>
<span className="switch switch-sm">
<label>
<input type="checkbox" name="select" onClick={this.handleClick} />
<span />
</label>
</span>
</div>
);
};
render() {
return this.checkbox();
}
}
Added OtherComponent with ContentComponent inside.
import React, { Component } from "react";
import ContentComponent from "./content";
export default class OtherComponent extends React.Component {
render() {
return <div>{this.props.show ? <ContentComponent /> : null}</div>;
}
}
Separated as per your requirement.
Modified App
import React, { Component, PropTypes } from "react";
import ToggleComponent from "./toggleComponent";
import OtherComponent from "./otherComponent";
export default class App extends React.Component {
constructor() {
super();
this.toggled = this.toggled.bind(this);
this.state = { show: false };
}
toggled(value) {
this.setState({ show: value });
}
render() {
return (
<div>
<ToggleComponent toggled={this.toggled} />
<OtherComponent show={this.state.show} />
</div>
);
}
}
Working demo at StackBlitz.
If you want to share states across components a good way to do that is to use callbacks and states. I will use below some functional components but the same principle can be applied with class based components and their setState function.
You can see this example running here, I've tried to reproduce a bit what you showed in your question.
import React, { useState, useEffect, useCallback } from "react";
import "./style.css";
const ToggleComponent = props => {
const { label: labelText, checked, onClick } = props;
return (
<label>
<input type="checkbox" checked={checked} onClick={onClick} />
{labelText}
</label>
);
};
const ContentComponent = props => {
const { label, children, render: renderFromProps, onChange } = props;
const [checked, setChecked] = useState(false);
const defaultRender = () => null;
const render = renderFromProps || children || defaultRender;
return (
<div>
<ToggleComponent
label={label}
checked={checked}
onClick={() => {
setChecked(previousChecked => !previousChecked);
}}
/>
{render(checked)}
</div>
);
};
const Holder = () => {
return (
<div>
<ContentComponent label="First">
{checked => (
<h1>First content ({checked ? "checked" : "unchecked"})</h1>
)}
</ContentComponent>
<ContentComponent
label="Second"
render={checked => (checked ? <h1>Second content</h1> : null)}
/>
</div>
);
};
PS: A good rule of thumb concerning state management is to try to avoid bi-directional state handling. For instance here in my example I don't use an internal state in ToggleComponent because it would require to update it if given checked property has changed. If you want to have this kind of shared state changes then you need to use useEffect on functional component.
const ContentComponent = props => {
const { checked: checkedFromProps, label, children, render: renderFromProps, onChange } = props;
const [checked, setChecked] = useState(checkedFromProps || false);
const defaultRender = () => null;
const render = renderFromProps || children || defaultRender;
// onChange callback
useEffect(() => {
if (onChange) {
onChange(checked);
}
}, [ checked, onChange ]);
// update from props
useEffect(() => {
setChecked(checkedFromProps);
}, [ checkedFromProps, setChecked ]);
return (
<div>
<ToggleComponent
label={label}
checked={checked}
onClick={() => {
setChecked(previousChecked => !previousChecked);
}}
/>
{render(checked)}
</div>
);
};
const Other = () => {
const [ checked, setChecked ] = useState(true);
return (
<div>
{ checked ? "Checked" : "Unchecked" }
<ContentComponent checked={checked} onChange={setChecked} />
</div>
);
};

Why would removing a component affects lifecycle of another component?

The following code example contains 2 components: Component1 logs prevState with componentDidUpdate method, Component2 is removed on a button click to show the use of componentWillUnmount.
const { useState } = React;
class Component1 extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'John',
};
}
componentDidUpdate(prevProps, prevState) {
console.log(prevState);
}
render() {
return (
<div>
<div>The name is {this.state.name}</div>
<label htmlFor="prevstate">Type here to see prevState in console: </label>
<input
id="prevstate"
onChange={(e) => {
this.setState({ name: e.target.value });
}}
/>
</div>
);
}
}
class Component2 extends React.Component {
componentDidMount() {
console.log('Mount');
}
componentWillUnmount() {
console.log('Unmount');
}
render() {
return (
<div>
<div>Click the button to remove the element</div>
</div>
);
}
}
const App = () => {
const [showComponent, setShowComponent] = useState(true);
return (
<div>
<Component1 />
{showComponent ? <Component2 /> : null}
<button
onClick={() => {
setShowComponent(false);
}}
>
Remove
</button>
</div>
);
};
// Render it
ReactDOM.render(<App />, document.getElementById('react'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Now I find that these 2 components interfere with each other: clicking the button also logs the prevState in the first component. What I thought would happen was that the componentDidUpdate method would only monitor the update of Component1, but seems it monitors the button which is outside of both components, why does this happen?
When the App is rendered, its children are rendered as well. You might try making Component1 a React.PureComponent or defining shouldComponentUpdate if you want to reduce the rerendering.

How to access properties in child from children props in react

The title is pretty straightforward, I need to access a property (a ref to be precise) on a child element that is passed through the children of my component, which means that I can't pass the ref in the parent afaik.
Here's a minimal example to highlight my issue:
import React from "react";
class Child extends React.Component {
myRef = React.createRef();
render() {
return <div ref={this.myRef}>child</div>;
}
}
const Parent = ({ children }) => {
const myChild = React.Children.toArray(children).find(
child => child.type === Child
);
// I want to access this
console.log(myChild.myRef);
// but it's undefined
return (
<div>
<h1>Parent</h1>
{children}
</div>
);
};
// I can't really change this component
export default function App() {
return (
<div className="App">
<Parent>
<Child />
</Parent>
</div>
);
}
I made a codesandbox highlighting my issue https://codesandbox.io/s/eloquent-wing-e0ejh?file=/src/App.js
Rather than declaring ref in <Child/>, you should declare ref in your <Parent/> and pass it to the child.
import React from "react";
class Child extends React.Component {
render() {
return <div ref={this.props.myRef}>child</div>;
}
}
const Parent = ({ children }) => {
const myRef = React.useRef(null);
// access it from here or do other thing
console.log(myRef);
return (
<div>
<h1>Parent</h1>
{ children(myRef) }
</div>
);
};
export default function App() {
return (
<div className="App">
<Parent>
{myRef => (
<Child myRef={myRef} />
)}
</Parent>
</div>
);
}

React get height of component

I need to know the height of a React Component inside another React component. I am aware that the height of an element can be reached by calling this.cmref.current.clientHeight. I'm looking for something like this:
child component:
const Comp = () =>{
return(
<div>some other stuff here</div>
)
}
export default Comp
parent component:
class App extends React.Component{
constructor(props){
super(props);
this.compref = React.createRef();
}
componentDidSomething(){
const height = this.compref.current.clientHeight;
//which will be undefined
}
render(){
return(
<div>
<Comp ref={this.compref} />
</div>
)
}
}
Is this possible? Thanks in advance.
You'll need to actually ref the div of the child component in order to get the element you want instead of the child component itself. To do this you could pass a function to the child that the child then passes to the div. Working example below:
const Comp = (props) =>{
return(
<div ref={props.onRef}>some other stuff here</div>
)
}
class App extends React.Component{
constructor(props){
super(props);
this.compref = React.createRef();
}
componentDidMount(){
const height = this.compref.current.clientHeight;
//which will be undefined --- No more!
console.log('height: ', height);
}
onCompRef = (ref) => {
this.compref.current = ref;
}
render(){
return(
<div>
<Comp onRef={this.onCompRef} />
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id='root' style='width: 100%; height: 100%'>
</div>
const Comp = React.forwardRef((props, ref) => (
<div ref={ref}> some other stuff here </div>
));
class App extends React.Component{
constructor(props){
super(props);
this.compref = React.createRef();
}
componentDidMount(){
const height = this.compref.clientHeight;
console.log("hieght", height);
}
render(){
return(
<div>
<Comp ref={(el) => this.compref = el} />
</div>
)
}
}
ReactDOM.render(<App />, document.querySelector("#app"))
Could you try this way. Hope it helps. Please refer forward refs https://reactjs.org/docs/forwarding-refs.html

How can I append a tab everytime I click in react?

I want to output one tab everytime I click on the + button. I got it to output one. But now I am completely stumped. Here is My main component.
import React, { Component } from 'react';
import './App.css';
import InputTab from './components/tabs/InputTabs/InputTab';
import AddTab from './components/tabs/IncrementTabs/AddTab';
class App extends Component {
state = {
elementlist: ""
}
AddComponentHandler = event =>{
this.setState(
{elementlist: <InputTab/>}
);
}
render() {
return (
<div>
{this.state.elementlist}
<AddTab AddComp = {this.AddComponentHandler.bind(this)}
list = {this.state.elementlist}/>
</div>
);
}
}
export default App;
here is the component button which I want to click to append the input.
import React from 'react';
import './AddTab.css';
const AddTab = props => {
return(
<div onClick = {props.AddComp}
className = "addTab">
+
</div>
);
}
export default AddTab;
And for reference, here is my inputtab which I want to output everytime i click.
i hope i am being clear enough. Thank you in advance for help.
import React from 'react';
import './InputTab.css';
const InputTab = props => {
return(
<div className = "tabContainer">
<input className = "inputTabName"/>
<div className = "weightBox">
<input className = "inputTabWeight"/>%
</div>
</div>
);
}
export default InputTab;
The ideal way would be to store an array of data in state, and then map() over that array to render out multiple InputTabs:
class App extends React.Component {
state = {
elementlist: []
}
AddComponentHandler = event => {
this.setState( prevState => ({
elementlist: prevState.elementlist.concat([Date.now()])
}));
}
render() {
return (
<div>
{this.state.elementlist.map( el => <InputTab /> )}
<AddTab AddComp={this.AddComponentHandler} />
</div>
);
}
}
const AddTab = props => {
return(
<div onClick = {props.AddComp}
className = "addTab">
+
</div>
);
}
const InputTab = props => {
return(
<div className = "tabContainer">
<input className = "inputTabName"/>
<div className = "weightBox">
<input className = "inputTabWeight"/>%
</div>
</div>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
You can just keep a simple integer counter of tabs in App state and increase it when click on AddTab button. Then you can just render one InputTab for each number from 0 to counter stored in state.
class App extends React.Component {
state = {
elementsCounter: 0
}
AddComponentHandler = event => {
this.setState( prevState => ({
elementsCounter: prevState.elementsCounter + 1
}));
}
render() {
return (
<div>
{[...Array(this.state.elementsCounter).keys()].map( index => <InputTab key={index} /> )}
<AddTab AddComp={this.AddComponentHandler} />
</div>
);
}
}
const AddTab = props => {
return(
<div onClick = {props.AddComp}
className = "addTab">
+
</div>
);
}
const InputTab = props => {
return(
<div className = "tabContainer">
<input className = "inputTabName"/>
<div className = "weightBox">
<input className = "inputTabWeight"/>%
</div>
</div>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Categories

Resources