How can I pass data to my child component? - reactjs

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

Related

Reactjs: How to pass complex data from child to parent

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

Why does console log inside setState function when called from child component gets access to updated state value?

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.

React, Uncaught TypeError: recipeList.push is not a function

I get error "Uncaught TypeError: recipeList.push is not a function"
when i try to submit data to localStorage. If i dont push but just rewrite existing data withouth using push() then i get no errors. I think this should be some newbie mistake.
AddRecipe.js page looks like this
import { useNavigate } from "react-router-dom";
import NewRecipeForm from "../components/recipes/NewRecipeForm";
const AddRecipe = () => {
const Navigate = useNavigate();
const setLocalStorage = (recipe) => {
const recipeList = JSON.parse(localStorage.getItem("recipe")) || [];
recipeList.push(recipe);
localStorage.setItem("recipe", JSON.stringify(recipeList));
Navigate("/");
};
// const RecipeFormHandler = (recipeData) => {
// localStorage.setItem("recipe", JSON.stringify(recipeData));
// Navigate("/");
// };
return (
<section>
<NewRecipeForm onAddRecipe={setLocalStorage} />
</section>
);
};
export default AddRecipe;
NewRecipeForm.js looks like this and i am trying to save recipeData to localStorage
import classes from "./NewRecipeForm.module.css";
import Card from "../ui/Card";
import { useRef } from "react";
const NewRecipeForm = (props) => {
const titleRef = useRef();
const imgRef = useRef();
const ingredientsRef = useRef();
const descriptionRef = useRef();
function submitHandler(event) {
event.preventDefault();
const enteredTitle = titleRef.current.value;
const enteredImg = imgRef.current.value;
const enteredingredients = ingredientsRef.current.value;
const enteredDescription = descriptionRef.current.value;
const recipeData = {
id: (Math.random() * 100).toString(),
title: enteredTitle,
image: enteredImg,
ingredients: enteredingredients,
description: enteredDescription,
};
props.onAddRecipe(recipeData);
}
return (
<Card>
<form className={classes.form} onSubmit={submitHandler}>
<div className={classes.control}>
<label htmlFor="title">Recipe Name</label>
<input type="text" required id="title" ref={titleRef} />
</div>
<div className={classes.control}>
<label htmlFor="image">Recipe Image</label>
<input type="url" required id="image" ref={imgRef} />
</div>
<div className={classes.control}>
<label htmlFor="ingredients">Ingredients</label>
<textarea
type="text"
required
id="ingredients"
rows="5"
ref={ingredientsRef}
/>
</div>
<div className={classes.control}>
<label htmlFor="description">Description</label>
<textarea id="description" required rows="5" ref={descriptionRef} />
</div>
<div className={classes.actions}>
<button type="reset">Reset</button>
<button type="submit">Add Recipe</button>
</div>
</form>
</Card>
);
};
export default NewRecipeForm;
I just used strings instead of Ref's. The replication of it got me to the working SHORT code here: SandBox.
NewRecipeForm.js
import { NewRecipeForm } from "./Child";
export default function App() {
const setLocalStorage = (recipe) => {
const recipeList = JSON.parse(localStorage.getItem("recipe")) || [];
recipeList.push(recipe);
localStorage.setItem("recipe", JSON.stringify(recipeList));
console.log(recipeList);
};
return (
<div className="App">
<NewRecipeForm onAddRecipe={setLocalStorage} />
</div>
);
}
Child.js
export const NewRecipeForm = (props) => {
function submitHandler(event) {
const recipeData = {
id: (Math.random() * 100).toString(),
title: "enteredTitle",
image: "enteredImg",
ingredients: "enteredingredients",
description: "enteredDescription"
};
props.onAddRecipe(recipeData);
}
return <button onClick={() => submitHandler()}>Click to print</button>;
};

How do I retrieve an item data by id and pass it to another component in React16 with Hooks

I want to create an edit screen. I have a component called Task that looks like this
const Task = ({task}) => {
return (
<li>
<div>
<div>{task.text}</div>
{task.day}
</div>
<div className="icons">
<Link
to={`/edit/${task.id}`} >
<RiEdit2FillIcon />
</Link>
</div>
</li>
)
}
That goes to a parent component with a tasks.map() and then to the main component that will render the list of tasks. But from this component, I want to click on that Edit Icon and open an Edit screen that is already Routed like this <Route path='/edit/:id' component={EditTask}/> That EditTask component is what I am working on now
import React from 'react'
import {useState, useEffect} from 'react'
import { Link } from 'react-router-dom'
import Task from './components/Task'
const EditTask = () => {
const api ="http://localhost:5000"
const [tasks, setTasks] = useState([])
const [task, setTask] = useState([])
const [text, setText] = useState('')
const [day, setDay] = useState('')
const [reminder, setReminder] = useState(false)
const onSubmit = (e) => {
e.preventDefault()
updateData()
}
//Get Request
useEffect(() => {
const getTask = async () => {
const tasksFromServer = await fetchTask()
setTasks(tasksFromServer)
}
getTask()
},[])
const fetchTask = async (id) => {
const res = await fetch(`${api}/tasks/${id}`)
const data = await res.json()
console.log(data)
return data
}
//Update request
const updateData = async (id) => {
const taskToEdit = await fetchTask(id)
const updateTask = {
...taskToEdit,
reminder: !taskToEdit.reminder,
text: taskToEdit.text,
day: taskToEdit.day
}
const res = await fetch(`${api}/tasks/${id}`, {
method: 'PUT',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify(updateTask)
})
const data = await res.json()
setTasks(
tasks.map((task) =>
task.id === id
? {
...task,
reminder: data.reminder,
text: data.text,
day: data.day
}
: task
)
)
}
return (
<div>
<header className='header'>
<h1>Edit</h1>
<Link to="/" className="btn btn-primary">Go Back</Link>
</header>
<form className="add-form" onSubmit={onSubmit}>
<Task task={task}/>
<div className="form-control">
<label>Task</label>
<input type="text" placeholder="Add Task" value={text} onChange={(e)=> setText(e.target.value)} />
</div>
<div className="form-control">
<label>Day & Time</label>
<input type="text" placeholder="Add Day & Time" value={day} onChange={(e)=> setDay(e.target.value)}/>
</div>
<div className="form-control form-control-check">
<label>Set Reminder</label>
<input type="checkbox" checked={reminder} value={reminder} onChange={(e)=> setReminder(e.currentTarget.checked)}/>
</div>
<input className="btn btn-block" type="submit" value="Save Task" />
</form>
</div>
)
}
export default EditTask
I'm a bit lost here. I can't figure out how to pass the ID from Task.js to EditTask.js and populate the form with the data form that ID.
Thanks in advance
You can get id in EditTask with useParams in "react-router
import { useParams } from "react-router";
const EditTask = () => {
const { id } = useParams();
}

Set title onSubmit based on current SELECT OPTION VALUE - react

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]);
});
}, []);

Resources