I have started learning React.js and i have covered basic concepts like React States, working and events.
just encountered a problem in rendering my data on the screen dynamically
here is the code.
Description: I am trying to create a very small SPA in which the user can enter the name of expense(title), the amount spent(amount), and the date of expense(date).
the user can then add these expenses using the add expense button and it should then be updated as a new expense component in the SPA.
problem: The dummy data(static data from an array) is rendered on the screen.
when I try to add a new expense, this new expense is displayed with the title from the DUMMY_DATA array.
what I want: whenever a user enters a new expense, then this new expense should be added with the entered data, not from DUMMY_DATA.
----APP.JS-----
import React, { useState } from "react";
import Expenses from "./components/Expenses/Expenses";
import NewExpense from "./components/NewExpense/NewExpense";
const DUMMY_EXPENSES = [
{
id: "e1",
title: "Toilet Paper",
amount: 94.12,
date: new Date(2020, 7, 14),
},
{ id: "e2", title: "New TV", amount: 799.49, date: new Date(2021, 2, 12) },
{
id: "e3",
title: "Car Insurance",
amount: 294.67,
date: new Date(2021, 2, 28),
},
{
id: "e4",
title: "New Desk (Wooden)",
amount: 450,
date: new Date(2021, 5, 12),
},
];
const App = () => {
const [expenses, setExpenses] = useState(DUMMY_EXPENSES);
const addExpenseHandler = (expense) => {
setExpenses((previousExpenses) => {
return [expense, ...previousExpenses];
});
};
return (
<div>
<NewExpense onAddExpense={addExpenseHandler} />
<Expenses items={expenses} />
</div>
);
};
export default App;
-----NEW EXPENSE------
import React from "react";
import "./NewExpense.css";
import ExpenseForm from "./ExpenseForm";
const NewExpense = (props) => {
const saveExpenseDataHandler = (enteredExpenseData) => {
const expenseData = { ...enteredExpenseData, id: Math.random().toString() };
console.log("------new expense-------");
console.log(expenseData);
props.onAddExpense(expenseData);
};
return (
<div className="new-expense">
<ExpenseForm onSaveExpenseData={saveExpenseDataHandler} />
</div>
);
};
export default NewExpense;
------EXPENSES------
import "./Expenses.css";
import ExpenseItem from "./ExpenseItem";
import Card from "../UI/Card";
import ExpensesFilter from "./ExpensesFilter";
const Expenses = (props) => {
const filterChangeHandler = (selectedYear) => {
console.log("in expenses.js");
console.log(selectedYear);
};
return (
<div>
<Card className="expenses">
<ExpensesFilter onChangeFilter={filterChangeHandler} />
{props.items.map((expense) => (
<ExpenseItem
title={expense.title}
amount={expense.amount}
date={expense.date}
/>
))}
</Card>
</div>
);
};
export default Expenses;
------EXPENSE FORM------
import "./ExpenseForm.css";
import React, { useState } from "react";
const ExpenseForm = (props) => {
const [enteredTitle, setEnteredTitle] = useState("");
const [enteredAmount, setEnteredAmount] = useState("");
const [enteredDate, setEnteredDate] = useState("");
const titleChangeHandler = (event) => {
setEnteredTitle(event.target.value);
// console.log(event.target.value);
};
const amoundChangeHandeler = (event) => {
setEnteredAmount(event.target.value);
};
const dateChangeHandeler = (event) => {
setEnteredDate(event.target.value);
};
const submitHandler = (event) => {
event.preventDefault();
const expenseData = {
title: enteredTitle,
amount: enteredAmount,
date: new Date(enteredDate),
};
console.log("--------expense form---------");
console.log(expenseData);
props.onSaveExpenseData(expenseData);
setEnteredTitle("");
setEnteredAmount("");
setEnteredDate("");
};
return (
<form onSubmit={submitHandler}>
<div className="new-expense__controls"></div>
<div className="new-expense__control "></div>
<label className="new-expense__control label">Title</label>
<input type="text" value={enteredTitle} onChange={titleChangeHandler} />
<div className="new-expense__controls"></div>
<div className="new-expense__control"></div>
<label>Amount</label>
<input
type="number"
value={enteredAmount}
min="0.01"
step="0.01"
onChange={amoundChangeHandeler}
/>
<div className="new-expense__controls"></div>
<div className="new-expense__control"></div>
<label>Date</label>
<input
type="date"
value={enteredDate}
min="2019-01-01"
max="2022-12-31"
onChange={dateChangeHandeler}
/>
<div className="new-expense__actions">
<button type="submit">Add Expense</button>
</div>
</form>
);
};
export default ExpenseForm;
------EXPENSE ITEM------
import userEvent from "#testing-library/user-event";
import React, { useState } from "react";
import "./ExpenseItem.css";
import ExpenseDate from "./ExpenseDate";
import Card from "../UI/Card";
const ExpenseItem = (props) => {
const [title, setTitle] = useState(props.title);
const clickHandeler = () => {
setTitle("Updated!");
};
return (
<Card className="expense-item">
<ExpenseDate date={props.date} />
<div className="expense-item__description">
<h2>{title}</h2>
</div>
<div className="expense-item__price">${props.amount}</div>
<button onClick={clickHandeler}>Change Title</button>
</Card>
);
};
export default ExpenseItem;
here are some snapshots.
1-> initial state
this is the initial state after saving the code and opening it in the browser
2-> here is the data i am going to add
3->error state. i want a a new expense to be formed(abcd) and to show the title amount and date with it
------EXPENSES------
syntax error in rendering title in block
it is props.title instead of just title
import userEvent from "#testing-library/user-event";
import React, { useState } from "react";
import "./ExpenseItem.css";
import ExpenseDate from "./ExpenseDate";
import Card from "../UI/Card";
const ExpenseItem = (props) => {
const [title, setTitle] = useState(props.title);
const clickHandeler = () => {
setTitle("Updated!");
};
return (
<Card className="expense-item">
<ExpenseDate date={props.date} />
<div className="expense-item__description">
<h2>{props.title}</h2>
</div>
<div className="expense-item__price">${props.amount}</div>
<button onClick={clickHandeler}>Change Title</button>
</Card>
);
};
export default ExpenseItem;
I am using material UI to list dropdown value I have a hub,Country and account dropdown fields i want to set the first value of the array object to select initial value selected.
data file:
export const dataHub = [
{ name: 'Western Europe', hubId: 1 },
{ name: 'IMMEA', hubId: 2 },
]
export const dataCountry = [
{ name: 'France', countryId: 1, hubId: 1 },
{ name: 'Germany', countryId: 2, hubId: 1 },
{ name: 'Italy', countryId: 3, hubId: 1 },
{ name: 'Spain', countryId: 4, hubId: 1 },
{ name: 'Sweden', countryId: 5, hubId: 1 },
{ name: 'Switzerland', countryId: 6, hubId: 2 },
{ name: 'Uk', countryId: 7, hubId: 1 },
]
export const dataAccount = [
{name:'Telco-channel',panterName:'',accountId:1,countryId:1},
{name:'Consumer-Online',panterName:'',accountId:2,countryId:2},
{name:'Education-Retail',panterName:'',accountId:3,countryId:2},
{name:'Non-Trade',panterName:'',accountId:4,countryId:2},
{name:'Telco-channel',panterName:'',accountId:5,countryId:3},
{name:'Commercial-channel',panterName:'',accountId:6,countryId:4},
{name:'Consumer-Retail',panterName:'',accountId:7,countryId:5},
{name:'Commercial-Online',panterName:'',accountId:8,countryId:6},
{name:'Non-Trade',panterName:'',accountId:9,countryId:6},
{name:'Education-Online',panterName:'',accountId:10,countryId:1},
{name:'Consumer-Retail',panterName:'',accountId:11,countryId:2},
{name:'Telco-channel',panterName:'',accountId:12,countryId:2},
{name:'Commercial-channel',panterName:'',accountId:13,countryId:3},
{name:'Consumer-Online',panterName:'',accountId:14,countryId:3},
{name:'Consumer-Online',panterName:'',accountId:15,countryId:4},
{name:'Consumer-Retail',panterName:'',accountId:16,countryId:4},
{name:'Non-Trade',panterName:'',accountId:17,countryId:4},
{name:'Telco-channel',panterName:'',accountId:18,countryId:4},
{name:'Consumer-Online',panterName:'',accountId:19,countryId:5},
{name:'Commercial-Retail',panterName:'',accountId:20,countryId:7},
{name:'Consumer-Online',panterName:'',accountId:21,countryId:7},
{name:'Education-Online',panterName:'',accountId:22,countryId:7},
{name:'Education-Retial',panterName:'',accountId:23,countryId:7},
{name:'Non-Trade',panterName:'',accountId:24,countryId:7},
]
below is the component rendering dropdown fields
import React, { useState, useEffect } from 'react'
import { dataHub, dataCountry, dataAccount } from "../../constants/defaultValues";
import SelectMenu from '../../components/SelectMenu';
const defaultItemHub = { name: 'Select Hub ...' };
const defaultItemCountry = { name: 'Select Country ...' };
const defaultItemAccount = { name: 'Select Account ...' };
const Filters = () => {
const [hub, setHub] = useState('')
const [country, setCountry] = useState('')
const [account, setAccount] = useState('')
const [countries, setCountries] = useState(dataCountry)
const [accounts, setAcconts] = useState(dataAccount)
useEffect(() => {
const defaultHub = sort(dataHub)[0]
const defaultCountry = sort(dataCountry).filter(country => country.hubId === defaultHub.hubId)[0];
let defaultAccount = sort(dataAccount).filter(account => account.countryId === defaultCountry.countryId)[0];
setHub(defaultHub)
setCountry(defaultCountry)
setAccount(defaultAccount)
}, [])
const hubChange = (event) => {
const hub = event.target.value;
const countries = dataCountry.filter(country => country.hubId === hub.hubId);
setHub(hub)
setCountries(countries)
setCountry('')
setAccount('')
}
const countryChange = (event) => {
const country = event.target.value;
const accounts = dataAccount.filter(account => account.countryId === country.countryId);
setCountry(country)
setAcconts(accounts)
setAccount('')
}
const accountChange = (event) => {
setAccount(event.target.value);
}
const hasHub = hub && hub !== defaultItemHub;
const hasCountry = country && country !== defaultItemCountry;
//console.log("defaultHub",defaultHub)
return (
<div className="container">
<div className="d-flex mr-1 justify-content-center align-items-center">
<SelectMenu field={"Hub"} value={hub} options={dataHub} fieldtype={"dropdown"} onChange={hubChange} />
<SelectMenu field={"Country"} value={country} disabled={!hasHub} options={countries} fieldtype={"dropdown"} onChange={countryChange} />
<SelectMenu field={"Account"} value={account} disabled={!hasCountry} options={accounts} fieldtype={"dropdown"} onChange={accountChange} />
</div>
</div>
)
}
export default Filters
the selectMenu component, I am passing props required props for the component below
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import InputLabel from '#material-ui/core/InputLabel';
import MenuItem from '#material-ui/core/MenuItem';
import FormControl from '#material-ui/core/FormControl';
import Select from '#material-ui/core/Select';
import TextField from '#material-ui/core/TextField';
const useStyles = makeStyles((theme) => ({
formControl: {
margin: theme.spacing(2),
minWidth: 180,
},
selectEmpty: {
marginTop: theme.spacing(2),
},
}));
export default function SelectMenu({ field, options, value, disabled, fieldtype, onChange }) {
const classes = useStyles();
const handleChange = (event) => {
onChange(event)
};
// When the field is a dropdown
if (fieldtype === "dropdown") {
return (
<div>
<FormControl variant="outlined" className={classes.formControl}>
<InputLabel id="demo-simple-select-outlined-label">{field}</InputLabel>
<Select
labelId="demo-simple-select-outlined-label"
id="demo-simple-select-outlined"
value={value || ''}
onChange={handleChange}
label={field}
disabled={disabled}
>
{
options.map((element, key) => {
return (
<MenuItem key={key} value={element}>{element.name}</MenuItem>
)
})
}
</Select>
</FormControl>
</div>
);
}
else {
// When the field is a Integer and Prepopulated
if (options != null) {
return (
<div>
<FormControl variant="outlined" className={classes.formControl}>
<TextField
id="outlined-read-only-input"
label={field}
value={options[0].value}
defaultValue="Hello World"
InputProps={{
readOnly: true,
}}
variant="outlined"
/>
</FormControl>
</div>
)
}
//When the field is a Integer and a Input from the user
else {
return (
<div>
<FormControl variant="outlined" className={classes.formControl}>
<TextField id="outlined-basic"
onChange={handleChange}
label={field} variant="outlined" />
</FormControl>
</div>
)
}
}
}
Can anyone help me what wrong i am doing,I am not able to set the default value in the dropdown.
enter image description here
I feel there are couple of reasons which might be causing this issue.
Make sure that, default value should present in actual options. i.e default country should be one among countries list then only default value gets picked by material UI component.
As per material UI docs, If the value is an object it must have reference equality with the option in order to be selected. If the value is not an object, the string representation must match with the string representation of the option in order to be selected. So you should define equality so that value property of Select will get matched with value property of MenuItem and then it gets picked as default value.
Try using defaultValue instead of value in <Select> tag.
I'm trying to set the new state with setState({index: index }) by passing the index but I got the following error
"Uncaught TypeError: Cannot read property 'map' of undefined"
I'm using react hooks and also I'm using an arrow function, I do not really understand what's going on...
Any suggestion on how to fix this issue?
import { useState } from "react";
import "./App.css";
const App = () => {
const [state, setState] = useState({
products: [
{
_id: "1",
title: "Shoes",
src: [
"https://www.upsieutoc.com/images/2020/01/07/img2.png",
"https://www.upsieutoc.com/images/2020/01/07/img2.png",
],
},
],
index: 0,
});
const { products, index } = state;
console.log("index", index);
const handleTab = (index) => {
console.log("index", index);
setState({ index: index });
};
return (
<div className="App">
{products.map((item) => (
<div className="details" key={item._id}>
<div >
<img src={item.src[index]} alt="" />
</div>
<div>
<div className="thumb">
{item.src.map((img, indx) => (
<img
src={img}
alt="img"
key={indx}
onClick={() => handleTab(indx)}
/>
))}
</div>
</div>
</div>
))}
</div>
);
};
export default App;
When you setState({ index: index }) it remove the products property inside your state.
Then do it instead
setState({ products, index: index })
The reason is your products state is replace by index state in handleTap function. useState will replace the current state with new state if you don't include current state.
It will be better if you separate product state and index state. For example:
const [products, setProducts] = useState([
{
_id: "1",
title: "Shoes",
src: [
"https://www.upsieutoc.com/images/2020/01/07/img2.png",
"https://www.upsieutoc.com/images/2020/01/07/img2.png",
],
},
])
const [index, setIndex]= useState(0);
then in your handleTab function just call setIndex to update the Index
I have the following iteration of option elements for a select list:
{allCustomers.map(customer => {
console.log(customer.id);
console.log(typeof(customer.id));
return (
<option
key={customer.id}
>
{customer.name}
</option>
);
})}
The list of customers I have all have unique ids and the id property is of type number. Here is what I see in the console for the logs statements I have:
1
number
2
number
3
number
4
With the snippet above I kept getting:
Warning: Each child in a list should have a unique "key" prop.
Then I tried the following and React was happy:
key={'' + customer.id}
Is this the proper way to use numbers as key in React? What is the reason that React is thinking I have duplicate keys in case I leave them as numbers?
Edit
This is the allCustomers list I have:
[
{id: 1, name: "Test Customer - 1", orderSchedules: Array(1)}
{id: 2, name: "Test Customer - 2", orderSchedules: Array(0)}
{id: 3, name: "Another Test Customer", orderSchedules: Array(1)}
{id: 4, name: "Foo Bar Baz", orderSchedules: Array(1)}
]
Edit - Full Code
import React from "react";
export default (props) => {
const {
orderSchedule,
setOrderSchedule,
allCustomers,
saveHandler,
} = props;
return (
<>
<h3>Order Schedule Details</h3>
<hr/>
<form onSubmit={saveHandler}>
<div className="form-group row">
<label htmlFor="order-schedule-detail-description-input">Order Schedule Description</label>
<input
id="order-schedule-detail-description-input"
type="text"
className="form-control"
value={orderSchedule.description}
onChange={(event) => {
setOrderSchedule({
...orderSchedule,
description: event.target.value
})
}}/>
<br/>
<br/>
<select
className="custom-select"
onChange={event => {
setOrderSchedule({
...orderSchedule,
customer: {
id: event.target.value
}
});
}}
>
{allCustomers && allCustomers.map(customer => {
return (
<option
value={customer.id}
key={customer.id}
>
{customer.name}
</option>
);
})}
</select>
</div>
<div className="form-group row">
<button type="submit"
className="btn btn-primary"
>
Save
</button>
</div>
</form>
</>
);
}
Edit - My Container Class
import React, {useEffect, useState} from "react";
import {useParams} from "react-router-dom";
import * as orderScheduleController from "./orderScheduleController";
import * as customerController from "./../customer/customerController";
import OrderScheduleDetails from "./OrderScheduleDetails";
export default ({history}) => {
let {id} = useParams();
const [orderSchedule, setOrderSchedule] = useState({
description: '',
customer: {}
});
const [allCustomers, setAllCustomers] = useState([{}]);
useEffect(() => {
if (id) {
orderScheduleController.getOrderSchedule(id)
.then(response => {
setOrderSchedule(response.data);
});
}
}, []);
useEffect(() => {
customerController.getAllCustomers()
.then(response => {
setAllCustomers(response.data);
});
}, []);
function saveHandler(event) {
event.preventDefault();
if (!orderSchedule.description) {
return;
}
orderScheduleController
.createOrderSchedule(orderSchedule)
.then(() => {
history.push('/orderSchedules');
});
}
return (
<OrderScheduleDetails
orderSchedule={orderSchedule}
setOrderSchedule={setOrderSchedule}
allCustomers={allCustomers}
saveHandler={saveHandler}
/>
)
}
Keys have to be unique across their siblings. It's possible that you have other children that are being rendered inside of the parent container that share one or more of the keys you’re specifying here. Your '' + customer.id hack would work because 1 === '1' is false.
I am trying to make a redux pattern with useContext and useReducer. I create a component that creates a context (a reducer), and I also have the initial values. I declare my reduction with the foregoing, and return to the child component that is being provided by the values of my Reducer.
Then in the next image I have a view that will render a component that makes a form and that is wrapped in the ExpenseReducer component so that it has the values of my reduce.
In the form, I import the context and try to use the dispatch, but I get an error like "undefined"
My Code
import React from "react";
//Context
export const ExpenseContext = React.createContext();
// Reducer
const reducer = (state, action) => {
switch (action.type) {
case "HANDLE_SUBMIT":
return alert("Guardado");
default:
return state;
}
};
// Valores iniciales
const initialExpenses = [
{ id: "37c237f8-3004-4f69-9101-62f59ba4ce09", charge: "carne", amount: "20" },
{
id: "32bf7455-61c8-48d5-abe1-a38c93dcf1c8",
charge: "internet",
amount: "20"
},
{ id: "7e22c2e8-7965-41fe-9f39-236f266c9f24", charge: "ca", amount: "1" }
];
function ExpenseReducer({ children }) {
const [expenses, dispatch] = React.useReducer(reducer, initialExpenses);
return (
<ExpenseContext.Provider value={{ expenses, dispatch }}>
{children}
</ExpenseContext.Provider>
);
}
export default ExpenseReducer;
import React from "react";
// Components
import ExpenseForm from "../components/ExpenseForm";
import ExpenseReducer from "../reducers/ExpenseReducer/ExpenseReducer";
function ExpenseNew() {
return (
<ExpenseReducer>
<ExpenseForm />
</ExpenseReducer>
);
}
import React, { useContext } from "react";
import "./ExpenseForm.scss";
import { ThemeContext } from "../App";
import { ExpenseContext } from "../reducers/ExpenseReducer/ExpenseReducer";
const ExpenseForm = () =>
// {
// // edit,
// // charge,
// // amount,
// // handleCharge,
// // handleAmount,
// // handleSubmit
// }
{
// Theme
const { theme } = useContext(ThemeContext);
const { expenseContext } = useContext(ExpenseContext);
return (
<form
className="form"
onSubmit={expenseContext.dispatch("HANDLE_SUBMIT")}
>
<div className="form-group">
{/*To conect the value with the variable */}
<input
type="text"
className={`${theme} form-control`}
// id="charge"
// name="charge"
// placeholder="Gasto"
// value={charge}
// onChange={handleCharge}
/>
</div>
<div className="form-group">
{/*To conect the value with the variable */}
<input
type="number"
className={`${theme} form-control`}
// id="amount"
// name="amount"
// placeholder="Cuanto"
// value={amount}
// onChange={handleAmount}
/>
<textarea
placeholder="Descripción"
className={`${theme} form-control`}
id=""
cols="30"
rows="10"
></textarea>
</div>
<button type="submit" className={`btn ${theme}`}>
{true ? "Editar" : "Guardar"}
</button>
</form>
);
};
export default ExpenseForm;
According to the docs:
Don’t forget that the argument to useContext must be the context object itself:
Correct: useContext(MyContext)
Incorrect: useContext(MyContext.Consumer)
Incorrect: useContext(MyContext.Provider)
Your reducer is returning the provider, not the context object. Hence it results in expenseContext being undefined.