I'm having a difficult time getting my onChange event to fire when a field within a form in my component is changed. When i make a change in the form the edit is allowed but i get the following error in the browser console
bundle.js:57140 Uncaught TypeError: Cannot set property '' of null
Any thoughts on how to resolve this issue would be of great help!
import React from 'react';
import ReactDOM from 'react-dom';
import autoBind from 'react-autobind';
import Form from 'grommet/components/Form';
import TextInput from 'grommet/components/TextInput';
import NumberInput from 'grommet/components/NumberInput';
import DateTime from 'grommet/components/DateTime';
import FormFields from 'grommet/components/FormField';
export default class OverviewEditPane extends React.Component {
constructor(props) {
super(props);
autoBind(this);
this.onOverviewChange = this.onOverviewChange.bind(this)
}
onOverviewChange(event) {
let state = this.state;
let field = event.target.name;
let value = event.target.value;
console.log(field);
state[field] = value;
this.setState({state});
}
render () {
return (
<table>
<FormFields>
<tbody>
<tr>
<td>{this.props.overview.map((P) => {return <TextInput size='small' key={P.id} id={P.id} value={P.FName} onChange={this.onOverviewChange} />;})}</td>
</tr>...
{ state } is shorthand for { state: state }. What you really want to do is update just one field in the state, not set the entire current state as state key.
Also make sure you don't mutate the state object directly.
onOverviewChange(event) {
const { name, value } = event.target;
this.setState({ [name]: value });
}
What you're trying to achieve here is wrong, but what you need (want) to do is probably this:
//Here's your updated function:
onOverviewChange(e) {
const { name, value } = e.target; // Dectructuring name and value event
this.setState({ [name]: value }); // Setting the state to it's new key/ value pairs
}
.... later in your code, you'll use this function to trigger an onChange method, something like this:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.onOverviewChange = this.onOverviewChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
onOverviewChange(e) {
const { name, value } = e.target; // Dectructuring name and value event
this.setState({ [name]: value }); // Setting the state to it's new key/ value pairs
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text"
// Here we associate it with our state
name="value"
// Here's where we make use of our function
onChange={this.onOverviewChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
ReactDOM.render(<Example/>, document.getElementById('container'));
<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="container"></div>
Related
I have a input box where I am showing the value if there is any fetched from the database and the input text box is editable. However, this input box doesn't reflect the changes on key press.
Here is the necessary following code:
constructor(props) {
super(props)
const { minorModel } = this.props;
this.state =
{
galleryModel: minorModel.galleryModel,
selectedIndex: 0
}
}
componentDidUpdate() {
this.props.minorModel.galleryModel = this.state.galleryModel;
}
onInputChange=(e)=>{
this.setState({
[e.target.name]:e.target.value
})
}
{this.state.galleryModel.photos.length > 0 &&
<PtInput type="text" label="Comment"
value={this.state.galleryModel.Comments[this.state.selectedIndex] === null ?
"Not Available " :
this.state.galleryModel.Comments[this.state.selectedIndex]}
onChange={this.onInputChange}
name={`${"Comment "+[this.state.selectedIndex]}`}
disabled={this.props.disabled}
/>
}
I probably think the problem is because I am using a data model which is passed as props in the constructor and the values in that data model is not changing so the new value is not reflected. Is that so? Any help is really appreciated to solve this issue.I also have a componentDidUpdate to reflect the changes.
There is no connection between the value attribute and your onChange handler, since they aren't relying on the same state, thus you have an uncontrolled input.
When the component mounts, you can load your data into the same state that is used to control the input.
Here is a simplified potential implementation:
class App extends React.Component {
constructor() {
super();
this.state = {
"pics": ['https://placehold.co/200x100', 'https://placehold.co/200x100'],
"comments": [["comment 1 pic 1", "comment 2 pic 1"], ["comment 1 pic 2", "comment 2 pic 2"]],
}
}
render() {
return (
<div>
{this.state.pics.map((pic, i) =>
<Picture pic={pic} comments={this.state.comments[i]} />)}
</div>
)
}
}
const Picture = ({ pic, comments }) => {
return <div>
<img src={pic} />
{comments.map(comment => <Comment comment={comment} />)}
</div>
}
class Comment extends React.Component {
constructor(props) {
super(props);
this.state = { value: this.props.comment };
this.handleChange = this.handleChange.bind(this);
}
componentDidUpdate() {
// do stuff when Comment component updates
}
handleChange(e) {
this.setState({ value: e.target.value });
}
render() {
return (
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
);
}
}
ReactDOM.render(<App />, 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>
Also, you shouldn't be updating props inside a component. They are meant to be immutable and top-down.
this.state.galleryModel.Comments[this.state.selectedIndex] is not the same state as this.state[${"Comment "+[this.state.selectedIndex]}]
So you are feeding the onChange value into state that you are not passing into the value prop on PtInput
to get things working properly, you need to decide which format you want to go with. You could resolve the galleryModel.Comments whenever minorModel updates to these indices ${"Comment "+[this.state.selectedIndex]} on your state, or work within the data model you have in galleryModel.Comments.
I'd suggest you consider why you need two sources of the same information.
I'm planning to add a prefilled form with React. I have the actual data on props. This is what I came up with.
#connect(...)
class Some extends React.Component {
state = {
...this.props.auth.user
}
render() {
// Create a form using the data on state
}
}
It looks not correct since I'm not using a react lifecycle hook here. I would like to ask if there is a better practice to achieve what I'm trying to do.
I am not sure about your architecture,since you are using uncontrolled component here, it is recommended to keep the source of truth at one place.
you can do something like this:
#connect(...)
class Some extends React.Component {
constructor(props) {
super(props);
this.state = {
userName:this.props.auth.user
}
}
handleChange = (event) => {
this.setState({userName: event.target.value});
}
render() {
return(
<div>
<input onChange={this.handleChange} id="some" type="text" value= {this.state.userName}/>
</div>
)
}
}
If you want to use controlled component that is controlled through parent/container. you can manage the values through props and set the props onChange.
So to elaborate on my previous responses you would do something like this to achieve what you want:
#connect(...)
class Some extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
}
}
handleChange = (event) => {
this.setState({ value: event.target.value });
}
render() {
return(
<div>
<input onChange={this.handleChange} id="some" type="text" value= {this.state.value|| this.props.value}/>
</div>
)
}
}
While your value is an empty string (in the state), the fields will be populated from your props and as soon as you start typing it will overwrite the prepopulated values with the ones in your state.
Best practices would be to actually have a Component that handles this logic and then passes the props to the form that should be just a dumb presentational component:
class SomeController extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
}
}
handleChange = (event) => {
this.setState({ value: event.target.value });
}
return (<Form handleChange={this.handleChange} value={this.state.value} />)
}
And then your form component:
const Form = (props) => (
<form>
<input onChange={props.handleChange} value={props.value} />
</form>
);
Hope this explanation helps.
i tried to use officejs react component in it and for osme reason i cant get it to work properly..effective here is the code. it works in codepen but when i put hte same code in excel addin project, i cant get the value in the textfields.
Code in codepen(it works):
[https://codepen.io/manish_shukla01/pen/ReWWmM][1]
Code in my project in app file(does not work in the sense that handlechange events are not getting fired i believe so value of my state.value1 remains blank even when i input anything):
import * as React from 'react';
import { Button, ButtonType } from 'office-ui-fabric-react';
import Header from './Header';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import * as OfficeHelpers from '#microsoft/office-js-helpers';
export default class App extends React.Component<any,any,any>{
constructor(props) {
super(props);
this.state = {
value1: '',
value2:'',
message:'Helloooo'
};
this.handleChange1 = this.handleChange1.bind(this);
this.handleChange2 = this.handleChange2.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange1(event) {
this.setState({value1: event.target.value});
}
handleChange2(event) {
this.setState({value2: event.target.value});
}
handleSubmit = async () => {
event.preventDefault();
this.setState({message: 'i got clicked'});
try {
//event.preventDefault();
await Excel.run(async context => {
/**
* Insert your Excel code here
*/
const range = context.workbook.getSelectedRange();
// Read the range address
range.load('address');
// Update the fill color
range.format.fill.color = 'blue';
range.values = [[this.state.value2]];
await context.sync();
console.log(`The range address was ${range.address}.`);
});
} catch(error) {
OfficeHelpers.UI.notify(error);
OfficeHelpers.Utilities.log(error);
};
}
render() {
return (
<form className='ms-welcome' onSubmit={this.handleSubmit}>
<Header logo='assets/logo-filled.png' title='Excel Analytics' message={this.state.message} />
<TextField label="field1"
value={this.state.value1} onChange={this.handleChange1}
required
/>
<Button className='ms-welcome__action' buttonType={ButtonType.primary}
onClick={this.handleSubmit}>Run
</Button>
</form>
);
}
}
I also faced same issue with Office UI Fabric TextField's "onChange" event and solved it using "onChanged" instead, which is deprecated as they say. But it worked for me.
First, add onChanged handler to TextField as below:
<TextField name="fieldName" label="field1" value={this.state.value1} onChanged={val => this.handleChange1("fieldName", val)} />
Also, note that "name" attribute is added to identify control in handleChange1.
Now change handler implementation as below:
handleChange1(name, value) {
this.setState(prevState => ({
result: {
...prevState.result,
[name]: value
}
}));
}
Hope this helps. Thanks!
I have a controlled input that accepts a prop called user. This object is passed to it by its parent component, where it is initialized asynchronously with an observer1.
In the parent component:
onAuthStateChanged() {
this.unregisterAuthObserver = onAuthStateChanged((user) => {
this.setState({
isSignedIn: Boolean(user),
user,
});
});
}
I would like to populate the initial state of the controlled input with user.displayName. Something like the following, which shouldn't be an anti-pattern because the state is only dependent on the prop user on construction.
class ControlledInput extends Component {
constructor(props) {
super(props);
const { user } = props;
this.state = {
displayName: user ? user.displayName : '',
};
}
}
When I refresh the controlled input, the following problem occurs:
The value of user is undefined because the observer has yet to fire. The component is constructed and displayName is assigned to ''.
The observer fires, user is assigned a complex object, and React re-renders the component. This does not change the state of the component and displayName is still ''.
I'm stuck on how to utilize the component lifecycle methods to achieve this. Is there a best practice for this scenario? Should I even be dependent on an asynchronous prop, or should I move the observer into the controlled component?
I've considered using componentDidUpdate() to determine when user is assigned, but it feels like a hack.
Use this as an opportunity to operate on the DOM when the component has been updated. This is also a good place to do network requests as long as you compare the current props to previous props (e.g. a network request may not be necessary if the props have not changed).
1 The observer is part of Firebase Authentication, but I'm not sure that's relevant to the question.
There is no need to assign in the constructor. You should use the props provided to the render method, and following the Lifting State Up pattern handle updates to the state in the parent. That will then flow down to the child component.
See the example below. The "increment" button simulates the async call. ControlledInput is a read-only display, while ControlledInput2 allows edits to the input that are reflected in state.
class Parent extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.onInputChanged = this.onInputChanged.bind(this);
this.state = {
user,
count: 1
};
}
handleClick(e) {
const { user, count } = this.state;
this.setState({
user: { ...user, displayName: `display ${count + 1}` },
count: count + 1
});
}
onInputChanged(name) {
this.setState({
user: Object.assign(user, { displayName: name })
});
}
render() {
const { user, count } = this.state;
return (
<div>
<ControlledInput user={user} />
<div>{user.displayName}</div>
<div>Count: {count}</div>
<button onClick={this.handleClick}>increment</button>
<div>
<ControlledInput2 onInputChanged={this.onInputChanged} user={user} />
</div>
</div>
);
}
}
const ControlledInput = ({ user }) =>
<input type="text" value={user.displayName} />;
class ControlledInput2 extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onInputChanged(e.target.value);
}
render() {
const { user } = this.props;
return (
<input
type="text"
onChange={this.handleChange}
value={user.displayName}
/>
);
}
}
const user = { displayName: undefined };
const props = { user };
const rootElement = document.getElementById("sample");
ReactDOM.render(<Parent {...props} />, rootElement);
<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>
<div id="sample"></div>
I am learning React and in the below code I get ...cannot update during an existing state transition.... While looking to fix it, I read in SO that setState should not be used within render() as it calls render() repeatedly resulting in infinite loop. But I dont know how to fix it.
import React from 'react';
import ReactDOM from 'react-dom';
export default class CheckBox extends React.Component{
constructor() {
super();
this.state = {isChecked: false};
}
handleChecked () {
this.setState({isChecked: !this.state.isChecked});
}
render(){
var txt;
if (this.state.isChecked) {
txt = 'checked'
} else {
txt = 'unchecked'
}
return(
<div>
<input type="checkbox" onChange={this.handleChecked()}/>
<p>This box is {txt}</p>
</div>
);
}
}
ReactDOM.render(<CheckBox/>, document.getElementById('hello'));
You should pass to onChange reference to function but not call it., in your example you are calling handleChecked(because there is () after function) and result pass to onChange however result in this case will be undefined so onChange looks like onChange={ undefined }. Also, you can't set state during the initial render, but you are trying to do it with this.handleChecked() which contains this.setState.
export default class CheckBox extends React.Component{
constructor() {
super();
this.state = {isChecked: false};
// set this (with .bind),
// because you need get methods from CheckBox, like .setState
this.handleChecked = this.handleChecked.bind(this);
}
handleChecked () {
this.setState({isChecked: !this.state.isChecked});
}
render(){
var txt;
if (this.state.isChecked) {
txt = 'checked'
} else {
txt = 'unchecked'
}
// remove () after handleChecked because you need pass
// reference to function
// also add return statement before <div>
return <div>
<input type="checkbox" onChange={ this.handleChecked }/>
<p>This box is {txt}</p>
</div>
}
}
Example