I'm trying to debounce textarea value with react/redux and show the debounced value in div#preview but i'm getting synthetic event warning after first function call.
I have reducer for handling textarea value state which is working as intended, but for simplicity i've wrote local state in this snippet.
If there is a better method besides debounce to avoid react rerendering after each keypress I would love to know. Thanks in advance.
class TextArea extends React.Component {
constructor(props){
super(props);
this.state = {foo: ''}
}
handleInputChange = _.debounce((e) => {
e.persist();
let value = e.target.value;
this.setState({foo: value});
}, 300);
render() {
return (
<div>
<textarea onChange={(e)=>this.handleInputChange(e)} value={this.state.foo}></textarea>
<p id="preview">{this.state.foo}</p>
</div>
);
}
}
ReactDOM.render(
<TextArea />,
document.getElementById("react")
);
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<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>
You get this error because you try to .persist() the event inside the debounce's timeout. When the timeout invokes the callback, the synthetic event was already released. So you'll have to persist the event outside of the debounce.
However, your idea has another problem. Since the textbox is a controlled component, debouncing the updated value, would cause the textbox to render (part of) the text only after the used stopped typing.
To prevent that you need to update the state for the controlled element immediately, and debounce the update for the display state (or the redux action dispatch).
For example:
class TextArea extends React.Component {
constructor(props){
super(props);
this.state = { foo: '', controlled: '' }
}
updateFoo = _.debounce((value) => { // this can also dispatch a redux action
this.setState({foo: value});
}, 300);
handleInputChange = (e) => {
const value = e.target.value;
this.setState({
controlled: value
});
this.updateFoo(value);
}
render() {
return (
<div>
<textarea onChange={ this.handleInputChange }
value={this.state.controlled} />
<p id="preview">{this.state.foo}</p>
</div>
);
}
}
ReactDOM.render(
<TextArea />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<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="react"></div>
The other answer already covered the problem with persisting the event. For the input debouncing aspect, you may want to read my blog post Practical Redux, Part 7: Form Change Handling. In that post, I show a reusable component that can handle debouncing text input updates for the rest of the application, while allowing them to re-render immediately with the current value.
I struggled a lot with this and wasn't able to achieve a satisfying result by myself. Eventually, I used https://github.com/nkbt/react-debounce-input which works perfectly and is much simpler than my previous failed attempts.
/**
* Updates the current node "text" value.
*
* #param event
*/
const onTextInputValueChange = (event: any) => {
const newValue = event.target.value;
patchCurrentNode({
data: {
text: newValue,
},
} as InformationNodeData);
};
<DebounceInput
element={'textarea'}
debounceTimeout={500}
placeholder={'Say something here'}
onChange={onTextInputValueChange}
value={node?.data?.text}
/>
patchCurrentNode writes to my Recoil store.
The DebounceInput component handles an internal state to display the latest value, while only updating the value in the store only once in a while.
Implementation isn't specific to Recoil, and would likely work like a charm using Redux.
Functional component:
import React, { useState, useMemo } from "react";
import debounce from "lodash.debounce";
export default function TextArea() {
const [foo, setFoo] = useState("");
const [controlled, setController] = useState("");
const updateFoo = useMemo(
() =>
debounce((value) => {
// this can also dispatch a redux action
setFoo(value);
}, 1000),
[]
);
const handleInputChange = (e) => {
const value = e.target.value;
setController(value);
updateFoo(value);
};
return (
<div>
<textarea onChange={handleInputChange} value={controlled} />
<p id="preview">{foo}</p>
</div>
);
}
Related
I have a React component (functional) that contains a child component modifying the state of the parent component. I am using the hook useState for this.
After the state change, there is a "Next" button in the parent component that executes a function referencing the updated state. The problem is this next function uses the old state from before the state was modified by the child component.
I can't use useEffect here as the function needs to execute on the click of the "Next" button and not immediately after the state change. I did some digging about JavaScript closures, but none of the answers address my specific case.
Here's the code
const ParentComponent = () => {
const [myState, setMyState] = useState(0);
const handleNext = () => {
console.log(myState); // prints 0 which is the old value
}
return (
<ChildComponent modifyState = {setMyState} />
<Button onClick={handleNext} > Next </Button>
)
}
export default ParentComponent;
BTW there are no errors.
It's a little difficult to understand without your ChildComponent code. setMyState suggests that you need to update the increase the state by one when you click the next button, but you can't do that without also passing in the state itself.
An easier (imo) solution is to pass a handler down to the child component that is called when the next button is clicked. The handler then sets the state.
const {useState} = React;
function ChildComponent({ handleUpdate }) {
function handleClick() {
handleUpdate();
}
return <button onClick={handleClick}>Click</button>
}
function Example() {
const [myState, setMyState] = useState(0);
function handleUpdate() {
setMyState(myState + 1);
}
function handleNext() {
console.log(myState);
}
return (
<div>
<ChildComponent handleUpdate={handleUpdate} />
<button onClick={handleNext}>Next </button>
</div>
)
}
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
try to modify like this
<ChildComponent modifyState={(value) => setMyState(value)} />
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 am following along with a video tutorial on using React. The presenter is currently detailing how to add a toggle button to a UI. They said to give it a go first before seeing how they do it, so I implemented it myself. My implementation was a little different to theirs, just the handler was different; but it does seem to work.
Can anyone with more experience using React tell me, is my toggleSideDrawerHandler wrong in some way? Or is it a valid shorter way of setting the state that depends on a previous state?
My implementation:
//Layout.js
class Layout extends Component {
state = {
showSideDrawer: false
};
toggleSideDrawerHandler = prevState => {
let newState = !prevState.showSideDrawer;
this.setState({ showSideDrawer: newState });
};
closeSideDrawerHandler = () => {
this.setState({ showSideDrawer: false });
};
render() {
return (
<Fragment>
<Toolbar drawerToggleClicked={this.toggleSideDrawerHandler} />
<SideDrawer
open={this.state.showSideDrawer}
close={this.closeSideDrawerHandler}
/>
<main className={styles.Content}>{this.props.children}</main>
</Fragment>
);
}
}
//Toolbar.js
const toolbar = props => (
<header className={styles.Toolbar}>
<DrawerToggle clicked={props.drawerToggleClicked} />
<div className={styles.Logo}>
<Logo />
</div>
<nav className={styles.DesktopOnly}>
<NavItems />
</nav>
</header>
);
Tutorial implementation:
toggleSideDrawerHandler = () => {
this.setState(prevState => {
return { showSideDrawer: !prevState.showSideDrawer };
});
};
Your solution works, but I guess in the part, where you call the toggleSideDrawerHandler you probably call it like
() => this.toggleSideDrawerHandler(this.state)
right?
If not, can you please paste the rest of your code (especially the calling part) to see where you get the prevState from?
This works, because you pass the old state to the method.
I would personally prefer the tutorials implementation, because it takes care of dependencies and the "user" (the dev using it) doesn't need to know anything about the expected data.
With the second implementation all you need to do is call the function and not think about getting and passing the old state to it.
Update after adding the rest of the code:
I think the reason, why it works is because the default value for your parameter is the one passed by the event by default, which is an event object.
If you use prevState.showSideDrawer you are calling an unknown element on this event object, that will be null.
Now if you use !prevState.showSideDrawer, you are actually defining it as !null (inverted null/false), which will be true.
This is why it probably works.
Maybe try to toggle your code twice, by showing and hiding it again.
Showing it will probably work, but hiding it again will not.
This is why the other code is correct.
You should stick to the tutorial implementation. There is no point in passing component state to the children and then from them back to the parents. Your state should be only in one place (in this case in Layout).
Child components should be only given access to the information they need which in this case is just showSideDrawer.
You are using this:
toggleSideDrawerHandler = prevState => {
let newState = !prevState.showSideDrawer;
this.setState({ showSideDrawer: newState });
};
This is a conventional way to update state in react, where we are defining the function and updating state inside. Though you are using term prevState but it doesn't holds any value of components states. When you call toggleSideDrawerHandler method you have to pass value and prevState will hold that value. The other case as tutorial is using:
toggleSideDrawerHandler = () => {
this.setState(prevState => {
return { showSideDrawer: !prevState.showSideDrawer };
});
};
This is called functional setStae way of updating state. In this function is used in setState methods first argument. So prevState will have a value equal to all the states in the component.Check the example below to understand the difference between two:
// Example stateless functional component
const SFC = props => (
<div>{props.label}</div>
);
// Example class component
class Thingy extends React.Component {
constructor() {
super();
this.state = {
temp: [],
};
}
componentDidMount(){
this.setState({temp: this.state.temp.concat('a')})
this.setState({temp: this.state.temp.concat('b')})
this.setState({temp: this.state.temp.concat('c')})
this.setState({temp: this.state.temp.concat('d')})
this.setState(prevState => ({temp: prevState.temp.concat('e')}))
this.setState(prevState => ({temp: prevState.temp.concat('f')}))
this.setState(prevState => ({temp: prevState.temp.concat('g')}))
}
render() {
const {title} = this.props;
const {temp} = this.state;
return (
<div>
<div>{title}</div>
<SFC label="I'm the SFC inside the Thingy" />
{ temp.map(value => ( <div>Concating {value}</div> )) }
</div>
);
}
}
// Render it
ReactDOM.render(
<Thingy title="I'm the thingy" />,
document.getElementById("react")
);
<div id="react"></div>
<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>
So depending on requirement you will use one of the two ways to update the state.
I'm lost! i have a really simple component but its going nuts.
i got an input with a change function and a button with onclick.
for some reason when i set the state in the onclick i get an error
Maximum update depth exceeded. This can happen when a component
repeatedly calls setState inside componentWillUpdate or
componentDidUpdate. React limits the number of nested updates to
prevent infinite loops.
Huh? im not doing any looping whats going on with this error then?
so i comment the line where i set the state and added a console log to see the value. now i discovered a true magic, when i run the code its showing in the console the value from my state without me clicking anything (the only place i use console.log is in the click event).
When i do click the button though, nothing really happens.
but listen to this, when i type inside my input, again the console logs the current value in the state!
why the console.log() call from the wrong method? is this a bug?
Here is my code:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
myValue: "Hello World"
};
this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleClick = (value) => {
console.log(value);
//this.setState({ myValue: value }); // this will error about infinite loop
};
handleChange = (e) => {
this.setState({ myValue: e.target.value });
}
render() {
return (
<div>
<div>
<input value={this.state.myValue} onChange={this.handleChange} />
</div>
<button onClick={this.handleClick(this.state.myValue)}>set</button>
<h3>{this.state.myValue}</h3>
</div>
);
}
}
render(<App />, document.getElementById("root"));
The problem is that you are passing to the onClick event a function invocation, and not a function reference.
im not doing any looping whats going on with this error then?
In the first initial render call when you pass the handler function, you actually invoke it. This function is updating the state which triggers another render call which you pass again a function invocation that will do another update to the state which will trigger another render call and so on.
Hence an infinite loop.
why the console.log() call from the wrong method? is this a bug?
As i mentioned above, you are passing a function invocation, hence on each render you call console.log(value) instead of listening to the onClick event, and when you change the input (which works as expected) you rerender again and call console.log(value) once more. So it's not the handleChange that calling the console.log, it is render function that is calling handleClick which invoke console.log.
No bugs or magics here, it may not be the desired behavior but it is the expected behavior according to your code and logic.
You got 2 main options here.
Easy and fast fix: Pass a function reference that returns a function with your logic there. I'm using currying, so with ES6
arrow functions is just as easy as adding another parameter and an
arrow:
handleClick = (value) => (e) => {
console.log(value);
this.setState({ myValue: value });
};
This approach does have it's advantages, like fast implementation
and easy to understand and read the code but it may cause
performance issues.
You see, you are returning a new instance of a
function on each render and react will treat it as a new prop this
can interrupt the diffing algorithm of react.
A working example:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
myValue: "Hello World"
};
this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleClick = (value) => (e) => {
console.log(value);
this.setState({ myValue: value });
};
handleChange = (e) => {
this.setState({ myValue: e.target.value });
}
render() {
return (
<div>
<div>
<input value={this.state.myValue} onChange={this.handleChange} />
</div>
<button onClick={this.handleClick(this.state.myValue)}>set</button>
<h3>{this.state.myValue}</h3>
</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>
A better approach that is considered as best practice, is to compose a
component that accepts an event and a parameter as separate props
and will send this value upwards when the event is triggered.
For example MyButton.js:
class MyButton extends React.Component{
handleClick = e =>{
const {onClick, clickValue} = this.props;
this.props.onClick(clickValue);
}
render(){
const {children} = this.props;
return <button onClick={this.handleClick}>{children}</button>
}
}
A working example:
class MyButton extends React.Component{
handleClick = e =>{
const {onClick, clickValue} = this.props;
this.props.onClick(clickValue);
}
render(){
const {children} = this.props;
return <button onClick={this.handleClick}>{children}</button>
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
myValue: "Hello World"
};
this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleClick = (value) => {
console.log(value);
this.setState({ myValue: value });
};
handleChange = (e) => {
this.setState({ myValue: e.target.value });
}
render() {
const {myValue} = this.state;
return (
<div>
<div>
<input value={myValue} onChange={this.handleChange} />
</div>
<MyButton clickValue={myValue} onClick={this.handleClick}>set</MyButton>
<h3>{myValue}</h3>
</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>
By the way, you don't need to bind the handlers to the class when you're using arrow functions. it binds the this automatically.
The body of ES6 arrow functions share the same lexical this as the
code that surrounds them, which gets us the desired result because of
the way that ES7 property initializers are scoped
As mentioned you are passing a function invocation than a function reference, so the simple and recommended change that one needs to do is to give a reference to a thick arrow function (or anonymous function):
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
myValue: "Hello World"
};
}
handleClick = (value) => {
console.log(value);
// No longer gives error
this.setState({ myValue: value });
};
handleChange = (e) => {
this.setState({ myValue: e.target.value });
}
render() {
return (
<div>
<div>
<input value={this.state.myValue} onChange={ () => {this.handleChange();} } />
</div>
<button onClick={ () => {this.handleClick(this.state.myValue);} }>set</button>
<h3>{this.state.myValue}</h3>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
Now when the <App /> component is rendered it no longer has the function invocation as the value in onClick or onChange event listeners, instead, it has a reference to the anonymous function which in turn calls your handleClick() and handleChange() functions. Hope it helps! :)
I have simple component
class ContentEditable extends React.Component {
constructor(props) {
super(props);
this.handleInput = this.handleInput.bind(this);
}
handleInput(event) {
let html = event.target.innerHTML;
if (this.props.onChange && html !== this.lastHtml) {
this.props.onChange({ target: { value: html, name: this.props.name } });
this.lastHtml = html;
}
}
render() {
return (
<span
contentEditable="true"
onInput={this.handleInput}
className={"auto " + this.props.className}
dangerouslySetInnerHTML={{ __html: this.props.value }}
/>
);
}
}
export default ContentEditable;
<ContentEditable
value={this.state.name}
onChange={e => {
this.setState({ name: e.target.value });
}}
/>;
The component works but the cursor position never changes, it is always on first position instead after the rendered text.
I tested examples form this forum but it doesn't work for me.
I use React 15.6.1 and test it on chrome (Os X).
Any hint how I can solve this problem?
The solution with useRef will be something look like below.
Here the useRef will keep the default value / initial value apart from the component rendering cycles, so it will retain the original value without being affected by other kinds of operations we do in the react component.
This component does two things
This will emit the user input to the parent component with an onChange method
Takes a default value from parent component as prop named value and renders the value in the custom input box (that was created using contentEditable)
I have added a code sandbox, link here, use this to see how this works!
The code sandbox example contains two components
one is ContentEditableWithRef which solves the problem with useRef , which is an uncontrolled component and
the other component is ContentEditable which uses useState to solve the same problem.
I also had same problem. Just fixed it with ref. Just assign textContent of event.target to ref.
const textareaEl = useRef<HTMLDivElement>(null);
const handleChange = (e: React.ChangeEvent<HTMLDivElement>) => {
textareaEl.current.textContent = e.target.textContent;
onChange(e); // If you have change event for form/state
};
/** If you're passing value from state,
you can mutate it each change for not losing cursor position.
*/
useEffect(() => {
if (value) {
textareaEl.current.textContent = value;
}
}, [value]);
return (
<div
id="textarea-element"
ref={textareaEl}
contentEditable={true}
suppressContentEditableWarning={true}
onChange={handleChange}
/>
)