I have a parent/child component:
class Input extends React.Component {
constructor(props) {
super(props);
this.state = {value: this.props.value};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
// event is persisted, used to update local state
// and then call parent onChange callback after local state update
e.persist();
this.setState(
{value: e.target.value},
() => this.props.onChange(e)
);
}
render() {
return (<input ... onChange={this.handleChange} />);
}
}
class Page extends React.Component {
constructor(props) {
super(props);
this.state = {modified: false};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const {name, value} = e.target;
console.log(`${name}: ${value}`);
// the two line execute fine, and everything works ok
// but as soon as I add the bottom on, Input no longer updates!
this.setState({modified: true});
}
render() {
return (
<Input ... onChange={this.handleChange}
style={{backgroundColor: this.state.modified?"red":"blue"}}
/>
);
}
}
So as soon as I do a setState on the parent, the child component no longer renders properly. Why? Does it have something to do with the event object or the fact that the parent event handler is called from the child event handler?
Use this (add spaces before and after ?):
style={{backgroundColor: this.state.modified ? "red" : "blue"}}
instead of this:
style={{backgroundColor: this.state.modified?"red":"blue"}}
After this change your example works fine:
class Input extends React.Component {
constructor(props) {
super(props);
this.state = {value: this.props.value};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
// event is persisted, used to update local state
// and then call parent onChange callback after local state update
e.persist();
this.setState(
{value: e.target.value},
() => this.props.onChange(e)
);
}
render() {
return (<input style={this.props.style} onChange={this.handleChange} />);
}
}
class Page extends React.Component {
constructor(props) {
super(props);
this.state = {modified: false};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const {name, value} = e.target;
console.log(`${name}: ${value}`);
// the two line execute fine, and everything works ok
// but as soon as I add the bottom on, Input no longer updates!
this.setState({modified: true});
}
render() {
return (
<Input onChange={this.handleChange}
style={{backgroundColor: this.state.modified ? "red":"blue"}}
/>
);
}
}
ReactDOM.render(
<Page />,
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">
<!-- This element's contents will be replaced with your component. -->
</div>
Related
I'm trying to send a form-submission handler to a child element through props. Everything renders, but when I click the submit button, I get no alert (see alert('Hi') in the handleSubmit function), and I also don't see the elements of SearchResults change. Instead, the whole page reloads and I'm back in the initial state. What is wrong?
Searcher.js:
class Searcher extends React.Component {
constructor(props) {
super(props);
this.state = {
results: [
{name:'1', key:0},
{name:'2', key:1}
]
};
}
handleSubmit = (event) => {
alert('Hi');
this.setState({
results: [
{name: 'hi', key: 0},
{name: 'again', key: 1}
]
})
event.preventDefault();
}
render() {
return (
<div>
<SearchForm/>
<SearchResults results={this.state.results} handleSubmit={this.handleSubmit}/>
</div>
)
}
}
export default Searcher;
SearchForm.js:
class SearchForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) { this.setState({value: event.target.value}); }
render() {
return (
<form onSubmit={this.props.handleSubmit}>
<input type="text" value={this.state.value} onChange={this.handleChange} />
<input type="submit" value="Submit" />
</form>
);
}
}
export default SearchForm;
SearchResults.js:
class SearchResults extends React.Component {
render() {
return (
this.props.results.map((result) => (<div key={result.key}>{result.name}</div>))
)
}
}
export default SearchResults;
Passing handeSubmit as props in <SearchForm /> in Searcher.js
render() {
return (
<div>
<SearchForm handleSubmit={this.handleSubmit}/> // Pass Handle Submit
<SearchResults results={this.state.results} /> // I don't see handleSubmit being used in SearchResults
</div>
)
}
Try passing handleSubmit to SearchForm or its value will be undefined
class Searcher extends React.Component {
...
handleSubmit = (event) => {
alert('Hi');
...
}
render() {
return <SearchForm handleSubmit={this.handleSubmit}/>
}
}
export default Searcher;
I am having an issue getting a third component to show up using REACT.
I am trying to change the state of workflow, and show Component Three, but I am not sure what I am doing wrong.
After clicking on the continue button, the state changes.
Workflow is changed to WELCOME_MSG, the switch below works.
But I can't seem to return this Component "ComponentThree"
case 'WELCOME_MSG':
return ();
class ComponentOne extends React.Component {
constructor(props) {
super(props)
this.state = {
workflow: 'GET_NAME'
}
this.setWorkflow = this.setWorkflow.bind(this);
}
setWorkflow() {
switch(this.state.workflow){
case 'GET_NAME':
return (<ComponentTwo/>);
case 'WELCOME_MSG':
return (<ComponentThree name={this.state.name} />);
}
}
render() {
console.log('ComponentOne: ',this.state.workflow );
return this.setWorkflow();
/*
switch(this.state.workflow){
case 'GET_NAME':
return (<ComponentTwo/>);
case 'WELCOME_MSG':
return (<ComponentThree name={this.state.name} />);
} */
}
}
// showThree()
class ComponentTwo extends React.Component {
constructor(props){
super(props)
this.state = {
workflow: 'GET_NAME',
name: 'Chris'
}
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
this.setWorkflow = this.setWorkflow.bind(this);
}
handleChange(e) {
//this.setState({name: event.target.name});
e.persist();
console.log('handleChange event.target.name:', e.target);
this.setState((prevState, props) => {
return {
name: e.target.value,
workflow: 'WELCOME_MSG',
}
})
/*
this.setState(state => ({
//name: this.state.name,
name: e.target.value,
//name: "sadfasdf",
})); */
}
setWorkflow() {
console.log("setWorkflow", this.state.workflow);
switch(this.state.workflow){
case 'GET_NAME':
return (<ComponentTwo/>);
case 'WELCOME_MSG':
return (<ComponentThree />);
}
// name={this.state.name}
}
handleClick(e) {
//console.log(this.state);
//e.preventDefault();
console.log('BEFORE handleClick this STATE ON CLICK :', this.state.workflow);
console.log('this.state.name:', this.state.name);
this.setState((prevState, props) => {
return {
workflow: 'WELCOME_MSG',
name: this.state.name,
}
})
return this.setWorkflow();
//this.setWorkflow = this.setWorkflow.bind(this);
/* this.setState(state => ({
//name: this.state.name,
name: this.state.name,
workflow: 'WELCOME_MSG'
})); */
console.log('ON CLICK AFTER SET STATE:', this.state);
//return (<ComponentThree name={this.state.name} />);
// e.preventDefault();
}
render() {
//console.log('this is:', this);
// onChange={this.handleChange}
return (
<div>
<h1>Enter your name</h1>
<div className="grid20 md-grid100">
<input type="text" name="fname" value={this.state.name} onChange={this.handleChange} />
</div>
<div className="grid80 md-grid100">
<button onClick={this.handleClick} >Continue</button>
</div>
</div>
)
}
}
class ComponentThree extends React.Component {
render() {
console.log('ComponentThree this is:', this);
return (
<div className="test">
<h1>Hello {this.state.name}</h1>
<h2>sfasfdadf</h2>
</div>
)
}
}
ReactDOM.render(<ComponentOne />, document.querySelector("#app"))
JS Fiddle Below
https://jsfiddle.net/ameshkin/cvg2rjzo/32/
The state variable to show ComponentTwo or Three is in ComponentOne, but you are updating the state in ComponentTwo, and not updating the state in ComponentOne. To update the state in ComponentOne, you need to pass a function from One to Two in the props.
Edit:
Here's a working fiddle https://jsfiddle.net/j5gxovdm/
Basically having the state in one place, instead of spread across all components (keeping a single source of truth)
return (
this.state.workflow == 'GET_NAME'
? <ComponentTwo
setWorkflow={this.setWorkflow.bind(this)}
onChange={(e) => this.setState({name: e.target.value})}
name={this.state.name}
/>
: <ComponentThree name={this.state.name} />
)
ComponentTwo has its own state and that state is updated upon clicking the button, not the ComponentOne state. As ComponentOne is a container component to switch ComponentTwo and ComponentThree, you have to pass the ComponentOne state as props for ComponentTwo and ComponentThree.
JS Fiddle Below
https://jsfiddle.net/richard929/g0b4d3ur/1/
I keep getting the error undefined is not an object (evaluating 'event.preventDefault)
Clearly i am not aloud to give an event parameter on this.handleChange on my child component that is rendered in the parent component.
Why i don't know. Been following a few online examples and they al did quite the same thing so I don't know why it's impossible to use (event) in my handleChange function.
// Parent component //
export class Home extends Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
state = {
name: ""
};
handleChange = event => { // so this event causes problems ...
// when i skip event i get my console.log
// and don't receive any errors
event.preventDefault();
console.log("handleChange has fired");
this.setState({
name: event.target.value
});
console.log(this.state.name);
};
render() {
console.log(this.state.name);
return (
<View style={styles.container}>
<MainTitle message={"Bienvenue " + this.state.name} />
<InputField getNewName={this.handleChange} />
<PrimaryButton text={"button"} />
</View>
);
}
}
// Child component //
class InputField extends Component {
constructor(props) {
super(props);
}
render() {
const { getNewName } = this.props
return (
<TextInput
placeholder={"Please enter your name"}
onChangeText={() => getNewName()}
/>
);
}
}
export default InputField;
I am assuming you don't need the native event object in your TextInput, because you want to use onChangeText which is perfect for your use case and easier to use. But you should pass the new value to your this.props.getNewName like this:
<TextInput
style={{height: 40, borderColor: 'gray', borderWidth: 1}}
onChangeText={(newText) => this.props.getNewName(newText)}
value={....}
/>
see here: https://facebook.github.io/react-native/docs/textinput
And your handler should not expect the event, but the newText passed from child TextInput:
handleChange = newTextHere => {
console.log("handleChange has fired", newTextHere);
this.setState({
name: newTextHere
});
console.log(this.state.name);
};
I am learning the concept of States in React. I am trying to understand the difference between using this.handleChange, and this.state.handleChange.
I would be grateful if someone could explain to me, the exact difference between the two, and why would this.state.handleChange not work?
class MyApp extends React.Component {
constructor(props) {
super(props);
this.state = {
inputValue: ''
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({
inputValue: event.target.value
});
}
render() {
return (
<div>
< GetInput input={this.state.inputValue} handleChange={this.handleChange} />
{ /* this.handleChanges, and this.state.handleChanges */ }
< RenderInput input={this.state.inputValue} />
</div>
);
}
};
class GetInput extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h3>Get Input:</h3>
<input
value={this.props.input}
onChange={this.props.handleChange}/>
</div>
);
}
};
class RenderInput extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h3>Input Render:</h3>
<p>{this.props.input}</p>
</div>
);
}
};
You can technically call this.state.handleChange so long as you add handleChange in your state.
But it doesn't really make sense since you don't want React to keep a track of it, and it will probably not change (unless you are doing some clever tricks).
constructor(props) {
super(props);
this.state = {
handleChange: e => {
e.preventDefault();
console.log("this.state.handleChange");
}
};
}
One would normally declare a member function in a class.
handleChange = e => {
e.preventDefault();
console.log("this.handleChange");
};
Here is the full working code
(working demo available on CodeSandBox).
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
handleChange: e => {
e.preventDefault();
console.log("this.state.handleChange");
}
};
}
handleChange = e => {
e.preventDefault();
console.log("this.handleChange");
};
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button onClick={this.handleChange}>this.handleChange</button>
<button onClick={this.state.handleChange}>
this.state.handleChange
</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
When you say this.state.something this means something is in the state field of the class. When you say this.someFunction this means something is in the class itself. this here is pointing out our class.
class App extends React.Component {
state = {
something: "Something",
}
someFunction = () => console.log(this.state.something);
render() {
return (
<div>
<button onClick={this.someFunction}>Click</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>
So, you can't use this.state.handleChange since there is no handleChange in the state. It is a function belongs to the class. This is why we use this.handleChange.
you can store a function in state
constructor(super){
super(props)
this.state = {
generateANumber: () => this.setState({ number: Math.floor(Math.random() * 100) }),
number: 0
}
}
then if you want to call it in your render method
render() {
return <p> {this.state.number} <button onClick={() => this.state.generateANumber()} Press Me To Generate A New Number </button> </p>
}
This is the concept of storing a function in state. This.function just means the function belongs to that class so you can use it using the this keyword.
Let's say I have a react component that updates state from a form.
class Form extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
someCheckboxState: false,
}
}
render() {
return (
<input onChange={this.handleChange} checked={this.state.someCheckboxState} />
);
}
handleChange(event) {
this.setState({
someCheckboxState: event.target.checked,
});
}
}
Now I want to send that state to the server (or somewhere). If I just do this
handleChange(event) {
this.setState({
someCheckboxState: event.target.checked,
});
SendStateToServer(JSON.stringify(this.state)); // BAD! Not yet mutated
}
I could put it in render but then it will get sent to the server on initial render as well as well and it seems silly to send state a function called render.
When is it okay to persist/serialize the state?
The second argument of React's setState is a callback that is fired after the state transition is complete.
this.setState(newState, () => console.log(this.state));
So, in your case:
handleChange(event) {
this.setState({
someCheckboxState: event.target.checked,
}, () => {
SendStateToServer(JSON.stringify(this.state));
});
}
Yes, setState() second parameter, perfect way to do such things.
class Form extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
someCheckboxState: false,
}
}
render() {
return ( < input type = "checkbox"
onChange = {
this.handleChange
}
checked = {
this.state.someCheckboxState
}
/>
);
}
handleChange(event) {
this.setState({
someCheckboxState: event.target.checked,
}, () => { // do your work
console.log(JSON.stringify(this.state));
});
}
}
ReactDOM.render( < Form / > , 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>