What is the recommended/cleanest way to dynamically change an event's binding in react?
For example, if I initially have a button like this
<button type="button" onClick={this.handleFirstClick}>
Then in the handleFirstClick method
handleFirstClick() {
//do other stuff
//rebind the button, so that next time it's clicked, handleSecondClick() would be called
}
In case it's not entirely clear what I mean, here's what I'd like to do, using jQuery instead of React
$('#myButton').on('click', handleFirstClick);
function handleFirstClick() {
//other stuff
$('#myButton').off('click');
$('#myButton').on('click', handleSecondClick);
}
Solution 1: React State and Ternary Expressions
In order to change the event's binding, you'll need to have a if-else within the render method. Create some state for the component to handle whether the button has been clicked yet. After the first click, set the state so that in the future the second function will be run. You can include a basic ternary expression to check the state in your render().
class FancyButton extends React.Component {
constructor() {
super()
this.state = {
clicked: false
}
//Bindings for use in render()
this.onFirstClick = this.onFirstClick.bind(this)
this.onSecondClick = this.onSecondClick.bind(this)
}
onFirstClick() {
console.log("First click")
this.setState({
clicked: true
})
}
onSecondClick() {
console.log("Another click")
}
render() {
return ( <
button onClick = {
this.state.clicked ? this.onSecondClick : this.onFirstClick
} > Click < /button>
)
}
}
ReactDOM.render( < FancyButton / > , 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>
Solution 2: Persisting State in an Object Property
In a more general sense, you may not need to change the event handler in your render method. If you just call one onClick handler and toggle an object property, you can skip rerendering the react component after each click (due to skipping the call to this.setState).
class FancyButton extends React.Component {
constructor() {
super()
this.clicked = false
//Bindings for use in render()
this.onClick = this.onClick.bind(this)
}
onClick() {
if (!this.clicked) {
this.onFirstClick()
} else {
this.onSecondClick()
}
this.clicked = true
}
onFirstClick() {
console.log("First click")
}
onSecondClick() {
console.log("Another click")
}
render() {
return ( <
button onClick = {
this.onClick
} > Click < /button>
)
}
}
ReactDOM.render( < FancyButton / > , 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>
I would personally recommend the second solution, as it is more efficient, but you can decide on your own which fits your situation the best.
Just keep a counter of how many times button is clicked, and then use it to assign a handler. See this snippet,
class App extends React.Component{
constructor(props){
super(props)
this.state={
clicked: 0
}
}
firstClick(){
console.log("first click")
this.setState({clicked: 1})
}
afterClick(){
console.log("more clicks")
}
render(){
return(
<div><button onClick={this.state.clicked === 0 ? this.firstClick.bind(this) : this.afterClick.bind(this)}>Click me</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById("app"))
<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="app"></div>
You could use a closure.
const createClickHandler = () => {
const firstClickHandler = () => {}
const secondClickHandler = () => {}
let clicked = false
return () => {
if (clicked) return secondClickHandler()
clicked = true
firstClickHandler()
}
}
Create a function that watches the state and determines which function should be ran.
handleClick() {
if (this.state.clickNumber === 1) {
this.handleFirstClick()
} elseif (this.state.clickNumber === 2) {
this.handleSecondClick()
}
}
handleFirstClick() {
//do stuff
this.setState({clickNumber: 2})
}
handleSecondClick(){
//do other stuff
// maybe increment click number again this.setState({clickNumber: 3})
}
In React, you'd be best off using state to manage these events. You can hold the state and actions in the individual button components, or in the parent component that renders the button components.
In set this up the button components utilize a binary state that toggles between click one and 2 functions with a Boolean value. This could also easily change to function two and stay there by modifying the buttons's handleClick setState call, or keep iterating to additional functions - or even call the same function with an updated input each click (depending on your use case).
In this example, I decided to show how this would work in a stripped down React App. In this example you'll notice that I keep the state in each button. It's doubtful that another component will need to track the click count, so this follows the react principal of pushing the state down to the lowest possible component. In this case, I allowed the parent component to hold the functions that would act on the button state. I didn't see a necessity in these functions and their overhead also being duplicated with each button component, but the button components certainly could have called the functions themselves as well.
I added two buttons to show how they will individually maintain their state. Forgive the comment block formatting, it's not displaying well outside of the snippet window.
/**
* #desc Sub-component that renders a button
* #returns {HTML} Button
*/
class BoundButton extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = ({
//toggled in this example to keep click state
click2: false,
});
}
handleClick(e) {
//calls parent method with the clicked button element and click state
this.props.click(e.nativeEvent.toElement, this.state.click2);
//toggles click state
this.setState({ click2: !this.state.click2 });
}
render() {
return (
<button
id = {this.props.id}
name = {this.props.name}
className = {this.props.className}
onClick = {this.handleClick} >
Click Here {this.state.click2 ? '2' : '1'}!
</button>
);
}
}
/**
* #desc Component that creates and binds button sub-components
* #returns {HTML} Bound buttons
*/
class BindExample extends React.Component {
constructor(props) {
super(props);
this.handleButtonClick = this.handleButtonClick.bind(this);
this.clickAction1 = this.clickAction1.bind(this);
this.clickAction2 = this.clickAction2.bind(this);
this.state = ({
//state vars
});
}
clickAction1(el) {
console.log('Action 1 on el: ', el);
}
clickAction2(el) {
console.log('Action 2 on el: ', el);
}
handleButtonClick(el, click2) {
console.log('click!');
if (click2) this.clickAction2(el);
else this.clickAction1(el);
}
render() {
return (
<div>
<BoundButton
id='ex-btn1'
name='bound-button-1'
className='bound-button'
click={this.handleButtonClick} />
<BoundButton
id='ex-btn2'
name='bound-button-2'
className='bound-button'
click={this.handleButtonClick} />
</div>
);
}
}
/**
* #desc React Class renders full page. Would have more components in a
* real app.
* #returns {HTML} full app
*/
class App extends React.Component {
render() {
return (
<div className='pg'>
<BindExample />
</div>
);
}
}
/**
* Render App to DOM
*/
/**
* #desc ReactDOM renders app to HTML root node
* #returns {DOM} full page
*/
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">
<!-- This div's content will be managed by React. -->
</div>
Related
I'm trying to implement the strategy design pattern to dynamically change how I handle mouse events in a react component.
My component:
export default class PathfindingVisualizer extends React.Component {
constructor(props) {
super(props)
this.state = {
grid: [],
mouseLeftDown: false,
};
const mouseStrat2 = null; // Object I will change that has different functions for handling events
}
componentDidMount() {
this.resetGrid();
this.mouseStrat2 = new StartEndStrat();
}
render() {
//buttons that change the object i want handling mouse events
<button onClick={() => this.mouseStrat2 = new StartEndStrat(this)}>startendstrat</button>
<button onClick={() => this.mouseStrat2 = new WallStrat(this)}>wallstrat</button>
}
}
I want my mouse strats that will access change the component with differing methods to handle mouse events
export class StartEndStrat {
handleMouseDown(row, col) {
// I want to access component state and call functions of the component
this.setState({ mouseLeftDown: true });
PathfindingVisualizer.resetGrid();
}
//other functions to change other stuff
handleMouseEnter(row, col) {
console.log('start end strat');
}
}
export class WallStrat {
handleMouseDown(row, col) {
this.setState({ mouseLeftDown: true });
}
handleMouseEnter(row, col) {
console.log('wallstrat');
}
}
You can try use Refs to do this.
refOfComponent.setState({ ... })
But I would rather recommend you to avoid such constructions as this may add complexity to your codebase.
Solution I found was to use a ref callback to make the DOM element a global variable.
<MyComponent ref={(MyComponent) => window.MyComponent = MyComponent})/>
Then you can access MyComponent with window.MyComponent, functions with window.MyComponent.method() or state variables with window.MyComponent.state.MyVar
My App.js:
function App() {
return (
<div className="App">
<PathfindingVisualizer ref={(PathfindingVisualizer) => {window.PathfindingVisualizer = PathfindingVisualizer}} />
</div>
);
}
Other.js:
handleMouseDown() {
window.PathfindingVisualizer.setState({mouseLeftDown: true});
}
I have created a toggle button which will show and hide the value variable. But I can't see the changes on the screen, Although console shows the value of 'show' is changing every time I click the 'Change Me' button.
import React from 'react'
export default function State(){
let val = 4;
let show = true;
function changeMe(){
show = !show;
console.log(show);
}
return(
<div>
{show ? <span>{val}</span> : null}
<br></br>
<button onClick = {changeMe}>Change Me</button>
</div>
)
}
What I understand about functional component is that they are stateless component and we can only present the state/props of them. Is this is the reason I can't create toggle button without hooks to render the changes. Please correct me If I am wrong or add on your answer/thought to clear my concept.
PS: I am new to React and learning concepts of React. So, it might be a silly question.
What I understand about functional component is that they are stateless component and we can only present the state/props of them. Is this is the reason I can't create toggle button without hooks to render the changes.
Yes. If you don't use hooks, function components are stateless. To have a stateful component, either:
Use hooks, or
Use a class component instead
Note that function components can have props without using hooks (and usually do). Props are basically state the parent element manages. The parent can even pass your function component a function it calls in response to an event that may make the parent component change the prop the function component uses (using state in the parent, via hooks or a class component). But props are distinct from state.
For instance, here's a function component with a ticks property updated by the parent:
const {Component, useState, useEffect} = React;
function Child({ticks}) {
return <div>{ticks}</div>;
}
class ClassParent extends Component {
constructor(props) {
super(props);
this.state = {
ticks: 0
};
this.onTick = this.onTick.bind(this);
}
componentDidMount() {
this.timer = setInterval(this.onTick, this.props.interval || 1000);
}
componentWillUnmount() {
clearInterval(this.timer);
}
onTick() {
this.setState(({ticks}) => {
++ticks;
return {ticks};
});
}
render() {
return <Child ticks={this.state.ticks} />;
}
}
function FunctionParent({interval = 1000}) {
const [ticks, setTicks] = useState(0);
useEffect(() => {
const timer = setInterval(() =>{
setTicks(t => t + 1);
}, interval);
}, []);
return <Child ticks={ticks} />;
}
function Example() {
return <div>
<ClassParent interval={800} />
<FunctionParent interval={400} />
</div>;
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
i want to show my functional component in class base component but it is not working. i made simpletable component which is function based and it is showing only table with some values but i want to show it when i clicked on Show user button.
import React ,{Component} from 'react';
import Button from '#material-ui/core/Button';
import SimpleTable from "../userList/result/result";
class ShowUser extends Component{
constructor(props) {
super(props);
this.userList = this.userList.bind(this);
}
userList = () => {
//console.log('You just clicked a recipe name.');
<SimpleTable/>
}
render() {
return (
<div>
<Button variant="contained" color="primary" onClick={this.userList} >
Show User List
</Button>
</div>
);
}
}
export default ShowUser;
Why your code is not working
SimpleTable has to be rendered, so you need to place it inside the render method. Anything that needs to be rendered inside your component has to be placed there
On Click can just contain SimpleTable, it should be used to change the value of the state variable that controls if or not your component will be shown. How do you expect this to work, you are not rendering the table.
Below is how your code should look like to accomplish what you want :
import React ,{Component} from 'react';
import Button from '#material-ui/core/Button';
import SimpleTable from "../userList/result/result";
class ShowUser extends Component{
constructor(props) {
super(props);
this.state = { showUserList : false }
this.userList = this.userList.bind(this);
}
showUserList = () => {
this.setState({ showUserList : true });
}
render() {
return (
<div>
<Button variant="contained" color="primary" onClick={this.showUserList} >
Show User List
</Button>
{this.state.showUserList ? <SimpleTable/> : null}
</div>
);
}
}
export default ShowUser;
You can also add a hideUserList method for some other click.
Or even better a toggleUserList
this.setState({ showUserList : !this.state.showUserList});
If you're referring to the method userList then it appears that you're assuming there is an implicit return value. Because you're using curly braces you need to explicitly return from the function meaning:
const explicitReturn = () => { 134 };
explicitReturn(); <-- returns undefined
const implicitReturn = () => (134);
implicitReturn(); <-- returns 134
The problem lies with how you are trying to display the SimpleTable component. You are using it inside the userList function, but this is incorrect. Only use React elements inside the render method.
What you can do instead is use a state, to toggle the display of the component. Like this:
const SimpleTable = () => (
<p>SimpleTable</p>
);
class ShowUser extends React.Component {
constructor(props) {
super(props);
this.state = {showSimpleTable: false};
this.toggle= this.toggle.bind(this);
}
toggle = () => {
this.setState(prev => ({showSimpleTable: !prev.showSimpleTable}));
}
render() {
return (
<div>
<button variant = "contained" color = "primary" onClick={this.toggle}>
Show User List
</button>
{this.state.showSimpleTable && <SimpleTable />}
</div>
);
}
}
ReactDOM.render(<ShowUser />, document.getElementById("app"));
<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="app"></div>
The functionality you are looking for is called Conditional Rendering. The onClick prop function is an event handler and events in react may be used to change the state of a component. That state then may be used to render the components. In normal vanilla javascript or jQuery we call a function and modify the actual DOM to manipulate the UI. But React works with a virtual DOM. You can achieve the functionality you are looking for as follows:
class ShowUser extends Component {
constructor(props) {
super(props)
// This state will control whether the simple table renders or not
this.state = {
showTable: false
}
this.userList.bind(this)
}
// Now when this function is called it will set the state showTable to true
// Setting the state in react re-renders the component (calls the render method again)
userList() {
this.setState({ showTable: true })
}
render() {
const { showTable } = this.state
return (
<div>
<Button variant="contained" color="primary" onClick={this.userList}>
Show User List
</Button>
{/* if showTable is true then <SimpleTable /> is rendered if falls nothing is rendered */}
{showTable && <SimpleTable />}
</div>
)
}
}
I've got a may confusing question because it does not fit standard-behaviour how react and the virtual dom works but i would like to know the answer anyway.
Imagine i've got a simple react-component which is called "Container".
The Container-component has a "div" inside of the render-method which contains another component called "ChildContainer". The "div" which surrounds the "ChildContainer" has the id "wrappingDiv".
Example:
render() {
<Container>
<div id="wrappingDiv">
<ChildContainer/>
</div>
</Container
}
How can i destroy the "ChildContainer"-component-instance and create a completly new one. Which mean the "ComponentWillUnmount" of the old instance is called and the "ComponentDidMount" of the new component is called.
I don't want the old component to update by changing the state or props.
I need this behaviour, because an external library from our partner-company got a libary which change the dom-items and in React i'll get a "Node not found" exception when i Update the component.
If you give the component a key, and change that key when re-rendering, the old component instance will unmount and the new one will mount:
render() {
++this.childKey;
return <Container>
<div id="wrappingDiv">
<ChildContainer key={this.childKey}/>
</div>
</Container>;
}
The child will have a new key each time, so React will assume it's part of a list and throw away the old one, creating the new one. Any state change in your component that causes it to re-render will force that unmount-and-recreated behavior on the child.
Live Example:
class Container extends React.Component {
render() {
return <div>{this.props.children}</div>;
}
}
class ChildContainer extends React.Component {
render() {
return <div>The child container</div>;
}
componentDidMount() {
console.log("componentDidMount");
}
componentWillUnmount() {
console.log("componentWillUnmount");
}
}
class Example extends React.Component {
constructor(...args) {
super(...args);
this.childKey = 0;
this.state = {
something: true
};
}
componentDidMount() {
let timer = setInterval(() => {
this.setState(({something}) => ({something: !something}));
}, 1000);
setTimeout(() => {
clearInterval(timer);
timer = 0;
}, 10000);
}
render() {
++this.childKey;
return <Container>
{this.state.something}
<div id="wrappingDiv">
<ChildContainer key={this.childKey}/>
</div>
</Container>;
}
}
ReactDOM.render(
<Example />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.2/umd/react-dom.production.min.js"></script>
Having said that, there may well be a better answer to your underlying issue with the plugin. But the above addresses the question actually asked... :-)
Using hooks, first create a state variable to hold the key:
const [childKey, setChildKey] = useState(1);
Then use the useEffect hook to update the key on render:
useEffect(() => {
setChildKey(prev => prev + 1);
});
Note: you probably want something in the array parameter in useEffect to only update the key if a certain state changes
React says we should not use refs where possible and I noticed that you can't use shallow rendering testing with refs so I have tried to remove refs where possible. I have a child component like this:
class Child extends React.Component {
play = () => {
//play the media
},
pause = () => {
//pause the media
},
setMedia = (newMedia) => {
//set the new media
}
}
I then have a parent component that needs to call these methods. For the setMedia I can just use props with the componentWillReceiveProps and call setMedia when the new props come in to the child.
With the play and pause functions I cannot do this.
Ben Alpert replied to this post and said:
In general, data should be passed down the tree via props. There are a few exceptions to this (such as calling .focus() or triggering a one-time animation that doesn't really "change" the state) but any time you're exposing a method called "set", props are usually a better choice. Try to make it so that the inner input component worries about its size and appearance so that none of its ancestors do.
Which is the best way to call a child function?
play() and pause() methods can be called from refs as they do not change the state just like focus() and use props for the other functions that have arguments.
Call the child functions by passing the method name in although this just seems hacky and a lot more complex:
class Child extends React.Component {
play = () => {
//play the media
},
pause = () => {
//pause the media
},
setMedia = (newMedia) => {
//set the new media
},
_callFunctions = (functions) => {
if (!functions.length) {
return;
}
//call each new function
functions.forEach((func) => this[func]());
//Empty the functions as they have been called
this.props.updateFunctions({functions: []});
}
componentWillReceiveProps(nextProps) {
this._callFunctions(nextProps.functions);
}
}
class Parent extends React.Component {
updateFunctions = (newFunctions) => this.setState({functions: newFunctions});
differentPlayMethod = () => {
//...Do other stuff
this.updateFunctions("play");
}
render() {
return (
<Child updateFunctions={this.updateFunctions}/>
);
}
}
Do this in the child component: this.props.updateFunctions({play: this.play});
The problem with this is that we are exposing(copying) a method to another component that shouldn't really know about it...
Which is the best way to do this?
I am using method number 2 at the moment and I don't really like it.
To override child functions I have also done something similar to above. Should I just use refs instead?
Rather than call child functions, try to pass data and functions down from the parent. Alongside your component, you can export a wrapper or higher order function that provides the necessary state / functions.
let withMedia = Wrapped => {
return class extends React.Component {
state = { playing: false }
play() { ... }
render() {
return (
<Wrapped
{...this.state}
{...this.props}
play={this.play}
/>
)
}
}
}
Then in your parent component:
import { Media, withMedia } from 'your-library'
let Parent = props =>
<div>
<button onClick={props.play}>Play</button>
<Media playing={props.playing} />
</div>
export default withMedia(Parent)
Keep the state as localized as you can, but don't spread it over multiple components. If you need the information whether it is currently playing in both the parent and the child, keep the state in the parent.
This leaves you with a much cleaner state tree and props:
class Child extends React.Component {
render() {
return (
<div>
<button onClick={this.props.togglePlay}>Child: Play/Pause</button>
<p>Playing: {this.props.playing ? 'Yes' : 'No'}</p>
</div>
);
}
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.togglePlay = this.togglePlay.bind(this);
this.state = {
playing: false
};
}
togglePlay() {
this.setState({
playing: !this.state.playing
});
}
render() {
return (
<div>
<button onClick={this.togglePlay}>Parent: Play/Pause</button>
<Child togglePlay={this.togglePlay} playing={this.state.playing} />
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById('app')
);
<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='app'></div>