Updated state is not passed as props to component after setState() - javascript

I have a main react component called 'App' which contain user input data in its state. The state is updated with setState() every time user enter new data. Then the state is passed as props to another component called 'IncomeList' which render the data on screen. However the IncomeList component is not getting updated state after user input some data.
class App extends React.Component {
constructor(props) {
super(props);
this.addData = this.addData.bind(this);
this.state = {
expenses: [],
income: [],
}
}
addData(data) {
if (data.type == 'income') {
this.setState((prevState) => {
income: prevState.income.push(data);
}, console.log(this.state.income));
} else if (data.type == 'expense') {
this.setState((prevState) => {
expenses: prevState.expenses.push(data);
})
}
}
render() {
return (
<div>
<UserInput addData={this.addData} />
<IncomeList income={this.state.income} />
</div>
);
}
}
// UserInput component which contain a form
class UserInput extends React.Component {
constructor(props) {
super(props);
this.addDataLocal = this.addDataLocal.bind(this);
}
addDataLocal(e) {
e.preventDefault();
const data = {
type: e.target.elements.type.value,
description: e.target.elements.description.value,
amount: e.target.elements.amount.value
}
this.props.addData(data);
}
render() {
return (
<div>
<form onSubmit={this.addDataLocal}>
<select name="type" id="input-type" name="type">
<option value="income">Income</option>
<option value="expense">Expense</option>
</select>
<input type="text" placeholder="decription..." name="description"/>
<input type="number" placeholder="amount..." name="amount"/>
<input type="submit" value="Add"/>
</form>
</div>
)
}
}
class IncomeList extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
{
this.props.income.map((item) => {
return (
<IncomeListItem key={item.amount} item={item}/>
)
})
}
</div>
)
}
}

You don't return anything from this.setState. You need return an object to be merged with your current state.
addData(data) {
const key = data.type === 'income' ? 'income' : 'expenses';
this.setState(prevState => ({
// with Computed Property Names we can make our
// code concise and avoid conditionals
[key]: [...prevState[key], data]
}), console.log(this.state[key]));
}

your addData should be like this
addData(data) {
if (data.type == 'income') {
let income=[...this.state.income];
income.push(data);
this.setState({
income:income
})
} else if (data.type == 'expense') {
let expenses=[...this.state.expenses];
expenses.push(data);
this.setState({
expenses:expenses
});
}
}

With #Asaf Aviv input I have created a working fiddle. Hope this will help.
JS Fiddle

Related

React data from Children to Parent, map json

I'm learning ReactJS and I want to map a json in a father component from child search bar. So I got this:
export default class Child extends Component {
constructor(props) {
super(props)
this.state = { data:[], value: '' };
this.handleSubmit = this.handleSubmit.bind(this)
this.handleChange = this.handleChange.bind(this)
}
guardar = (data) => {
this.setState({ data })
this.props.parentCallback({ data })
}
handleChange(e) {
this.setState({ value: e.target.value })
axios.get(`http://localhost:3001/api/search?query=${ e.target.value }`)
.then(( { data } ) => this.guardar(data) )
}
handleSubmit(e) {
e.preventDefault()
}
render() {
return(
<form onSubmit={this.handleSubmit}>
<input type="text"
name='searchbar'
onChange={this.handleChange}/>
</form>
)
}
}
export default class Parent extends Component {
state = {
data: [],
}
handleCallback = (childData) => {
this.setState({
data: childData
})
console.log(this.state.data);
}
render() {
const { data } = this.state
return(
<div>
<SearchBar parentCallback = {this.handleCallback}/>
<ProductCard />
{ [data].map( res => <li key={res.id}>{ res.title }</li>) }
</div>
)
}
}
Here is the result:
I want to map if even if the array is empty, in the console shows me the 50 elements only if I write more than twice in the input and I want them when I reload the page.
Beforehand thank you very much!!

How to get value from this.state. Property of undefined

I'm trying to pass value from one component to another. First one looks like this:
class ListStation extends Component {
constructor(props) {
super(props)
this.state = {
stations: []
}
this.editStation = this.editStation.bind(this);
}
editStation(id) {
this.props.history.push(`/add-station/${id}`);
}
componentDidMount() {
StationService.getStations().then((res) => {
this.setState({ stations: res.data });
})
}
}
render() {
return (
<div>
<tbody>
{this.state.stations.map(
station =>
<tr key={station.id}>
<td>{station.city}</td>
<td>{station.name}</td>
<td>
<button onClick={() => this.editStation(station.id)} className="btn btn-info">Modify</button>
...
</div>
</div>
);
}
}
export default ListStation;
And another looks like this:
import React, { Component } from 'react';
import StationService from '../services/StationService';
class CreateStationComponent extends Component {
constructor(props) {
super(props)
this.state = {
station: {
id: this.props.match.params.id,
city: '',
name: '',
trains: [
{
number: '',
numberOfCarriages: ''
}
]
}
}
this.changeCityHandles = this.changeCityHandles.bind(this);
this.changeNameHandles = this.changeNameHandles.bind(this);
this.saveStation = this.saveStation.bind(this);
}
componentDidMount() {
if (this.state.station[0].id === '_add') {
return;
} else {
StationService.getStationById(this.state.id).then((res) => {
let station = res.data;
this.setState({ name: station[0].name, city: station[0].city })
});
}
console.log(this.state.station.city + 'dfddddd');
}
But when I try to pass value from one component to another I get error: Property of undefined. The response I get from API looks like this:
I'm trying to edit values based on the id taken from the first component but it seems to fail.
if (this.state.station[0].id === '_add') {
return;
}
Have a look at this if statement from your codebase I think you should remove [0] after this.state.station ... this is because station is an object not an Array
Change it to if (this.state.station.id === '_add') {

Why isn't React re-rendering the page after the state is changed?

so I was working on a basic Todo app using React.js and I was wondering why the todo component does not automatically re-render once the state changed (the state contains the list of todos- so adding a new todo would update this array)? It is supposed to re-render the Header and the Todo component of the page with the updated array of todos passed in as props. Here is my code:
import React from 'react';
import './App.css';
class Header extends React.Component {
render() {
let numTodos = this.props.todos.length;
return <h1>{`You have ${numTodos} todos`}</h1>
}
}
class Todos extends React.Component {
render() {
return (
<ul>
{
this.props.todos.map((todo, index) => {
return (<Todo index={index} todo={todo} />)
})
}
</ul>
)
}
}
class Todo extends React.Component {
render() {
return <li key={this.props.index}>{this.props.todo}</li>
}
}
class Form extends React.Component {
constructor(props) {
super(props);
this.addnewTodo = this.addnewTodo.bind(this);
}
addnewTodo = () => {
let inputBox = document.getElementById("input-box");
if (inputBox.value === '') {
return;
}
this.props.handleAdd(inputBox.value);
}
render() {
return (
<div>
<input id="input-box" type="text"></input>
<button type="submit" onClick={this.addnewTodo}>Add</button>
</div>
)
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = { todos: ['task 1', 'task 2', 'task 3']}
this.handleNewTodo = this.handleNewTodo.bind(this);
}
handleNewTodo(todo) {
let tempList = this.state.todos;
tempList.push(todo);
this.setState = { todos: tempList };
}
render() {
return (
<div>
<Header todos={this.state.todos} />
<Todos todos={this.state.todos} />
<Form todos={this.state.todos} handleAdd={this.handleNewTodo} />
</div>
)
}
}
You are not updating the state correctly.
You need to make a copy of the this.state.todos, add the new todo in the copied array and then call this.setState function
handleNewTodo(todo) {
let tempList = [...this.state.todos];
tempList.push(todo);
this.setState({ todos: tempList });
}
Notice that this.setState is a function
You're updating state incorrectly,
handleNewTodo(todo) {
let tempList = [...this.state.todos];
tempList.push(todo);
this.setState({ todos: tempList });
}
This is the correct syntax.

Can't get the actual value of a input in react

I was developing a react component to get a value inside a input and automatically show it in a tag, using refs.
All works fine, but the value shown is the previous value.
I really don't now how to fix this. I using the onChange event in the input to change the state of what will be shown, it is clear that the present value is not taken, but rather the previous value
class Conversor extends Component {
constructor(props){
super(props)
this.state = {
value: null
}
this.output = this.output.bind(this)
}
output(){
console.log(this.state)
this.refs.output.innerHTML = this.state.value
}
render() {
return (
<div>
<h2>{this.state.inputValue}</h2>
<input ref="input" type="text" onChange={() => {this.setState({ value: this.refs.input.value }); this.output()}}/>
<label ref="output"></label>
</div>
);
}
}
If i put the value "Hello World" in the input, the value shown is "Hello Worl", when it's have to be the "Hello World"
You can use event to do this and no need of output() function.
class Conversor extends Component {
constructor(props){
super(props)
this.state = {
value: null
}
}
render() {
return (
<div>
<h2>{this.state.inputValue}</h2>
<input ref="input" type="text" onChange={(e) => {this.setState({ value: e.target.value });}}/>
<label ref="output">{this.state.value}</label>
</div>
);
}
}
The best way to achieve your goal is not using the refs. Here is how you do it
class Conversor extends Component {
constructor(props){
super(props)
this.state = {};
}
handleChange = (e) => {
const { id, value } = e.target;
this.setState({
[id]: value
})
}
render() {
const { name, anotherName } = this.state;
return (
<div>
<h2>{name}</h2>
<input id="name" name="name" type="text" onChange={this.handleChange}/>
<h2>{anotherName}</h2>
<input id="anotherName" name="anotherName" type="text" onChange={this.handleChange}/>
</div>
);
}
}
If you still want to use the refs then do the following,
class Conversor extends Component {
constructor(props){
super(props)
this.state = {
value: null
}
}
output = (e) =>{
this.setState({value: e.target.value }, () => {
this.refs.output.innerHTML = this.state.value
})
}
render() {
return (
<div>
<input ref="input" type="text" onChange={this.output}/>
<label ref="output"></label>
</div>
);
}
}
You don't need to bind your input handler function at all. Instead of doing that, just use an arrow function like _handleInputTextChange . Check this out:
import React, { Component } from 'react';
class InputTextHandler extends Component {
constructor(props){
super(props)
this.state = {
inputValue: ''
}
}
_handleInputTextChange = e => {
const inputValue = e.target.value;
this.setState({inputValue})
console.log(inputValue)
}
render() {
return (
<div>
<input
type="text"
onChange={this._handleInputTextChange}/>
</div>
);
}
}
export default InputTextHandler;
Two things: grab the event value in the onChange method, and pass the this.output method as the second argument to setState which fires after the state has been updated which is not a synchronous operation.
render() {
return (
<div>
<h2>{this.state.inputValue}</h2>
<input ref="input" type="text" onChange={event => {this.setState({ value:event.target.value }, this.output)}}/>
<label ref="output"></label>
</div>
);
}
Try it here!

Automatically render child component when state has been updated in parent component

The parent component Dashboard holds the state for every ListItem I add to my Watchlist. Unfortunately, every time I am adding an Item, it gets added to the DB, but only shows up when I refresh the browser.
class UserDashboard extends React.Component {
state = {
data: []
}
componentWillMount() {
authService.checkAuthentication(this.props);
}
isLoggedIn = () => {
return authService.authenticated()
}
getAllCoins = () => {
//fetches from backend API
}
addWishlist = () => {
this.getAllCoins()
.then(things => {
this.setState({
data: things
})
})
console.log("CHILD WAS CLICKED")
}
componentDidMount() {
this.getAllCoins()
.then(things => {
this.setState({
data: things
})
})
}
render() {
return (
<div className="dashboard">
<h1>HI, WELCOME TO USER DASHBOARD</h1>
<SearchBar
addWishlist={this.addWishlist}
/>
<UserWatchlist
data={this.state.data}
/>
</div>
);
}
}
The User Watchlist:
class UserWatchlist extends React.Component {
constructor(props) {
super(props)
}
// componentDidUpdate(prevProps) {
// if (this.props.data !== prevProps.data) {
// console.log("CURRENT", this.props.data)
// console.log("PREVs", prevProps.data)
// }
// }
render() {
return (
<div>
<h2>These are tssssyou are watching:</h2>
<ul className="coin-watchlist">
{
this.props.data.map((coin, idx) => {
return <ListItem key={idx}
coin={coin.ticker}
price={coin.price}
/>
})
}
</ul>
</div>
)
}
}
The search Bar that shows potential Items to watch over:
class SearchBar extends React.Component {
constructor(props) {
super(props)
this.state = {
coins: [],
searchValue: ""
}
}
searchHandler = e => {
e.preventDefault()
const value = e.target.value
this.setState({
searchValue: value
});
if (value === "") {
this.setState({
coins: []
})
} else {
this.getInfo()
}
}
getInfo = () => {
// Searches the API
}
addWishlist = () => {
this.props.addWishlist();
}
render() {
const {coins, searchValue} = this.state
return (
<div className="coin-search">
<form>
<input
type="text"
className="prompt"
placeholder="Search by ticker symbol"
value={searchValue}
onChange={this.searchHandler}
/>
</form>
<ul className="search-suggestions">
{
coins.filter(searchingFor(searchValue)).map( coin =>
<Currency
coin={coin}
addWishlist={this.addWishlist}
/>
)
}
</ul>
</div>
);
}
}
And the actual Currency that gets clicked to be added:
class Currency extends React.Component {
addToWatchlist = () => {
// POST to backend DB to save
};
fetch("/api/add-coin", settings)
.catch(err => {
return err
})
}
clickHandler = () => {
this.addToWatchlist()
this.props.addWishlist()
}
render() {
return(
<div className="search-results">
<li>
<h3> { this.props.coin.currency } </h3>
<button
className="add-to-list"
onClick={this.clickHandler}
>
+ to Watchlist
</button>
</li>
</div>
)
}
}
As you can see, I am sending props down all the way down to child. When I click the button to Add to Watchlist, I see the console.log message appear, saying "CHILD WAS CLICKED". I've even tried just calling the method to fetch from backend API again.
Also, in UserWatchlist, I've tried a componentDidUpdate, but both prevProps and this.props show the very same array of data. Somewhere in the chain, my data is getting lost.
This is also my first time posting a question here, so if it can be improved, I am happy to add extra details and contribute something to this community
You probably forgot to wait for addToWatchlist to complete:
addToWatchlist = () => {
// POST to backend DB to save
return fetch("/api/add-coin", settings)
.catch(err => {
return err
})
}
clickHandler = () => {
this.addToWatchlist().then(() => {
this.props.addWishlist()
})
}

Categories