Storing input value in array does not work (react) - reactjs

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))

Related

React + Firebase – Update the increment state of Like/Clap button to Firebase

Codesandbox: https://codesandbox.io/s/github/adamschwarcz/react-firebase-app
I am really new to react and firebase and I followed this tutorial to come up with this app (full project – github link here) – it's an "Add your Wish app"
My problem is that I cannot store clap count on each post to my firebase – this component is called LikeButton.js.
I have been trying to add some similar firebase code (handleChange, handleSubmit, componentDidMount... etc.. etc..) as I learned in the tutorial to LikeButton.js to store the total amount of counts in firebase each time the button is clicked and the amount of claps incremented by +1.
Simply what I want – everytime the clap button is clicked and the initial ('0') state of count is incremented to +1 the current count is going to be updated into the database.
Just cannot come up with solution, can somebody please help?
My LikeButton.js code without any firebase:
import React, { Component } from 'react'
import firebase from '../../firebase.js';
import { makeStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import './Like.css';
class LikeButton extends Component {
state = {
count: 0,
}
incrementLike = () => {
let newCount = this.state.count + 1
this.setState({
count: newCount
})
console.log(this.state.count);
}
render() {
return(
<div class="counter">
<Button type="submit" color="primary" onChange={this.handleCount} onClick={this.incrementLike}>{this.state.count} 👏</Button>
</div>
)
}
}
export default LikeButton
My Add.js code with firebase:
import React, { Component } from 'react';
import firebase from '../../firebase.js';
import Button from '#material-ui/core/Button';
import TextField from '#material-ui/core/TextField';
import FadeIn from "react-fade-in";
import Placeholder from '../Placeholder/Placeholder.js';
import LikeButton from '../Like/Like.js'
import './Add.css';
class Add extends Component {
constructor() {
super();
this.state = {
loading: true,
currentItem: '',
username: '',
items: []
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
this.setState({
[e.target.name]: e.target.value
});
}
handleSubmit(e) {
e.preventDefault();
const itemsRef = firebase.database().ref('items');
const item = {
title: this.state.currentItem,
user: this.state.username
}
itemsRef.push(item);
this.setState({
currentItem: '',
username: ''
});
}
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/posts")
.then(response => response.json())
.then(json => {
setTimeout(() => this.setState({ loading: false }), 1500);
});
const itemsRef = firebase.database().ref('items');
itemsRef.on('value', (snapshot) => {
let items = snapshot.val();
let newState = [];
for (let item in items) {
newState.push({
id: item,
title: items[item].title,
user: items[item].user
});
}
this.setState({
items: newState
});
});
}
removeItem(itemId) {
const itemRef = firebase.database().ref(`/items/${itemId}`);
itemRef.remove();
}
render() {
return (
<div className="container">
<div className="wrap">
<section className="add-item">
<h1>Napíš svoj wish</h1>
<h3>Možno prilepíš sebe, možno posunieš firmu.</h3>
<form onSubmit={this.handleSubmit}>
<TextField
id="filled-required"
label="Meno"
name="username"
variant="filled"
value={this.state.username}
onChange={this.handleChange}
/>
<TextField
required
id="standard-multiline-flexible"
label="Tvoje prianie"
name="currentItem"
variant="filled"
multiline
rows="6"
rowsMax="8"
value={this.state.currentItem}
onChange={this.handleChange}
/>
<Button
type="submit"
variant="contained"
color="primary">
Poslať wish
</Button>
</form>
</section>
<section className='items-list'>
<div className="item">
<div>
{this.state.items.map((item) => {
return (
<div>
{this.state.loading ? (
<>
<FadeIn>
<Placeholder />
</FadeIn>
</>
) : (
<div className="wish" key={item.id}>
<FadeIn>
<h2>{item.title}</h2>
<div className="name">
<p>poslal <span>{item.user}</span></p>
<LikeButton />
</div>
</FadeIn>
</div>
)}
</div>
)
})}
</div>
</div>
</section>
</div>
</div>
);
}
}
export default Add
First of all, you need to tell the LikeComponent which Wish it will be updating, and you will also need to be able to access the clapCount of the wish from the LikeComponent. This can be done easily using props. You should re-configure LikeComponent to accept a prop similar to wish, which would be the wish that you are displaying and modifying.
So, this line in Add.js
<LikeButton />
would instead look like <LikeButton wish={item} />. This way, your LikeComponent can access the item/wish.
Next, in the LikeComponent, you need to remove the local state and instead use the clap count stored in Firebase. Luckily, since you're passing the wish via a prop, you can simply refactor the LikeComponent to look like this:
class LikeButton extends Component {
incrementLike = () => {
// TODO: Implement clap incrementation via Firebase updates
}
render() {
return(
<div class="counter">
<Button type="submit" color="primary" onClick={this.incrementLike}>{this.props.wish.clapCount} 👏</Button>
</div>
)
}
}
Next, we need to actually implement incrementLike. Luckily, since we are getting the wish item passed to us via the wish prop, we can easily update it like so:
incrementLike = () => {
// get a reference to the item we will be overwriting
const wishRef = firebase.database().ref(`/items/${this.props.wish.id}`);
// get the current value of the item in the database
wishRef.once('value')
.then(snapshot => {
// get the value of the item. NOTE: this is unsafe if the item
// does not exist
let updatedWish = snapshot.val();
// update the item's desired property to the desired value
updatedWish.clapCount = updatedWish.clapCount + 1;
// replace the item with `wish.id` with the `updatedWish`
wishRef.set(updatedWish);
});
}
While this should work with only a few tweaks, I'm sure there's a better way to do it. You might even be able to avoid the call to once('value') since you're passing wish as a prop to LikeComponent. You should play around with it.
However, I strongly encourage you to explore migrating to Firebase Cloud Firestore. It's API is way more straightforward (in my opinion) than Realtime Database.

How do I render two inputs into one list in Reactjs

I'm trying to teach myself how to code and created a little todo app. In the rendering of each todo input I have the element and then a checkbox to click for it to be removed. I tried to create a separate input to give the amount of time it will take for each item to be created. When I tried to link that up to my rendering method, nothing renders and I have zero error messages.
import React from 'react';
class InputBar extends React.Component {
state={ todo: '',
time: null
}
onInputSubmit = e =>{
e.preventDefault();
this.props.todoSubmit(this.state.todo)
this.props.timeSubmit(this.state.time)
this.setState({
todo: '',
time: this.state.time
})
}
render() {
return (
<div className="input-group mb-3">
<form onSubmit={ this.onInputSubmit } >
<label>Input Todo</label>
<div className='input-control'>
<input
type='text'
className="form-control"
aria-label="Sizing example input"
aria-describedby="inputGroup-sizing-default"
value={this.state.todo}
onChange={e => this.setState({
todo: e.target.value
})}
/>
<input
type='number'
required
className='input-control'
defaultValue={0}
value={this.state.time}
placeholder='How long will it take?'
onChange={e => this.setState({
time: e.target.value
})} />
</div>
</form>
</div>
)
}
}; export default InputBar
import React from 'react';
import InputBar from './inputbar';
class List extends React.Component {
state = {
list: [],
nextId: 1
};
componentDidMount() {
const list = JSON.parse( localStorage.getItem( "list" ) );
this.setState( { list } );
}
addToList = (todo, time, list) => {
this.setState({
list: [
{
name: todo,
text: time,
id: this.state.nextId
},
...this.state.list,
],
nextId: this.state.nextId + 1
},
() => {
localStorage.setItem("list", JSON.stringify(this.state.list));
});
}
removeFromList = (id) => {
this.setState({
list: this.state.list.filter(entry => entry.id !== id )
},
() => {
localStorage.setItem("list", JSON.stringify(this.state.list));
}
);
}
renderList = () => {
return this.state.list.map((element) => {
return (
<div>
<li>
{element.name}
<input
style={{marginLeft: '15px'}}
type='checkbox'
onClick={()=> this.removeFromList(element.id)}
/>
</li>
</div>
)
})
}
render() {
console.log(this.state.todo, this.state.time)
return (
<div>
<InputBar
todoSubmit={this.addToList}
timeSubmit={this.addToList}
/>
<ul>
{ this.renderList() }
</ul>
</div>
)
}
};
export default List;
//this is then send to imported an app component to be rendered
Hi & welcome to Stack Overflow, Elias.
You pass two handlers to your InputBar component that both resolve to the addToList handler defined in your list component. However, when you call these handlers, the arguments do not match what addToList is expecting, which is a list, a todo and a time.
You obviously don't need a list argument (it's never used in addToList as you manage the list present in that component state's anyway, which is fine), so list can be removed.
And in my opinion, you do not need 2 handlers (one for the todo, one for the time value). One handler that adds both todo AND time would be better (after all, the idea is to submit a todo as a whole object) and would line up with what addToList would expect.
In summary, here are the changes I suggest:
In inputbar.js:
onInputSubmit = e => {
e.preventDefault();
const { todo, time } = this.state
this.props.handleSubmit(todo, time)
this.setState({
todo: '',
time: this.state.time
})
}
In your List component:
addToList = (todo, time) => {
// just removed the unnecessary 'list' param
// actual code left untouched
}
// other code
render() {
console.log(this.state.todo, this.state.time)
return (
<div>
<InputBar handleSubmit={this.addToList} />
<ul>
{ this.renderList() }
</ul>
</div>
)
}

after deletion the UI re renders and getting all the check boxes

The checkboxes and delete function working fine.. after the deletion the UI re renders and again im getting all the check boxes .. The marked checkboxes should not come again. Please suggest me how to stop this. I dont want the checked boxes in the UI after the delete button is clicked .
import React,{Component} from 'react';
import './UserList.css';
import jsonData from '../../people_(5).json';
const list = jsonData.People
const list2 = list.map(v => {
return {
...v,
value: false
}
})
list2.forEach((obj,index)=>obj.id=index)
class UserList extends Component{
state={
userList: list2
};
handleChange = e => {
console.log(this.state.userList)
const id = e.target.id;
this.setState(prevState => {
return {
userList: prevState.userList.map(
li => (li.id === +id ? {...li,
value: !li.value
} : li)
)
};
});
};
handleClick = () => {
console.log(this.state.userList)
this.setState(prevState => {
return {
userList: prevState.userList.filter(li => !li.value)
};
});
};
render(){
return(
<form className="pa4">
<fieldset id="people" className="del bn">
<legend className="fw7 mb2">People<button onClick={this.handleClick}>Delete</button></legend>
{this.state.userList.map(e => (
<div key={e.id}>
<input
type="checkbox"
id={e.id}
checked={e.value}
onChange={this.handleChange}
/>
<label htmlFor={e.name}>{e.name}</label>
</div>
))}
</fieldset>
</form>
);
}
}
export default UserList
I think the position that you put your button isn't correct.Every time click the button inside the form element will cause the page re-render,maybe you can change the button position like this
<div>
<form className="pa4">
<fieldset id="people" className="del bn">
...
</fieldset>
</form>
<button onClick={this.handleClick}>delete</button>
</div>
After the page re-render,people will be set to the initial value, so the handleChange actually doesn't work, then you can't delete the corresponding input element.
You Are not Updating the right values at the right place. Please check your logic in below code. You have un controlled checkboxes and also you are making changes to current state from prevState instead filter data by checkbox states
handleClick = () => {
console.log(this.state.userList)
this.setState(prevState => {
return {
userList: prevState.userList.filter(li => !li.value)
};
});
};

Cannot control state properly

I have 3 components: App, Map and ListPlaces. In ListPlaces component, when a user types something in the input element, I want to change the state(markers's state) in App.js to show only related markers on the map.
Edit: When I edit my typo, the error was disappeared. However, I think the logic is still wrong. Because when I write something in the input element, markers array would be 0 immediately. And of course, all markers are disappeared.
More Explanation:
After componentDidMount, my markers array holds 7 items. And Map component takes this markers array and render markers on the map. However, I need to control my markers from ListPlaces component according to value of input element. So I put this: onChange={e => {this.updateQuery(e.target.value); changeMarkersHandler(e.target.value)}} in onChange attribute of input element. (Omit the this.updateQuery, for now, you can focus on only changeMarkersHandler).
This changeMarkersHandler runs changeMarkers function in App.js, but I don't know why my marker arrays would be 0 immediately while changeMarkers function is working.
Note: I am using react-google-maps and I've omitted some code blocks which aren't related to question.
App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
places: [],
markers: [],
markerID: -1,
newMarkers: []
};
this.changeMarkers = this.changeMarkers.bind(this);
}
componentDidMount() {
fetch("api_url")
.then(response => response.json())
.then(data => {
this.setState({
places: data.response.venues,
markers: data.response.venues
});
})
.catch(error => {
console.log("Someting went wrong ", error);
});
}
changeMarkers(value) {
const newMarkers = this.state.markers.filter(
place => place.name === value
);
this.setState({
newMarkers : newMarkers,
markers: newMarkers
})
}
render() {
return (
<div className="App">
<Map role="application"
places={this.state.places}
markers={this.state.markers}
openInfoHandler={this.openInfo}
closeInfoHandler={this.closeInfo}
markerID={this.state.markerID}
googleMapURL="url_here" />
<ListPlaces changeMarkersHandler={this.changeMarkers} />
</div>
);
}
}
ListPlaces.js
import React, { Component } from "react";
import escapeRegExp from "escape-string-regexp";
class ListPlaces extends Component {
state = {
searchQuery: ""
};
updateQuery = query => {
this.setState({ searchQuery: query});
};
render() {
const { toggleListHandler, locations, openInfoHandler, changeMarkersHandler} = this.props;
let showLocations;
if (this.state.searchQuery) {
const match = new RegExp(escapeRegExp(this.state.searchQuery), "i");
showLocations = locations.filter(location =>match.test(location.name));
} else {
showLocations = locations;
}
return (
<div>
<aside>
<h2>Restaurants</h2>
<nav>
<div className="search-area">
<input
className="search-input"
type="text"
placeholder="Search Restaurant"
value={this.state.searchQuery}
onChange={e => {this.updateQuery(e.target.value); changeMarkersHandler(e.target.value)}}
/>
</div>
<ul>
{showLocations.map(location => {
return (
<li
key={location.id}
onClick={e =>
openInfoHandler(e, location.id)
}
>
{location.name}
</li>
);
})}
</ul>
</nav>
<p>some text</p>
</aside>
<a
onClick={toggleListHandler}
id="nav-toggle"
className="position"
>
<span />
</a>
</div>
);
}
}
export default ListPlaces;
You have a typo in you constructor.
this.changeMarkers(this.changeMarkers.bind(this));
should be
this.changeMarkers = this.changeMarkers.bind(this);

React updating state onChange strange behaviour

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()} />

Resources