I've created a class based component that renders an input field. I need the global state to update while the user types into the input. The issue is that the global state is always one step (render?) behind from what's actually in the input field. For example, when I write “winners” in the input, the state is “winner” instead. How can I fix this?
Component
class TeamCardT1 extends Component {
constructor(props) {
super(props);
// local state
this.state = {
team_one_name: "Team One",
};
// bind events
this.handleName = this.handleName.bind(this);
};
handleName = e => {
this.setState({
...this.state,
team_one_name: e.target.value
});
this.props.handleSubmit(this.state);
};
render() {
const { team_one_name } = this.state;
return (
<>
<div className="teamCard_container">
<input
type="text"
id="team_one_name"
name="team_one_name"
value={team_one_name}
onChange={this.handleName}
maxLength="35"
minLength="2"
className="teamCard_teamName" />
<PlayersCardT1 />
<ScoreCard />
</div>
</>
)
};
}
index.js for the component
const mapStateToProps = ({ team_one_name }) => {
return {
team_one_name,
};
};
// Dispatch
const mapDispatchToProps = dispatch => {
return {
handleSubmit: (data) => { dispatch(updateTeamOneName(data)) }
};
};
export default connect(mapStateToProps, mapDispatchToProps)(TeamCardT1);
You handleSubmit with the previous state, change to the current value.
handleName = e => {
this.setState({ team_one_name: e.target.value });
this.props.handleSubmit(e.target.value);
};
Notice that you already have a shallow merge with setState so you don't need to destruct this.state.
state is one step behind because you should call the prop function as a setState callback by this way the prop function will call just after the state set.
handleName = e => {
this.setState({
...this.state,
team_one_name: e.target.value
}, () => {
this.props.handleSubmit({value: e.target.value});
});
};
Related
I have a situation where I am passing params from Child Class to Parent on callback. Even before the Parent call executes the part where the state update has to happen, the state is already updated before that part is executed. For example, in the code below, the first console.log statement already has an updated value of state variable x set to the new one in the Parent class. This is very surprising.
export default class Parent extends Component {
constructor(props) {
super(props);
this.state = {
...this.props,
};
this.handleCallback = this.handleCallback.bind(this);
}
handleCallback = (x) => {
console.log('1 CALLBACK in Parent', this.state);
this.setState({x});
console.log('2 CALLBACK in Parent', this.state);
}
render() {
return (
<Child onSubmit={this.handleCallback}>
)
}
}
export default class Child extends Component {
constructor() {
super();
this.state = {
x: {
y: 1,
z: 2
}
};
}
handleInputChange = event => {
const {name, value} = event.target;
this.setState((prevState) => ({
x: {
...prevState.x,
[name]: value
}
}));
};
handleSubmit = event => {
this.props.onSubmit(this.state.x);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
.
// update to {this.state.x} happens here in input form
<input className='inputText' type='text' name='y' placeholder={this.state.x.y} onChange={this.handleInputChange} />
<input className='inputText' type='text' name='z' placeholder={this.state.x.z} onChange={this.handleInputChange} />
.
)
}
}
I am wondering what could be going on wrong here and what is the best practice here ?
i have an application like
export default function App(props) {
return <>
<FooList />
<hr/>
<CreateFoo />
</>;
}
my FooList is a class like
export default class FooList extends React.Component {
constructor(props) {
super(props);
this.state = {
foos = []
};
}
componentDidMount() {
Axios.get('/getFooListPath').then(res=>{
this.setState({foos=> res.data});
}).catch(err=>{
console.log(err);
});
}
render() {
const mapedFoos = this.state.foos.map((foo, i)=><li key={i}>foo</li>);
return <ul>mapedFoos</ul>;
}
}
and CreateFoo is like
export default function CreateFoo(props) {
const [txt, setTxt] = useState("");
const handleChange = (event) => setTxt(event.target.value);
const handleChange = () => {
Axios.post("/createFooPath", txt).then(res=>{
// TODO : maybe calling some method here
}).catch(err=>{
console.log(err);
});
};
return <>
<input type="text" onChnage={handleChange} />
<button onClick={handleClick} >create</button>
</>;
}
problem is that i want to re-render FooList after i successfully created foo.
i already thought about moving foos to parent App, and send it to FooList as props. but i want to make FooList independent from parent, so wherever i want to use it, it show me current foos on database
I am trying to track a form in this.state with event handlers on the inputs, but for some reason this.state is being reset back to its default state when the event handler tries to update it. This is an example of what the Component looks like.
class ExampleReport extends React.Component {
constructor(props) {
super(props)
this.state = {
reportDetails: null,
report: this.props.report,
form: {}
}
this.textInputHandler = this.textInputHandler.bind(this)
}
textInputHandler(e) {
var reportForm = this.state.form;
var target = e.target;
var name = target.className;
var value = target.value;
reportForm[name] = value;
this.setState({ form: reportForm })
}
render(){
return(
<form>
<input className="example" type="text" onChange={(e) => {this.textInputHandler(e)}} />
</form>
)
}
}
Before textInputHandler is called, this.state has an object and array stored in it, but once setState is called they are reset back to the default in the constructor. On subsequent updates to the text input this.state.form persists but everything else is reset. How can I prevent this from happening?
UPDATE
After trying some of the solutions suggested below I went back and logged this.state at just about every possible point and found that it is reset even before setState() is being called in the input handler.
You forget about input value and update state by ref (make mutation).
class ExampleReport extends React.Component {
constructor(props) {
super(props)
this.state = {
reportDetails: null,
report: this.props.report,
form: { example: '', },
}
this.textInputHandler = this.textInputHandler.bind(this)
}
textInputHandler(e) {
const { target: { name, value } } = e;
this.setState(prevState => ({
...prevState,
form: {
...prevState.form,
[name]: value,
},
}));
}
render() {
const { form: { example } } = this.state;
return (
<form>
<input
className="example"
type="text"
name="example"
value={example}
onChange={this.textInputHandler}
/>
</form>
)
}
}
Once way to solve this would be like below, spreading reportForm in a new object
textInputHandler(e) {
var reportForm = this.state.form;
var target = e.target;
var name = target.className;
var value = target.value;
reportForm[name] = value;
this.setState({ form: { ...reportForm } }) // <----------------------
}
However you may want to use more declarative solution as provided by Kirill but without unnecessary state changes
textInputHandler(e) {
const { className: name, value } = e.target;
this.setState(prevState => { form: { ...prevState.form, [name]: value } }) // <----------------------
}
try changing your onChange to this
onChange={this.textInputHandler}
like how they have it in the react documentation
EX)
<input type="text" value={this.state.value} onChange={this.handleChange} />
then do the spreading stuff
don't add, e.preventDefault because that only works for submitting
Look at Krill answer he has everything you need on there
compare it to yours from the top, you have a lot of stuff missing
I'm having trouble figuring out how to pass event.target.value into my setState below.
I was thinking that the first event (= event =) would get passed in closure and be available to the return, but I get undefined when I debug that.
What is the right away to get to event.target.value?
class InputElement1CC extends React.Component {
state = {
inputText: "",
historyList: []
};
handleChange = event => {
this.setState(previousState => {
return {
inputText: event.target.value,
historyList: [...previousState.historyList, event.target.value]
};
});
};
render() {
return (
<div>
home
<h1>InputElement1CC - Class Component</h1>
<input placeholder="Enter Some Text" onChange={this.handleChange} />
<br />
{this.state.inputText}
<hr />
<br />
<ul>
{this.state.historyList.map(rec => {
return <div>{rec}</div>;
})}
</ul>
</div>
);
}
}
export default InputElement1CC;
Set the value to a variable and use that variable. Something like this:
handleChange = event => {
const value = event.target.value;
this.setState(
state => ({
inputText: value,
historyList: [...state.historyList, value]
}),
() => console.log("state", this.state)
);
};
I've read this post: React setState not Updating Immediately
and realized that setState is async and may require a second arg as a function to deal with the new state.
Now I have a checkbox
class CheckBox extends Component {
constructor() {
super();
this.state = {
isChecked: false,
checkedList: []
};
this.handleChecked = this.handleChecked.bind(this);
}
handleChecked () {
this.setState({isChecked: !this.state.isChecked}, this.props.handler(this.props.txt));
}
render () {
return (
<div>
<input type="checkbox" onChange={this.handleChecked} />
{` ${this.props.txt}`}
</div>
)
}
}
And is being used by another app
class AppList extends Component {
constructor() {
super();
this.state = {
checked: [],
apps: []
};
this.handleChecked = this.handleChecked.bind(this);
this.handleDeleteKey = this.handleDeleteKey.bind(this);
}
handleChecked(client_id) {
if (!this.state.checked.includes(client_id)) {
let new_apps = this.state.apps;
if (new_apps.includes(client_id)) {
new_apps = new_apps.filter(m => {
return (m !== client_id);
});
} else {
new_apps.push(client_id);
}
console.log('new apps', new_apps);
this.setState({apps: new_apps});
// this.setState({checked: [...checked_key, client_id]});
console.log(this.state);
}
}
render () {
const apps = this.props.apps.map((app) =>
<CheckBox key={app.client_id} txt={app.client_id} handler={this.handleChecked}/>
);
return (
<div>
<h4>Client Key List:</h4>
{this.props.apps.length > 0 ? <ul>{apps}</ul> : <p>No Key</p>}
</div>
);
}
}
So every time the checkbox status changes, I update the this.state.apps in AppList
when I console.log new_apps, everything works accordingly, but console.log(this.state) shows that the state is not updated immediately, which is expected. What I need to know is how I can ensure the state is updated when I need to do further actions (like register all these selected strings or something)
setState enables you to make a callback function after you set the state so you can get the real state
this.setState({stateYouWant}, () => console.log(this.state.stateYouWant))
in your case:
this.setState({apps: new_apps}, () => console.log(this.state))
The others have the right answer regarding the setState callback, but I would also suggest making CheckBox stateless and pass isChecked from MyApp as a prop. This way you're only keeping one record of whether the item is checked, and don't need to synchronise between the two.
Actually there shouldn't be two states keeping the same thing. Instead, the checkbox should be stateless, the state should only be kept at the AppList and then passed down:
const CheckBox = ({ text, checked, onChange }) =>
(<span><input type="checkbox" checked={checked} onChange={() => onChange(text)} />{text}</span>);
class AppList extends React.Component {
constructor() {
super();
this.state = {
apps: [
{name: "One", checked: false },
{ name: "Two", checked: false }
],
};
}
onChange(app) {
this.setState(
previous => ({
apps: previous.apps.map(({ name, checked }) => ({ name, checked: checked !== (name === app) })),
}),
() => console.log(this.state)
);
}
render() {
return <div>
{this.state.apps.map(({ name, checked }) => (<CheckBox text={name} checked={checked} onChange={this.onChange.bind(this)} />))}
</div>;
}
}
ReactDOM.render(<AppList />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>