I am building a basic react app combined with the Pokeapi. Whenever the user types something in the input field of my pokedex, I want to update the state to then (onSubmit) find this pokemon in the Pokeapi.
Whenever I log the state (in the state update function), it logs the state -1 character as typed in the input field.
Printscreen of result
Snippet of component:
import React, { Component } from 'react';
export default class Pokedex extends Component {
constructor(props) {
super(props);
this.state = {
pokemon: "",
result: {}
}
}
setPokemon(value) {
this.setState({
...this.state.pokemon,
pokemon: value.toLowerCase()
});
console.log(this.state.pokemon);
}
render() {
return (
<div className="container-fluid">
<div className="pokedex row">
<div className="col-half left-side">
<div className="screen"/>
<div className="blue-button"/>
<div className="green-button"/>
<div className="orange-button"/>
</div>
<div className="col-half right-side">
<input type="text" placeholder="Find a pokemon" onChange={(e) => this.setPokemon(e.target.value)}/>
</div>
</div>
</div>
)
}
}
Why does this happen?
setState is an async function. That means using console.log immediately after setState will print the last state value. If you want to see the latest updated value then pass a callback to setState function like this
setPokemon(value) {
this.setState({pokemon: value.toLowerCase()},
() => console.log(this.state.pokemon));
}
This first way you can directly set the state of pokemon inside of the input.
<input type="text" placeholder="Find a pokemon" onChange={(e) => this.setState({ pokemon:e.target.value }) }/>
remove the function set pokemon.
setPokemon(value) {
this.setState({
...this.state.pokemon,
pokemon: value.toLowerCase()
});
console.log(this.state.pokemon);
}
theres no reason to use the spread operator, all you would simply do if you did want to use a setter is,
setPokemon = (value) => {
this.setState({ pokemon:value })
}
but even then the first way is better.
Theres also
setPokemon = (e) => {
this.setState({ pokemon:e.target.value })
}
then in input <input onChange={this.setPokemon()} />
Related
I'm working on a CV Generator and I don't know how to properly append the school and field of study values to a new div inside React.
Using the onSubmit function I'm able to get the values after filling them out and clicking save, but I can't figure out where to go from here.
Update
What I want to do is take the values from the input and create a new div above the form that displays those values. For example, I want the School value to show
School: University of Whatever
And the same goes for Field of Study.
Field of Study: Whatever
I know how to do this in vanilla JS but taking the values and appending them to the DOM but it doesn't seem to work that way in React.
class Education extends Component {
constructor(props) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
}
onSubmit = (e) => {
e.preventDefault();
const schoolForm = document.getElementById("school-form").value;
const studyForm = document.getElementById("study-form").value;
};
render() {
return (
<>
<h1 className="title">Education</h1>
<div id="content">
<form>
<label for="school">School</label>
<input
id="school-form"
className="form-row"
type="text"
name="school"
/>
<label for="study">Field of Study</label>
<input
id="study-form"
className="form-row"
type="text"
name="study"
/>
<button onClick={this.onSubmit} className="save">
Save
</button>
<button className="cancel">Cancel</button>
</form>
)}
</div>
</>
);
}
}
export default Education;
You should use state in order to save the values then show it when the user submits.
import React from "react";
class App extends React.Component {
constructor(props) {
super(props);
this.state = { scool: "", study: "", showOutput: false };
this.onSubmit = this.onSubmit.bind(this);
}
onSubmit = (e) => {
e.preventDefault();
this.setState({
showOutput: true
});
};
setStudy = (value) => {
this.setState({
study: value
});
};
setSchool = (value) => {
this.setState({
school: value
});
};
render() {
return (
<>
<h1 className="title">Education</h1>
<div id="content">
{this.state.showOutput && (
<>
<div>{`school: ${this.state.school}`}</div>
<div>{`study: ${this.state.study}`}</div>
</>
)}
<form>
<label for="school">School</label>
<input
id="school-form"
className="form-row"
type="text"
name="school"
onChange={(e) => this.setSchool(e.target.value)}
/>
<label for="study">Field of Study</label>
<input
id="study-form"
className="form-row"
type="text"
name="study"
onChange={(e) => this.setStudy(e.target.value)}
/>
<button onClick={this.onSubmit} className="save">
Save
</button>
<button className="cancel">Cancel</button>
</form>
)
</div>
</>
);
}
}
export default App;
I have also added 2 functions to set state and a condition render based on showOutput.
You don't append things to the DOM in react like you do in vanilla. You want to conditionally render elements.
Make a new element to display the data, and render it only if you have the data. (Conditional rendering is done with && operator)
{this.state.schoolForm && this.state.studyform && <div>
<p>School: {this.state.schoolForm}</p>
<p>Field of Study: {this.state.studyForm}</p>
</div>}
The schoolForm and studyForm should be component state variables. If you only have them as variables in your onSubmit, the data will be lost after the function call ends. Your onSubmit function should only set the state, and then you access your state variables to use the data.
Do not use document.getElementById. You don't want to use the 'document' object with react (Almost never).
You can access the element's value directly using the event object which is automatically passed by onSubmit.
handleSubmit = (event) => {
event.preventDefault();
console.log(event.target.school.value)
console.log(event.target.study.value)
}
When I submit a value to the input field it should push the value into an array, its fine as it perfectly logs the input value however it does not update the list array in state (this.state.list). It works the first time as it shows in an LI on the webpage however the value does not show in the console. However when I hit enter twice it works though.
import React from 'react';
class App extends React.Component{
constructor(props){
super(props )
this.state = {
input: "",
list: []
}
}
handleChange = (e) => {
this.setState({
input : e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault()
console.log('A value was submitted into the input field --- ' + this.state.input)
const input = this.state.input
console.log(input)
let tasks = []
tasks.push(input)
this.setState({list: [...this.state.list, tasks]})
console.log(this.state.list);
}
render(){
return(
<div className="App">
Hello world
<form onSubmit={this.handleSubmit}>
<input onChange={this.handleChange} value={this.state.value} type="text"></input>
<li>
{this.state.list}
</li>
</form>
</div>
)
}
}
export default App;
i can spot two issues here based on my understanding
I think you are trying to push an entire array to the list when you use
let tasks = []
tasks.push(input)
this.setState({list: [...this.state.list, tasks]})
So each list element has a full array inside it
Secondly you are trying to view an entire array inside a list item
<li>
{this.state.list}
</li>
which should look like this:
<ul>
{this.state.list.map((item, index) => {
return <li key={index}>{item}</li>;
})}
</ul>
Try this:
import React from "react";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
input: "",
list: []
};
}
handleChange = e => {
this.setState({
input: e.target.value
});
};
handleSubmit = e => {
e.preventDefault();
console.log("A value was submitted into the input field", this.state.input);
const input = this.state.input;
this.setState(
{ list: [...this.state.list, input] },
() = > {console.log("state.list", this.state.list)}
);
};
render() {
return (
<div className="App">
Hello world
<form onSubmit={this.handleSubmit}>
<input
onChange={this.handleChange}
value={this.state.value}
type="text"
/>
<ul>
{this.state.list.map((item, index) => {
return <li key={index}>{item}</li>;
})}
</ul>
</form>
</div>
);
}
}
export default App;
React state updates are asynchronous, so the console.log(this.state.list); will only display the state of the current render cycle. All state updates during a render cycle are queued up and batch processed during reconciliation before the next render/commit phases run. If you use the setState callback you can log the updated state.
this.setState(
{
list: [...this.state.list, tasks]
},
() => console.log(this.state.list)
);
The reason it works upon the second press of enter is you've submitted the form again, reset state and you log the result of the previous state update.
That's because setState gets invoked asynchronously.
If you want to do something once setState is invoked, you can pass a callback.
this.setState({list: [...this.state.list, tasks]}, () => { console.log(this.state.list); })
It does not change immediately: use callback of state change to log:
this.setState({list: [...this.state.list, tasks]},
()=>console.log(this.state.list))
I am new to React and need some help to my specific situation. I have a top-level app.js where I render
export default class Page extends React.Component {
constructor(props) {
super(props);
this.state = {
currentGuess: '',
historicGuess: '',
result: ''
};
}
handleCurrentGuess(event) {
console.log(event)
this.setState({currentGuess: event.target.value})
}
handleSend() {
console.log(this.state.currentGuess)
}
render() {
return (
<div className="wrapper">
<Header />
<Logic handleCurrentGuess={this.handleCurrentGuess}/>
<Result />
</div>
)
}
}
The component has to be stateful, and I enter the currentGuess value into state.
The <Logic /> looks like this:
export default function Logic(props) {
console.log(props)
return (
<div className="logic">
<form>
<input type="text" onChange={props.handleCurrentGuess}/>
<button onClick={(e) => {
e.preventDefault()
props.handleSend
}}>Send</button>
</form>
</div>
)
}
The issue now is that I cannot find documentation on how to pass both pass the function on to the AND get returned a value from the input. Most docs show onChange via the input directly, but I want to fetch the value ONLY when someone clicks on the submit button (or hits enter). So,
how do I pass the correct function to the child, and how do I get the text value back on button press within the Logic component?
If you want to console.log the state right now (for testing purposes obviously) here is the two problems with your code.
First, you are not passing your handleSend function as a prop to Logic component.
Second, on your button, you are not invoking this handleSend function in your onClick handler.
Here is a working example.
const Logic = props => (
<div className="logic">
<form>
<input type="text" onChange={props.handleCurrentGuess} />
<button onClick={props.handleSend}>Send</button>
</form>
</div>
);
class Page extends React.Component {
state = {
currentGuess: '',
historicGuess: '',
result: ''
};
handleCurrentGuess = event =>
this.setState({ currentGuess: event.target.value })
handleSend = (e) => {
e.preventDefault();
console.log(this.state.currentGuess)
}
render() {
return (
<div className="wrapper">
<Logic
handleCurrentGuess={this.handleCurrentGuess}
handleSend={this.handleSend} />
</div>
)
}
}
ReactDOM.render(<Page />, 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>
I slightly changed the code. Use some arrow functions so no need to .bind them, remove the unnecessary constructor, use class-fields, etc. I also used the function reference for onClick in the button.
I have a component in which I'm trying to populate a <Select /> component with some options from my props. When the component mounts, I set the state of jobNumbers to an empty array.
I have two dropdowns in which one's values, depend on the other's selected value. When the value is selected, I run an onChange function to populate the second dropdown. The only problem is when I do this.setState({jobNumbers: [...array elements...]}), the state still shows the jobNumbers array to be empty. The function that actually does the state setting is getCustomerOptions().
Here is my component in it's entirety (it's not TOO terribly long)
import React from 'react';
import SelectInput from '../../components/SelectInput';
import LocationSelector from '../../components/LocationSelector';
import { Field } from 'redux-form/immutable';
import Select from 'react-select';
import 'react-select/dist/react-select.css';
class InputCurrentCustomerLocation extends React.Component {
constructor(props) {
super(props);
this.state = {
jobNumbers: [],
};
this.getCustomerOptions = this.getCustomerOptions.bind(this);
this.onChange = this.onChange.bind(this);
}
componentWillMount() {
if (this.props.active) {
this.props.input.onChange(this.props.active);
}
}
onChange(event) {
if (this.props.input.onChange) {
this.props.input.onChange(event.value); // <-- To be aligned with how redux-form publishes its CHANGE action payload. The event received is an object with 2 keys: "value" and "label"
// Fetch our Locations for this customer
this.props.handleCustomerLocationFetch(event.value);
this.getCustomerOptions(event);
}
}
getCustomerOptions(event) {
let options = [];
if(event) {
this.props.options.forEach((option, index) => {
if(option.value === event.value) {
console.log('props options', this.state);
this.setState({ jobNumbers: this.props.options[index] });
console.log('state options', this.state);
}
})
}
}
render() {
const { meta } = this.props;
return (
<div>
<Select
options={this.props.options} // <-- Receive options from the form
{...this.props}
value={this.props.input.value || ''}
// onBlur={() => this.props.input.onBlur(this.props.input.value)}
onChange={this.onChange.bind(this)}
clearable={false}
/>
{meta.error && <div className="form-error">{meta.error}</div>}
{this.props.activeLocations ? false : (
<div>
<div>
<p> Select a location </p>
<Field
name="locations"
component={props =>
<LocationSelector
{...props}
// active={this.props.activeLocations}
locations={this.props.locations}
/>
}
/>
</div>
<div>
<p> Select a job number</p>
<Field
name="jobNumber"
component={props =>
<Select
options={this.state.jobNumbers}
value={this.props.input.value || ''}
onChange={this.onChange.bind(this)}
clearable={false}
/>
}
/>
</div>
</div>
)}
</div>
);
}
}
export default InputCurrentCustomerLocation;
I'm relatively new to React and redux and I'm not entirely sure why this is happening. Shouldn't the state be populated?
this.setState({ jobNumbers: this.props.options[index] });
is async.
so when you do a console log on the state after setState, the state still won't change.
you should check the value on componentDidUpdate(prevProps, prevState) , and print it there.
I am trying to pass data from one component to another. but it has no parent child relation and it is independent from each other. i am able to set the state but problem is after clicking enter my text data get cleared. not sure why,
export class EmpSearch extends React.Component {
// Not needed anymore as state going to Redux and not local component state
/*
constructor(props) {
super(props);
this.state = {
Empnumber: ''
};
}
*/
EmpSearch = (e) => {
if (e.key === 'Enter') {
browserHistory.push('/Emp/' + e.target.value);
}
}
updateEmpNumber(e) {
this.props.dispatch({
type: 'UPDATE_EMP_NUMBER',
payload: e.target.value
});
}
render() {
return (
<div className="row">
<form>
<div className="form-group">
<label htmlFor="Empnumber">Emp Number</label>
<input type="text" className="form-control" id="Empnumber" placeholder="Emp Number" value={this.props.Empnumber} onChange={this.updateEmpNumber.bind(this)} onKeyPress={this.EmpSearch}/>
</div>
</form>
</div>
);
}
}
function mapStateToProps(state){
return {
Empnumber: state.Empnumber
}
}
export default connect(mapStateToProps)(EmpSearch);
browserHistory.push('/Emp/' + e.target.value); is probably causing your component to be unmounted and remounted.
You say the value is stored in the redux state, but I can't see where you update the redux state.