ReactText.js
As you can see I want to pass data from child(GenderDropDown.js) component to parent component(ReactText.js). But the problem is that i want both gender and name in parent component with a condition that if we select gender male then we write Mr.name and if it is female then it
will show Ms.name?
import React, { useState } from "react";
import './ReactText.css';
import GenderDropDown from "./GenderDropDown";
const ReactText = () => {
const [showName, setShowName] = useState();
const [male, setMale] = useState();
const [female, setFemale] = useState();
if (male) return <div>MR</div>;
if (female) return <div>Ms.</div>;
const nameHandler = (name) => {
setShowName(name);
}
return (
<div className="Mainclass">
<div className="subclass" >Mr {showName}</div>
<div className="subclass2">
<GenderDropDown nameHandler={nameHandler} ></GenderDropDown>
</div>
</div>
)
}
GenderDropDown.js
Here is problem from where I want to pass data of gender and name to parent component.
import React, { useState } from "react";
import NameField from "./NameField";
import './ReactText.css';
const GenderDropDown = (nameHandler) => {
const [gender, setGender] = useState('');
const getName=(name)=>{
console.log(name);
}
return (
<div className="class1">
<div className="subclass1">
<select id="gender" onChange={(e) => setGender(e.target.value)}>
<option>Select gender</option>
<option value="male">Male</option>
<option value="female">Female</option>
</select>
<h3>Selected gender: {gender}</h3>
</div>
<div >
<NameField getName={getName} ></NameField>
</div>
</div>
)
}
export default GenderDropDown;
NameField.js
It is a child component of GenderDropDown.js.
import React, { useState } from "react";
import './ReactText.css';
const NameField = ({ getName }) => {
const [fullName, setFullName] = useState('')
const inputEvent = (event) => {
console.log(event.target.value);
setFullName(event.target.value)
getName(event.target.value);
}
return (
<div className="class2">
<input className="input"
type="text"
name="Name"
onChange={inputEvent}
value={fullName} />
</div>
)
}
export default NameField;
You can move all the state up to the ReactText component. Then separate NameField and GenderDropDown:
const ReactText = () => {
const [gender, setGender] = useState("");
const [showName, setShowName] = useState("");
const title = gender === "male" ? "Mr." : gender === "female" ? "Ms." : "";
return (
<div className="Mainclass">
<div className="subclass2">
<GenderDropDown value={gender} onChange={(v) => setGender(v)} />
<NameField value={showName} onChange={(v) => setShowName(v)} />
</div>
<div className="subclass">
{title} {showName}
</div>
</div>
);
};
Here is the working example:
https://codesandbox.io/s/hopeful-blackwell-vvdj3s?file=/src/App.js
Related
Below are the two mentioned cases when console logging a state results in different outputs.
CASE1: Console logging currentTitle state logs the previous state even after updating state in titleChangeHandler function.
See Case1 Console Log
import "./ExpenseForm.css";
const ExpenseForm = (props) => {
const [currentTitle, setCurrentTitle] = useState("");
const [currentAmount, setCurrentAmount] = useState("");
const [currentDate, setCurrentDate] = useState("");
const titleChangeHandler = (event) => {
setCurrentTitle(event.target.value);
console.log(currentTitle);
};
const amountChangeHandler = (event) => {
setCurrentAmount(event.target.value);
};
const dateChangeHandler = (event) => {
setCurrentDate(event.target.value);
};
const submitHandler = (event) => {
event.preventDefault();
const expenseData = {
title: currentTitle,
amount: currentAmount,
date: currentDate,
};
props.onSaveExpenseData(expenseData);
setCurrentAmount("");
setCurrentTitle("");
setCurrentDate("");
};
return (
<form onSubmit={submitHandler}>
<div className="new-expense__controls">
<div className="new-expense__control">
<label>Title</label>
<input
type="text"
value={currentTitle}
onChange={titleChangeHandler}
/>
</div>
<div className="new-expense__control">
<label>Amount</label>
<input
type="number"
min="0.1"
step="0.1"
value={currentAmount}
onChange={amountChangeHandler}
/>
</div>
<div className="new-expense__control">
<label>Date</label>
<input
type="date"
min="2019-01-01"
max="2022-12-31"
value={currentDate}
onChange={dateChangeHandler}
/>
</div>
</div>
<div className="new-expense__actions">
<button type="submit">Add expense</button>
</div>
</form>
);
};
export default ExpenseForm;
CASE 2: When we pass a function(onSelectYear) to a child component using props, and call setState function(setYear) inside the passed function, when invoked from child component(ExpensesFilter), then console logging the state shows the latest value after updating.
See Case2 Console Log
// Expenses.js
import "./Expenses.css";
import ExpenseItem from "./ExpenseItem";
import React, { useState } from "react";
import ExpensesFilter from "./ExpenseFilter";
import Card from "../UI/Card";
const Expenses = (props) => {
const [year, setYear] = useState("2020");
const onSelectYear = (year) => {
setYear(year);
console.log(year);
};
return (
<div>
<Card className="expenses">
<ExpensesFilter
selectedYear={year}
onSelectYear={onSelectYear}
></ExpensesFilter>
<ExpenseItem
title={props.expenses[0].title}
amount={props.expenses[0].amount}
date={props.expenses[0].date}
></ExpenseItem>
<ExpenseItem
title={props.expenses[1].title}
amount={props.expenses[1].amount}
date={props.expenses[1].date}
></ExpenseItem>
<ExpenseItem
title={props.expenses[2].title}
amount={props.expenses[2].amount}
date={props.expenses[2].date}
></ExpenseItem>
<ExpenseItem
title={props.expenses[3].title}
amount={props.expenses[3].amount}
date={props.expenses[3].date}
></ExpenseItem>
</Card>
</div>
);
};
export default Expenses;
// ExpenseFilter.js
import React from "react";
import "./ExpenseFilter.css";
const ExpensesFilter = (props) => {
const onSelectChange = (event) => {
props.onSelectYear(event.target.value);
};
return (
<div className="expenses-filter">
<div className="expenses-filter__control">
<label>Filter by year</label>
<select value={props.selectedYear} onChange={onSelectChange}>
<option value="2022">2022</option>
<option value="2021">2021</option>
<option value="2020">2020</option>
<option value="2019">2019</option>
</select>
</div>
</div>
);
};
export default ExpensesFilter;
I understand that react schedules the state changes and this operation is async in nature. Thats why an immediate console log doesn't reflect updated value. But in the second case, we are essentially doing the same. Can anyone please explain, what makes the second case different from first?
If we look at this part of the code:
const [year, setYear] = useState("2020");
const onSelectYear = (year) => {
setYear(year);
console.log(year);
};
The year in console.log(year) is the value that is being passed as an argument to the function onSelectYear
const [year /* this is out of scope for onSelectYear */ , setYear] = useState("2020");
const onSelectYear = (year /* this value */) => {
setYear(year);
console.log(year); // is being logged here
};
On changing the function definition like so:
const [year /* this is now in scope for onSelectYear */ , setYear] = useState("2020");
const onSelectYear = (newYear /* changed the name */) => {
setYear(newYear);
console.log(year); // the year as defined by useState is now being logged here
};
The same observation will be seen as Case 1.
I am beginner and practicing on Library Management System in react. So I have components named BookDetails.js, BookList.js. BookDetails contains the form for entering Title and Description. So How can I pass the data entered from BookDetails to BookList and to dispaly from App.
import React, { useState } from 'react'
import BookList from './BookList'
const BookDetails = (props) => {
const [bookdetails, setbookDetails] = useState('')
const [desc, setDesc] = useState('')
const titleChangehandler = (e) => {
setbookDetails(e.target.value)
}
const descriptionChangehandler = (e) => {
setDesc(e.target.value)
}
const submitHandler = (e) => {
e.preventDefault()
return (
<div className='bookdetails'>
<form className='form_bookdetails' onSubmit={submitHandler}>
<div>
<label>Enter Title:</label>
<input type='text' value={bookdetails} onChange={titleChangehandler}></input>
</div>
<div>
<label>Enter Description:</label>
<input type='text' value={desc} onChange={descriptionChangehandler}></input>
</div>
<div>
<button type='submit'>Add Details</button>
</div>
</form>
</div>
)
}
}
export default BookDetails
BookList.js
import React from 'react'
import './BookList.css'
import BookDetails from './BookDetails'
const BookList = () => {
return (
<div className="booklist">
<header>BookList</header>
<BookDetails />
</div>
)
}
export default BookList
You need to use props. BookList state will have an update function that it will pass to the BookDetail via props. Example (CodeSandbox) with Todo with title & description.
BookDetail will invoke this method on every save which then would update the original list.
TodoList.js
export default function TodoList() {
const [todo, setTodo] = React.useState(null);
const [todoList, setTodoList] = React.useState([]);
React.useEffect(() => {
getTodos();
}, []);
function getTodos() {
console.log("===> fetch all todos!!");
fetchTodos().then((todos) => {
setTodoList(todos);
});
}
function editTodo(todo) {
console.log("===> set todo => ", todo);
setTodo(todo);
}
function handleUpdate(updatedTodo) {
// update Todo
const updatedTodos = todoList.map((el) =>
el.id === updatedTodo.id ? updatedTodo : el
);
setTodoList(updatedTodos);
setTodo(null);
}
return (
<div>
<ul>
{todoList.map((item) => (
<li key={item.id}>
{item.title}, {item.description}
<button onClick={() => editTodo(item)}>edit</button>
</li>
))}
</ul>
{todo && <TodoDetail todo={todo} updateTodo={handleUpdate} />}
</div>
);
}
TodoDetail.js
import React from "react";
export default function TodoDetail(props) {
const [todo, setTodo] = React.useState(props.todo);
console.log("todo =>", todo);
function handleChange(key, value) {
console.log("===> todo changed!");
setTodo({
...todo,
[key]: value
});
}
function handleSubmit() {
// api PUT on todo
console.log("===> todo edit submit!!");
props.updateTodo(todo);
}
return (
<div>
<form onSubmit={handleSubmit}>
<label htmlFor="title">
<input
value={todo.title}
onChange={(e) => handleChange("title", e.target.value)}
/>
<input
value={todo.description}
onChange={(e) => handleChange("description", e.target.value)}
/>
</label>
<button type="submit">submit</button>
</form>
</div>
);
}
You can store the list of books in your BookList component like
const [bookList, setBookList] = useState([])
This way your BookList component has access to the books. You can then create a function to add books to the list
function addBook(book) {
setBookList([...bookList, book])
}
Then pass the addBook() function to the BookDetails component to use it on submit.
<BookDetails addBook={addBook}
Now BookDetails can access the function as a prop
props.addBook("pass new book here")
I try to pass data (like an object) from one component to another but I always get an empty object.
I call an async function to get data.
I use the setTeamAData / setTeamBData to populate the object
I pass to my child component TeamA / Team B as "data"
when I print props.data I always get {}
import React, { useEffect, useState } from 'react';
import Team from './Team';
import PlayerSelect from './PlayerSelect';
import axios from 'axios';
import moment from 'moment';
function Match(props) {
const [id, setId] = props.match.params.id;
const [teamAData, setTeamAData] = useState({});
const [teamBData, setTeamBData] = useState({});
const [location, setLocation] = useState(0);
const [datetime, setDatetime] = useState(moment(Date.now()).format("YYYY-MM-DDTkk:mm"));
const [locations, setLocations] = useState([]);
const [teamA, setTeamA] = useState([0,0,0,0,0]);
const [teamB, setTeamB] = useState([0,0,0,0,0]);
const [teamAName, setTeamAName] = useState('');
const [teamBName, setTeamBName] = useState('');
const [teamAScore, setTeamAScore] = useState(0);
const [teamBScore, setTeamBScore] = useState(0);
const [pichichi, setPichichi] = useState(['']);
const [mvp, setMVP] = useState([]);
useEffect(() => {
console.log('USE EFFECT!')
async function fetchLocations() {
const response = await axios.get('locations');
setLocations(response.data);
}
fetchLocations();
async function fetchMatch(id) {
const response = await axios.get(`matches/${id}`);
const match = response.data;
setLocation(match.location);
setTeamAData({name: response.data.teamAName});
setTeamBData({name: response.data.teamBName});
setTeamAScore(match.teamAScore);
setTeamBScore(match.teamBScore);
setPichichi(match.pichichi);
}
if (props.match.params.id)
fetchMatch(props.match.params.id);
}, []);
async function handleSubmit(event) {
event.preventDefault();
const response = await axios.post('matches', {
location: location,
datetime: datetime,
teamAName: teamAName,
teamA: teamA,
teamAScore: teamAScore,
teamB: teamB,
teamBName: teamBName,
teamBScore: teamBScore,
pichichi: pichichi,
mvp: mvp
});
}
function handlePlayerChange(teamId, ddId, value) {
let team = teamA;
if (teamId == 'B')
team = teamB;
team[ddId[1]] = value;
if (teamId == 'A')
setTeamA(team);
else
setTeamB(team);
}
function handleNameChange(teamId, name) {
if (teamId == 'A')
setTeamAName(name);
else
setTeamBName(name);
}
return (
<div>
<form onSubmit={handleSubmit}>
<div className="field">
<div className="label">Location:</div>
<div className="control">
<div className="select">
<select onChange={e=> setLocation(e.target.value)} value={location}>
<option value="0">---</option>
{
locations.map(location =>
<option value={location._id} key={location._id}>{location.name}</option>
)
}
</select>
</div>
</div>
</div>
<div className="field">
<div className="label">Date / time :</div>
<div className="control">
<input type="datetime-local" className="input" value={datetime} onChange={(e) => setDatetime(e.target.value)}/>
</div>
</div>
<div className="columns">
<div className="column">
<Team id="A"
data={teamAData}
onNameChange={handleNameChange}
onPlayerChange={handlePlayerChange}/>
</div>
<div className="column">
<Team id="B"
data={teamBData}
onNameChange={handleNameChange}
onPlayerChange={handlePlayerChange}/>
</div>
</div>
<div className="field">
<div className="label">Score A</div>
<div className="control">
<input type="text" value={teamAScore} onChange={e=> setTeamAScore(e.target.value)}/>
</div>
</div>
<div className="field">
<div className="label">Score B</div>
<div className="control">
<input type="text" value={teamBScore} onChange={e=> setTeamBScore(e.target.value)}/>
</div>
</div>
<div className="field">
<div className="label">Pichichi</div>
<div className="control">
<PlayerSelect multiple value={pichichi}/>
</div>
</div>
<div className="field">
<div className="label">MVP</div>
<div className="control">
<PlayerSelect value={mvp}/>
</div>
</div>
<button className="button is-primary" type="submit">Create</button>
</form>
</div>
)
}
export default Match;
This is the child component:
import React, { Fragment, useEffect, useState } from 'react';
import PlayerSelect from './PlayerSelect';
function Team(props) {
console.log(props)
const [numberOfPlayers, setNumberOfPlayers] = useState(6);
const [players, setPlayers] = useState([]);
const [name, setName] = useState(props.data.name);
useEffect(() => {
}, []);
function handlePlayerChange(id, value) {
let p = players;
p.push(value)
setPlayers(p);
props.onPlayerChange(props.id, id, value);
}
function addPlayer(event) {
setNumberOfPlayers(numberOfPlayers+1);
}
function removePlayer(event) {
setNumberOfPlayers(numberOfPlayers-1);
}
function handleNameChange(event) {
event.preventDefault();
setName(event.target.value);
props.onNameChange(props.id, event.target.value);
}
let dds = []
for (let i = 0; i < numberOfPlayers; ++i) {
const id = `${props.id}${i}`;
dds.push(
<PlayerSelect key={id} id={id} onChange={handlePlayerChange}/>
)
}
const placeholderName = `Team ${props.id}`;
return (
<>
<div className="control">
<input type="text" value={name} onChange={handleNameChange} className="input" placeholder={placeholderName}/>
</div>
{/*<button className="button" onClick={addPlayer}>+</button><br/>*/}
{dds}
</>
)
}
export default Team;
data is only ever {} when the children components mount and name state is initialized.
const [name, setName] = useState(props.data.name);
If the data prop is asynchronously updated then the children components need to also handle the data prop updating later. Use an useEffect hook with a dependency on the data prop` to update the local state when it updates from the parent component.
useEffect(() => {
setName(props.data.name);
}, [props.data.name]);
I found this tutorial on how to create a react quizz app on youtube link to tutorial
I am trying to set the title based on the current Select Option Value when submitting the form.
Currently I managed to change the title only when a different option is selected.
import React, { useState, useEffect, useRef } from "react";
import "./App.css";
import axios from "axios";
import FlashcardList from "./components/FlashcardList";
function App() {
const [flashcards, setFlashcards] = useState([]);
const [categories, setCategories] = useState([]);
const [title, setTitle] = useState("General Knowledge");
const categoryEl = useRef();
const amountEl = useRef();
useEffect(() => {
axios.get("https://opentdb.com/api_category.php").then((res) => {
setCategories(res.data.trivia_categories);
});
}, []);
function decodeString(str) {
const textArea = document.createElement("textarea");
textArea.innerHTML = str;
return textArea.value;
}
function handleSubmit(e) {
e.preventDefault();
axios
.get("https://opentdb.com/api.php", {
params: {
amount: amountEl.current.value,
category: categoryEl.current.value,
},
})
.then((res) => {
setFlashcards(
res.data.results.map((questionItem, index) => {
const answer = decodeString(questionItem.correct_answer);
const options = [...questionItem.incorrect_answers, answer];
return {
id: `${index} - ${Date.now()}`,
question: decodeString(questionItem.question),
answer: answer,
options: options.sort(() => Math.random() - 0.5),
};
})
);
});
}
function getTitle(e) {
setTitle(e.target.options[e.target.selectedIndex].text);
}
return (
<>
<form className="header" onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="category">Category</label>
<select id="category" ref={categoryEl} onChange={getTitle}>
{categories.map((category) => {
return (
<option value={category.id} key={category.id}>
{category.name}
</option>
);
})}
</select>
</div>
<div className="form-group">
<label htmlFor="amount">Number Of Questions</label>
<input
type="number"
id="amount"
min="1"
step="1"
defaultValue={10}
ref={amountEl}
/>
</div>
<div className="form-group">
<button className="btn">Generate</button>
</div>
</form>
<div className="container">
<h1 className="title">{title}</h1>
<FlashcardList flashcards={flashcards} />
</div>
</>
);
}
export default App;
Code
Live demo
You can set the category as soon the categories are fetched. Can just use the zeroth element to set the title.
useEffect(() => { axios.get("https://opentdb.com/api_category.php").then((res) => {
setCategories(res.data.trivia_categories);
setTitle(res.data.trivia_categories[0]);
});
}, []);
When I make a selection from the dropdown I saved the selected value to type then when I click the button I add an object to drums, I map over thee drums and based on the type I want to render the component with the same name.
Sandbox here
import React, { useState } from "react";
import uuid from "react-uuid";
import "./styles.css";
const Snare = () => {
return <div>Snare</div>;
};
const Gong = () => {
return <div>Gong</div>;
};
export default function App() {
const [drums, setDrums] = useState([]);
const [type, setType] = useState();
return (
<div className="App">
{drums.map((Drum, index) => (
<Drum.type /> // Why cant I use `.type`?
))}
<label>
Drum type to add:
<select onChange={e => setType(e.target.value)} value={type}>
<option value="">Select...</option>
<option value="Snare">Snare</option>
<option value="Gong">Gong</option>
</select>
<button
onClick={() => {
setDrums([...drums,
{
id: uuid(),
type
}
]);
}}
>
Add drum
</button>
</label>
</div>
);
}
In your case Drum.type is not a component but a string, you need to maintain a map of the string to component and then render it
const map = {
Snare: Snare,
Gong: Gong
};
export default function App() {
const [drums, setDrums] = useState([]);
const [type, setType] = useState();
return (
<div className="App">
{drums.map((Drum, index) => {
const Component = map[Drum.type];
return <Component key={index}/>;
})}
<label>
Drum type to add:
<select onChange={e => setType(e.target.value)} value={type}>
<option value="">Select...</option>
<option value="Snare">Snare</option>
<option value="Gong">Gong</option>
</select>
<button
onClick={() => {
setDrums([
...drums,
{
id: uuid(),
type
}
]);
}}
>
Add drum
</button>
</label>
</div>
);
}
Working demo
That's because the type is a string.
You could create a mapping to solve this and use React.createElement().
Something like:
const mapping = {
'Snare': Snare,
'Gong': Gong
}
{ drums.map(({ type }, index) => (
React.createElement(mapping[type], { key: index })
))
}