I have declared a state called account_type. I have created an onChange event which changes the value of the state upon clicking the div.
<div
className="price-plan"
value="star"
onClick={() => this.setPlan("star")}
>
The issue is that the account_type state does not get updated the first time I click on the div. It only gets updated when I click on it twice. Is there a way to update the state just by clicking the div. Here's an excerpt from my code showing what I am trying to do
let isRedirect = false;
class PricePlan extends React.Component {
constructor(props) {
super(props);
this.state = {
account_type: "",
renderRedirect: false
};
this.handleChange = this.handleChange.bind(this);
}
// Handle fields change
handleChange = input => e => {
this.setState({ [input]: e.target.value });
};
setPlan(plan) {
this.setState({
account_type: plan
});
console.log(this.state.account_type);
// if (this.state.account_type !== undefined) {
// isRedirect = true;
// }
}
render() {
if (isRedirect) {
return (
<Redirect
to={{
pathname: "/sign-up",
state: { step: 2, account_type: this.state.account_type }
}}
/>
);
}
return (
<div
className="price-plan"
value="star"
onClick={() => this.setPlan("star")}
>
<h3>{this.props.planName}</h3>
<div className="mute price-row">Name</div>
<p className="price">Price</p>
<span className="billed-frequency">Cycle</span>
</div>
);
}
}
As #Jayce444 suggests, setState do not immedeately updates state. So setPlan should look like
setPlan(plan) {
this.setState({
account_type: plan
});
console.log(plan); // Don't expect immediate state change in event handler
}
But you can use this.state.account_type anywhere in render() function. And rendering will happen after this.state.account_type is updated on first click.
Related
I'd like for my state to update changes to immediately be shown when state changes, but I can't seem to figure out why It isn't. Basically when a user clicks on a dropdown item from the menu, the items inner text ... that they clicked on should appear as an h1 on the screen, but instead it doesn't appear until the next click. How can I change this? Hopefully I made sense. Code can be found here.
Parent Component (APP):
class App extends React.Component {
state = {
loading: true,
bases: ['USD', 'EUR', 'AUD', 'CAD', 'JPY', 'NZD'],
selectedBase: null
};
// When Component Mounts Overlay goes for 3 Seconds
componentDidMount() {
setTimeout(() => this.setState({
loading: false,
}), 3000)
this.onBaseChange('USD');
}
// When User selects a new Base in Search Component, state is updated
onBaseChange = newBase => {
this.setState({ selectedBase: newBase });
}
// need to find out how to see state change immediatly after its updated!
// Rendered Content:
render(){
return (
<>
{this.state.loading === false ? (
<div className="App">
<div id="one">
<h1>{this.state.selectedBase}</h1>
<Search bases = {this.state.bases} selectedBase = {this.state.selectedBase} onBaseChange = {this.onBaseChange}/>
</div>
</div>
) : (
<Overlay />
)}
</>
);
}
}
export default App;
Child Component (Search):
class Search extends Component {
state = {
dropdownVisible: false,
term: '',
selectedBase: this.props.selectedBase
};
// when a base is clicked from dropdown, the selectedBase is updated, term is set back to empty, and dropdown back to non-visible.
// passing state of child up to parent through prop
// clearing input search on click
onBaseSelect = (event) => {
// when an base is clicked from dropdown, the selectedBase is updated, term is set back to empty, and dropdown back to nonvisible.
this.setState({
selectedBase: event.target.innerHTML,
term: '',
dropdownVisible: false
})
// passing state of child up to parent through prop
this.props.onBaseChange(this.state.selectedBase)
// clearing input search on click
document.getElementById("input_search").value = "";
}
render(){
return(
<div id="search">
<div id="dropdown" style={{display: this.state.dropdownVisible ? "block" : "none"}}>
<ul>
{/* filterng out base array based on users input */}
{this.props.bases.filter(base => base.includes(this.state.term.toUpperCase())).map((filteredBase, index) => (
<li onClick = {this.onBaseSelect} key={index}>{filteredBase}</li>
))}
</ul>
</div>
</div>
)
}
}
export default Search
this.setState is an asynchronous function, so when you do
// passing state of child up to parent through prop
this.props.onBaseChange(this.state.selectedBase)
// clearing input search on click
document.getElementById("input_search").value = "";
the state is not yet updated. So send that code as as a callback to this.setState like this,
onBaseSelect = (event) => {
// when an base is clicked from dropdown, the selectedBase is updated, term is set
back to empty, and dropdown back to nonvisible.
this.setState({
selectedBase: event.target.innerHTML,
term: '',
dropdownVisible: false
},
()=>{
// passing state of child up to parent through prop
this.props.onBaseChange(this.state.selectedBase)
// clearing input search on click
document.getElementById("input_search").value = "";
);
}
I have a component with an empty metadata object at DOM load, the server sends data to fill the empty metadata object with properties that will be assigned values within the form. I am able to iterate through the meta data and see multiple input fields correctly labeled yet when I got to input something it either doesn't change anything and the console logs the single keystroke or it returns TypeError: Cannot read property 'handleChange' of undefined. The title field handles the change just fine.
My code:
class Item extends React.Component{
constructor(props) {
super(props);
this.state = {
title: '',
metadata: {}
}
}
componentDidMount() {
... //retrieve metadata from server
this.setState({
metadata: metadata
});
console.log(metadata); //{meta1: "", meta2: "", meta3: "", meta4: "", meta5: "", …}
}
handleChange = (field) => {
return (value) => this.setState({ [field]: value });
}
render() {
const {
title,
metafield
} = this.state;
}
return(
//code to start form
<TextField value={title} onChange={this.handleChange(title)} label="Title" type=text />
{Object.keys(metadata).map(function(key) {
return (
<TextField key={key} value={metadata[key]} onChange={this.handleChange({key})} label={key} type=text />
)
})}
//code to end form
)
}
I'm sure it's because the handleChange isn't equipped to handle changes on object properties but I'm not sure how to access that layer. I've tried binding a handleMetadataChange function on the constructor and use e.target to assign the values but the failing behavior persists.
There are a couple of bugs:
handleChange sets state like this: this.setState({ [field]: value}); but the values are in state.metadata not in state.
In render
you get metafield from state but initially you set metadata
and in handleChange you don't use any of it.
You always re create onChange for TextField even if nothing has changed, this causes needless DOM re renders.
Here is a working example:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
metadata: {},
};
}
componentDidMount() {
Promise.resolve().then(() =>
this.setState({
metadata: { x: 'x', y: 'y' },
})
);
}
handleChange = (field, value) =>
//you forgot you are setting metadata of state
this.setState({
...this.state,
metadata: { ...this.state.metadata, [field]: value },
});
render() {
const {
metadata, //you used metaField here but it's metadata
} = this.state;
return (
<div>
{Object.keys(metadata).map(key => (
<TextField
key={key}
value={metadata[key]}
onChange={this.handleChange} //always pass the same handler function
changeKey={key} //added for optimization
label={key}
/>
))}
</div>
);
}
}
//make textfield a pure component as it only receives props
// You could call this TextFieldContainer and not change TextField at all
const TextField = React.memo(function TextField({
value,
onChange,
changeKey,
label,
}) {
const rendered = React.useRef(0);
rendered.current++;
return (
<div>
times rendered: {rendered.current}
<label>
{label}
<input
type="text"
value={value}
onChange={e =>
onChange(changeKey, e.target.value)
}
/>
</label>
</div>
);
});
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Insert this at the end of your constructor: this.handleChange = this.handleChange .bind(this);
You have to be careful about the meaning of this in JSX callbacks. In JavaScript, class methods are not bound by default. If you forget to bind this.handleClick and pass it to onClick, this will be undefined when the function is actually called.
This is not React-specific behavior; it is a part of how functions work in JavaScript. Generally, if you refer to a method without () after it, such as onClick={this.handleClick}, you should bind that method
Handling Events
class Item extends React.Component{
constructor(props) {
super(props);
this.state = {
title: '',
metadata: {}
}
}
componentDidMount() {
... //retrieve metadata from server
this.setState({
metadata: metadata
});
console.log(metadata); //{meta1: "", meta2: "", meta3: "", meta4: "", meta5: "", …}
}
handleChange = (field,e) => {
let temp = this.state.metdata;
temp[field] = e.target.value;
this.setState({metadata: temp });
}
render() {
const {
title,
metafield
} = this.state;
}
return(
//code to start form
<TextField value={title} onChange={this.handleChange(title)} label="Title" type=text />
{Object.keys(metadata).map(function(key) {
return (
<TextField key={key} value={metadata[key]} onChange={(e)=>this.handleChange(e,key)} label={key} type=text />
)
})}
//code to end form
)
}
I'm currently following this and I did get it to work. But I would like to know if there is a way to stop the Query Render from reloading the data when calling this.setState(). Basically what I want is when I type into the textbox, I don't want to reload the data just yet but due to rendering issues, I need to set the state. I want the data to be reloaded ONLY when a button is clicked but the data will be based on the textbox value.
What I tried is separating the textbox value state from the actual variable passed to graphql, but it seems that regardless of variable change the Query will reload.
Here is the code FYR.
const query = graphql`
query TestComponentQuery($accountId: Int) {
viewer {
userWithAccount(accountId: $accountId) {
name
}
}
}
`;
class TestComponent extends React.Component{
constructor(props){
super(props);
this.state = {
accountId:14,
textboxValue: 14
}
}
onChange (event){
this.setState({textboxValue:event.target.value})
}
render () {
return (
<div>
<input type="text" onChange={this.onChange.bind(this)}/>
<QueryRenderer
environment={environment}
query={query}
variables={{
accountId: this.state.accountId,
}}
render={({ error, props }) => {
if (error) {
return (
<center>Error</center>
);
} else if (props) {
const { userWithAccount } = props.viewer;
console.log(userWithAccount)
return (
<ul>
{
userWithAccount.map(({name}) => (<li>{name}</li>))
}
</ul>
);
}
return (
<div>Loading</div>
);
}}
/>
</div>
);
}
}
Okay so my last answer didn't work as intended, so I thought I would create an entirely new example to demonstrate what I am talking about. Simply, the goal here is to have a child component within a parent component that only re-renders when it receives NEW props. Note, I have made use of the component lifecycle method shouldComponentUpdate() to prevent the Child component from re-rendering unless there is a change to the prop. Hope this helps with your problem.
class Child extends React.Component {
shouldComponentUpdate(nextProps) {
if (nextProps.id === this.props.id) {
return false
} else {
return true
}
}
componentDidUpdate() {
console.log("Child component updated")
}
render() {
return (
<div>
{`Current child ID prop: ${this.props.id}`}
</div>
)
}
}
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
id: 14,
text: 15
}
}
onChange = (event) => {
this.setState({ text: event.target.value })
}
onClick = () => {
this.setState({ id: this.state.text })
}
render() {
return (
<div>
<input type='text' onChange={this.onChange} />
<button onClick={this.onClick}>Change ID</button>
<Child id={this.state.id} />
</div>
)
}
}
function App() {
return (
<div className="App">
<Parent />
</div>
);
}
I have a <Select> component from react-select renders a couple options to a dropdown, these options are fetched from an api call, mapped over, and the names are displayed. When I select an option from the dropdown the selected name does not appear in the box. It seems that my handleChange method is not firing and this is where I update the value of the schema name:
handleChange = value => {
// this is going to call setFieldValue and manually update values.dataSchemas
this.props.onChange("schemas", value);
This is not updating the value seen in the dropdown after something is selected.
I'm not sure if I'm passing the right thing to the value prop inside the component itself
class MySelect extends React.Component {
constructor(props) {
super(props);
this.state = {
schemas: [],
fields: [],
selectorField: ""
};
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
axios.get("/dataschemas").then(response => {
this.setState({
schemas: response.data.data
});
console.log(this.state.schemas);
});
}
handleChange = value => {
// this is going to call setFieldValue and manually update values.dataSchemas
this.props.onChange("schemas", value);
const schema = this.state.schemas.find(
schema => schema.name === value.target.value
);
if (schema) {
axios.get("/dataschemas/2147483602").then(response => {
this.setState({
fields: response.data.fields
});
console.log(this.state.fields);
});
}
};
updateSelectorField = e => {
this.setState({ selectorField: e.target.value });
};
handleBlur = () => {
// this is going to call setFieldTouched and manually update touched.dataSchemas
this.props.onBlur("schemas", true);
};
render() {
return (
<div style={{ margin: "1rem 0" }}>
<label htmlFor="color">
DataSchemas -- triggers the handle change api call - (select 1){" "}
</label>
<Select
id="color"
options={this.state.schemas}
isMulti={false}
value={this.state.schemas.find(
({ name }) => name === this.state.name
)}
getOptionLabel={({ name }) => name}
onChange={this.handleChange}
onBlur={this.handleBlur}
/>
{!!this.props.error && this.props.touched && (
<div style={{ color: "red", marginTop: ".5rem" }}>
{this.props.error}
</div>
)}
</div>
);
}
}
I have linked an example showing this issue.
In your handleChange function you are trying to access value.target.value. If you console.log(value) at the top of the function, you will get:
{
id: "2147483603"
selfUri: "/dataschemas/2147483603"
name: "Book Data"
}
This is the value that handChange is invoked with. Use value.name instead of value.target.value.
I have a list of dynamically generated inputs.
input --> onClick new Input beneath
[dynamically added]input
input
How can give just this dynamically added input focus?
The input has the textInput ref. This partly works:
componentWillUpdate(){
this.textInput.focus();
}
Yet, just works or the first new Input. Then it seems like the logic breaks.
the inputs are .map() from an array. Is there a way to either say, if the current rendered element has el.isActive to focus it. Or just say focus the input with the index 5?
CODE
Inputsgenerating file/component
import React from 'react';
import ReactDOM from 'react';
import _ from 'lodash'
class SeveralInputs extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ' '
}
this.showIndex = this
.showIndex
.bind(this)
this.map = this
.map
.bind(this)
this.handleChange = this
.handleChange
.bind(this);
}
componentWillUpdate() {
this.textinput && this
.textInput
.focus();
}
render() {
return (
<ul>
{this.map()}
</ul>
)
}
map() {
{
return this
.props
.data
.map((name, index) => <li
onKeyPress={this
.showIndex
.bind(this, index)}
key={index}><input
onChange={this
.handleChange
.bind(this, index)}
task={this.task}
value={name.value}
ref={(input) => {
this.textInput = input;
}}
type="text"/>{name.value}</li>)
}
}
handleChange(index, e) {
let data = this
.props
.data
.splice(index, 1, {
value: e.target.value,
isActive: true
})
this
.props
.refreshState(data);
}
showIndex(index, e) {
if (e.which === 13 || e.keyPress === 13) {
let data = this.props.data[index].isActive = false
data = this
.props
.data
.splice(index + 1, 0, {
value: ' ',
isActive: true
})
this
.props
.refreshState(data);
} else {
return null
}
}
}
export default SeveralInputs
The data that lives in the parent component
const data = [
{
value: 0,
isActive: true
}, {
value: 2,
isActive: false
}
]
The parents state:
this.state = {
error: null,
data
};
The parents render
render() {
return (
<div>
{/* <Input/> */}
{/* <SeveralItems refreshState={this.refreshState} data={this.state.data.value}/> */}
<SeveralInputs refreshState={this.refreshState} data={this.state.data}/> {/* <SeveralInputsNested refreshState={this.refreshState} data={this.state.data}/> {this.items()} */}
</div>
);
}
refreshState(data) {
this.setState({data: this.state.data})
console.log(this.state.data)
}
The first issue I see is that in refreshState you pass some data that you do not handle, try this:
refreshState(newData) {
this.setState({data: newData})
}
And trying to log this.state right after won't work because :
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.