react-codemirror beforeChange event - javascript

I am using react-codemirror node module as follows:
<CodeMirror
className={className}
value={this.state.code}
onBeforeChange={this.onBeforeChange}
onChange={this.onChange}
options={options}
/>
The change event works fine, but I can't seem to hook up with the beforeChange event. Anyone know what I am doing wrong?
I have declared handlers in my class as follows:
onBeforeChange(change) {
console.log('calling beforeChange');
}
onChange(newCode) {
this.setState({
code: newCode
});
}

Author of react-codemirror2 here. I stumbled upon your question and wanted to follow up with a detailed answer as there are some breaking changes in 3.x. The component now ships with an UnControlled and Controlled variant based on different use cases. I see you are calling setState within the onBeforeChange callback. In your case, I'd suggest leveraging the controlled component as such...
import {Controlled as CodeMirror} from 'react-codemirror2'
<CodeMirror
value={this.state.value}
options={options}
onBeforeChange={(editor, data, value) => {
this.setState({value}); // must be managed here
}}
onChange={(editor, metadata, value) => {
// final value, no need to setState here
}}
/>
With the controlled variant, managing state is required on the value prop to see any changes.
Additionally, the UnControlled component also has an onBeforeChange callback as well, yet with different behavior as such...
import {UnControlled as CodeMirror} from 'react-codemirror2'
<CodeMirror
value={value}
options={options}
onBeforeChange={(editor, data, value, next) => {
// hook to do whatever
next();
}}
onChange={(editor, metadata, value) => {
}}
/>
Here however, onChange will be deferred until next is invoked if onBeforeChange is specified. If not, onChange will fire regardless. Important to note, though, with the UnControlled variant, the editor will always react to input changes - the difference will simply be if onChange is called or not.
These changes were inspired due to the needs of the community and I encourage you to open an issue should anything not be working as you expect.

It turns out react-codemirror does NOT expose the beforeChange event.
However, react-codemirror2 does. A switch of library fixed this for me!
My final callback code:
onBeforeChange(cm, change, callOnChange) {
const options = Object.assign({}, DEFAULT_OPTIONS, this.props.options)
if (options.singleLine) {
var after = change.text.join('').replace(/\n/g, '')
change.update(change.from, change.to, [after])
}
callOnChange()
}
onChange(cm, change, code) {
this.setState({ code })
if (this.props.onChange) {
this.props.onChange(code)
}
}

Related

Improve textfield render performance

I'm using React 17 and MUI 5. I found a problem in my project when I have a component with a huge form with multiple <Textfields>.
handleChange = (event) => {
this.setState({ value: event.target.value });
};
...
<TextField
value={this.state.value}
onChange={this.handleChange}
label="Textfield"
/>
Everytime I type a char in my textfield the setState is triggered and my render() function is called everytime. So on my huge form I have a lag when I type.
So I tried the solution to use onBlur event:
<TextField
onBlur={this.handleChange}
label="Textfield"
/>
It works well if I'm on "Add" mode because the setState is triggered only when I leave the textfield. But I have a problem when I'm on "Edit" mode. When I'm on "Edit" mode I get the current value and I put it inside my textfield. And with onBlur event I can't use the textfield value prop because there is a conflict and I can't write anymore in the textfield.
So I found another solution. It's to use ref. But I have the same problem like with onBlur event. I can't use value prop. So I did this :
<TextField
inputRef={(el) => {
this.textRef = el;
if (el && this.state.mode === "edit") {
this.textRef.value = this.state.node.value;
}
}}
label="Textfield"
/>
So when I'm on edit mode I init the textfield with the current value (this.state.node is a node of my TreeView and each node have a edit button). This code works. But I have the feeling it's not the good way to do that. This if mode == edit is weird.
Do you have a better way ?
You could examine uncontrolled inputs, it could fit in your case
An uncontrolled form doesn’t have state, so every time you type, your component doesn’t re-render, increasing overall performance.
There is an explanation: https://hackernoon.com/you-might-not-need-controlled-components-fy1g360o
I doubt the "huge form" is actually the problem. React only updates the DOM when there are changes, so even though the component is rerendered on every character typed, only the one input field that changes should get flushed to the DOM.
Check if you're doing expensive calculations in your render() function. In that case, memoize these.
Another take on fixing this would be to use uncontrolled components as mentioned in the other answer.
Also consider if you can split up your component into multiple, smaller components so the problem is minimized.
The following "benchmark" code is written with functional components but is using MUI v5 and React 17 and runs with 100 TextFields without any noticable lag when entering text, at least on my (relatively beefy) desktop PC.
Using <input> from HTML even allowed me to get this working with 1000 elements.
const inputFieldKeys = Array.from({
length: 100
}).map((e, i) => "field_" + i.toString());
const App = (props) => {
const [inputState, setInputState] = React.useState({});
// this runs fine even without the `useCallback` optimization, at least for me
const handleChange = React.useCallback((e) => {
setInputState(oldState => ({ ...oldState,
[e.target.name]: e.target.value
}));
}, [setInputState]);
return ( <div>
{inputFieldKeys.map(key=>(
<MaterialUI.TextField name={key} key={key} value={inputState[key] || ""} onChange={handleChange}
label={key}/>
))}
</div>
);
};
ReactDOM.render( < App / > , document.getElementById("root"));
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/#mui/material#5/umd/material-ui.production.min.js"></script>
<div id="root"></div>
Note for MUI performance:
MUI v5 uses the sx prop for inline styling. This can lead to bad performance in cases with many components when used without care. mui docs

DOM created dynamically not showing up in Component

I have a function which basically generates dynamic dom as below
const arrMarkup = [];
const getMarkup = () => {
{
if(true){
arrMarkup.push(<Accordion expanded={expanded === cust.name} onChange={handleChange(cust.name)}>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1bh-content"
id="panel1bh-header"
>
<Typography className={salesEvent.name && classes[salesEvent.name]}></Typography>
</AccordionSummary>
</Accordion>
)
})
})
}
return <div>{arrMarkup}</div> ;
}
Now, i am trying to execute this function is on useEffect as below
useEffect(() => {
getMarkup();
}, [cust]);
and trying to add in return of JSX as
return (
<div>
{arrMarkup}
</div>
)
but can not see the markup added, however i can see its added in array arrMarkup. What's wrong?
React only re-renders your component when its state or props change. As far as one can tell from your question, arrMarkup isn't either a state member or a prop. (If it were, directly modifying it would be against the React rules, because you must not directly modify state or props.)
It's hard to tell you what to do without more information, but you probably want arrMarkup to be a state member, for instance (in a functional component using hooks):
const [arrMarkup, setArrMarkup] = useState([]);
and then to update it appropriately, for instance:
setArrMarkup(current => [...current, <NewStuff>goes here</NewStuff>]);
Note that I used the callback version of the state setter. That's important when you're updating state based on existing state, since state updates may be asynchronous and can be batched (so the state information you already have can be stale).
FWIW, a couple of other observations:
It's unusual to have the useEffect dependency be cust (a single object as far as I can tell) and have triggering the effect add an entry to an array that has previous entries for previous values of cust which (apparently) you're no longer storing in the component's state anywhere. That just feels very off, without more context.
You haven't shown the definition of handleChange, but onChange={handleChange(cust.name)} looks like it's probably incorrect. It calls handleChange, passing in cust.name, and the uses its return value os the change handler. Did you mean onChange={() => handleChange(cust.name)}, so that handleChange is called when the event occurs?

Using React components inside KnockoutJS

We have a big web application mostly built using KnockoutJS. I'm looking at if there is a possibility to migrate to React, but in a way that require rewriting the entire application. My idea was to use a bottom-up approach: start by replacing the basic building blocks one by one.
I was inspired by some prior work to call ReactDOM.render inside a KnockoutJS binding handler:
ko.bindingHandlers.react = {
init() {
return {controlsDescendantBindings: true};
},
update(element, valueAccessor) {
const {component, props} = valueAccessor();
ReactDOM.render(React.createElement(component, props), element);
}
};
Knockout has its own dependency tracking system, so it will call the update method whenever some data changes.
It works perfectly, and the component is re-rendered to reflect any changes to data. However, it breaks down when data is updated inside a React event handler. E.g., this component does not work as expected:
const Input = function ({value}) {
return <input type="text"
value={value()}
onChange={e => value(e.target.value)}/>;
}
Note: value in this case is a ko.observable, and calling it like in the event handler will cause the bindingHandler's update method to be called, which in turn calls ReactDOM.render. However, this render only works once, after that the component stops updating.
The issue is demonstrated in this CodePen. Click the box and try to type something. One update goes through, after than, the component's function stops being called.
Edit: I believe the issue is that the second call to ReactDOM.render (when the value is updated by the user in the onChange handler) is not synchronous. This means that Knockout's dependency detection cannot work and any subsequent calls no longer call the update method of the binding handler.
Can this be circumvented somehow?
As I guessed, the issue seems to be that ReactDOM.render is asynchronous in some cases - in this case when called from an event handler in React.
This means that if you dereference any observables that you depend on in the update method itself, Knockout's dependency tracking mechanism works as expected. This is why the modification Gawel1908 proposed makes it work - not because the value is "reset", but because props.value is dereferenced.
I decided to instead use a convention: always unwrap any such observables in the valueAccessor itself:
<div data-bind="react: { component: Input, props: { value: val(), setValue: val }}">
</div>
And don't unwrap it in the component:
const Input = function ({value, setValue}) {
return <input
type="text"
value={value}
onChange={e => setValue(e.target.value)}/>;
}
Updated, working codepen.
For me worked:
update(element, valueAccessor) {
const {component, props} = valueAccessor();
props.value(props.value());
ReactDOM.render(React.createElement(component, props), element);
}
if you refresh your observable it will work but unfortunetaly i don't know how.

Warning: This synthetic event is reused for performance reasons happening with <input type="checkbox" />

I've been working on a simple react-redux todo example for a class and I came across several warning messages that show in the console everytime I check and uncheck a checkbox input.
You can see the warnings in the following images.
I also did a google search for the warning message but couldn't find any solution that works. Also, what stroke my attention was that it looks like it was trying to access every property of the native event, and DOM element.
This is the code for the presentational component that has the input checkbox
class TodoItem extends React.Component {
state = {
isChecked: false
};
handleCheckbox = () => {
this.setState({
isChecked: !this.state.isChecked
});
};
render() {
const { todos, onItemClick } = this.props;
const { isChecked } = this.state;
return (
<div>
<ul>
{todos.map((todo, id) => {
return (
<li key={id} onClick={onItemClick}>
<input
onChange={this.handleCheckbox}
type="checkbox"
checked={isChecked}
/>
<label>
<span />
{todo.textInput}
</label>
</li>
);
})}
</ul>
</div>
);
}
}
export default TodoItem;
I uploaded the example on CodeSandbox as well: https://codesandbox.io/s/k0mlxk1yqv
If you want to replicate this error you need to add an Item to the todo List and click the checkbox to check and uncheck a couple of times.
If anyone has any idea why this warning signs keep appearing and how to disable them I would appreciate your input very much :)
This happened because the event implicitly passed to onItemClick is used in an asynchronous context.
As Andre Lemay said, you should assign your needs to local variables and reference them.
In my case, I had this code:
handleInput = e => { // <-- e = synthetic event
this.setState(state => ({ // <-- asynchronous call
data: {
...state.data,
[e.target.name]: e.target.value // <-- this was causing the warnings (e.target is in an asynchronous context)
}
}));
};
Then I changed it to:
handleInput = e => {
const { name, value } = e.target; // <-- moved outside asynchronous context
this.setState(state => ({
data: {
...state.data,
[name]: value
}
}));
};
I'd suggest trying two solutions:
First change
onChange={this.handleCheckbox}
to
onChange={() => this.handleCheckbox()}
If that won't work, in 'handleCheckbox' add event.persist(); Like this:
handleCheckbox = (event) => {
event.persist();
this.setState({
isChecked: !this.state.isChecked
});
};
This may be a little late, but I just came across the same problem and solved in a way that I think might be better than Adam Orlov's answer. I don't believe either answer is directly applicable to the asked question, but this comes up when googling about synthentic events and checkboxes so it's as good a place as any...
I believe Adam is correct in his belief that React will essentially clear all properties of the SyntheticEvent object (which makes sense, since React is telling us that it's reusing the object).
However, unless you need the entire object, I don't think calling event.persist() is the best solution, as according to the documentation, that will remove the object from the pool (presumably they put it there for a good reason).
If you want to access the event properties in an asynchronous way, you should call event.persist() on the event, which will remove the synthetic event from the pool and allow references to the event to be retained by user code.
Instead of doing this, if you only need one or two values from the event object, you can just assign those to local variables, and then reference the local variables inside your own function, like this:
<input type="checkbox" onChange={(event) => {
let checked = event.currentTarget.checked; //store whatever values we need from from the event here
this._someOtherFunction(checked)
} />
In this way, you don't have to restructure your code in any way to avoid doing anything async that relies on event data, and you also don't have to worry about potential performance impacts as you allow React to do what it wants with the event pool.
Similar problem here though my setup is, functional component, Material UI <Textform /> input.
The guy above that mentioned event.persist();, thank you that worked for me, but the first suggestion had no noticeable affect, not sure if thats because Im using functional components and not class components. (I dont use class components anymore, only functional with hooks)
Also note the warning info suggested to use event.persist(). My issue was I was capturing form input using onChange and storing input into my state, after about the second or third character it would throw errors and also crash my app.
Before:
const handleChange = (e) => {
setState((form) => ({
...form,
[e.target.name]: e.target.value,
}));
};
After:
const handleChange = (e) => {
e.persist();
setState((form) => ({
...form,
[e.target.name]: e.target.value,
}));
};
So it appears this is the correct solution to a similar issue, while I was not using a checkbox, I was using a form input, a Material-UI <TextField />. I can remove the single line of
e.persist();
and the code will fail again, add it back, everything is good no crashing no warnings.
for the ones that came to this problem with react native.
i face this problem on a navigation
PersonalProductsScreen.navigationOptions=({navigation})=>{
const handleEditButton=navigation.navigate.bind(this,"EditProduct")
return {
headerTitle:"My Products",
headerRight:<CustomHeaderButton
iconName="ios-add"
title="Add"
iconSize={26}
color={colors.bright}
onPress={handleEditButton}
/>
}
}
pay attention to the method i used . I was trying to bind the navigate method.
This is the refactor:
const handleAddButton=()=>navigation.navigate("EditProduct")

React Checkbox Does Not Update

I want to update an array each time a checkbox is toggled to true. With this current code, if I click on a checkbox it will log that it's false. Even though I have just updated the state. Does setState just take some time, like an API call? That doesn't make sense to me.
import React, {Component} from 'react';
class Person extends Component {
constructor(props) {
super(props);
this.state = {
boxIsChecked: false
};
this.checkboxToggle = this.checkboxToggle.bind(this);
}
checkboxToggle() {
// state is updated first
this.setState({ boxIsChecked: !this.state.boxIsChecked });
console.log("boxIsChecked: " + this.state.boxIsChecked);
if (this.state.boxIsChecked === false) {
console.log("box is false. should do nothing.");
}
else if (this.state.boxIsChecked === true) {
console.log("box is true. should be added.");
this.props.setForDelete(this.props.person._id);
}
}
render() {
return (
<div>
<input type="checkbox" name="person" checked={this.state.boxIsChecked} onClick={this.checkboxToggle} />
{this.props.person.name} ({this.props.person.age})
</div>
);
}
}
export default Person;
I have tried onChange instead of onClick. I feel like I'm already following advice I've read about for the basic component formulation from here and here. Is the fact I'm using Redux for other values affecting anything? Is there a way to just read what the checkbox is, instead of trying to control it? (The checkbox itself works fine and the DOM updates wether it's checked or not correctly.)
I know that this thread is answered but i also have a solution for this, you see the checkbox didn't update with the provided value of setState, i don't know the exact reason for this problem but here is a solution.
<input
key={Math.random()}
type="checkbox"
name="insurance"
defaultChecked={data.insurance}
/>
by giving the key value of random the checkbox gets re-rendered and the value of the checkbox is updated, this worked for me. Also i am using hooks, but it should work for class based implementation to.
reference: https://www.reddit.com/r/reactjs/comments/8unyps/am_i_doing_stupid_or_is_this_a_bug_checkbox_not/
setState() is indeed not reflected right away:
Read here in the docs:
setState() enqueues changes to the component state and tells React
that this component and its children need to be re-rendered with the
updated state. This is the primary method you use to update the user
interface in response to event handlers and server responses.
Think of setState() as a request rather than an immediate command to
update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee that the state changes are applied immediately.
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
setState() will always lead to a re-render unless
shouldComponentUpdate() returns false. If mutable objects are being
used and conditional rendering logic cannot be implemented in
shouldComponentUpdate(), calling setState() only when the new state
differs from the previous state will avoid unnecessary re-renders.
and here some experiments
So it might be better to catch the event and check it, and not depend on the this.setState()
Something like that:
handleChange: function (event) {
//you code
if (event.target.checked) {
console.log("box is true. should be added.");
this.props.setForDelete(this.props.person._id);
}
}
and
render() {
return (
<div>
<input type="checkbox" name="person" checked={this.state.boxIsChecked}
onChange={this.handleChange.bind(this)}/>
{this.props.person.name} ({this.props.person.age})
</div>
);
}
You can't access the updated value of state just after calling setState().
If you want to do stuff with updated value of state, you can do like this. i.e inside setState() callback function
checkboxToggle() {
// state is updated first
this.setState({ boxIsChecked: !this.state.boxIsChecked },()=>{
console.log("boxIsChecked: " + this.state.boxIsChecked);
if (this.state.boxIsChecked === false) {
console.log("box is false. should do nothing.");
}
else if (this.state.boxIsChecked === true) {
console.log("box is true. should be added.");
this.props.setForDelete(this.props.person._id);
}
});
React solution:
HTML:
<input type="checkbox" ref={checkboxRef} onChange={handleChange}/>
JS:
const checkboxRef = useRef("");
const [checkbox, setCheckbox] = useState(false);
const handleChange = () => {
setCheckbox(checkboxRef.current.checked);
};
Now your true/false value is stored in the checkbox variable
To expand no newbies answer; what worked for me:
remove preventDefault() from onChange Event-Listener but this didn't feel right for me since I want only react to update the value. And it might not apply in all cases like OPs.
include the checked value in the key like key={"agree_legal_checkbox" + legalcheckboxValue} checked={legalcheckboxValue}
I got both these solutions from newbies answer but having a math.random key just felt wrong. Using the value for checked causes the key only to change when when the checked value changes as well.
(not enough reputation to simply post this as a comment, but still wanted to share)
I arrived here trying to figure out why my checkbox, rendered using the HTM tagged template library, wasn't working.
return html`
...
<input type="checkbox" checked=${state.active ? "checked" : null} />
...
`;
It turns out I was just trying too hard with that ternary operator. This works fine:
return html`
...
<input type="checkbox" checked=${state.active} />
...
`;

Categories

Resources