Pass value from parent to child component with React - reactjs

I trying to display the rating of a query in my React App. But I'm not sure if I understand how to handle the state.
This is my query component:
import React, { Component, useRef, useState, useEffect } from 'react';
import { render } from 'react-dom';
import InputSearchLandlord from './search'
import './style.css'
import SimpleRating from '../components/star_display'
import ReactStars from 'react-rating-stars-component'
import './style.css'
const HandleSearch = () => {
const [ratingValue, setRating] = useState(0)
const [name, searcName] = useState("")
const nameForm = useRef(null)
const average = arr => arr.reduce( ( p, c ) => p + c, 0 ) / arr.length;
const ratings = []
const displayComment = async() => {
try {
const form = nameForm.current
const name = form['name'].value
searchName(name)
const response = await fetch(`localhost`)
const jsonData = await response.json()
getComments(jsonData)
comments.forEach(e => {
console.log(e.rating)
ratings.push(e.rating)
})
const rating = average(ratings) //Avg of all rating associated with the search
console.log(rating) //Should be pass to Rating component
setRating(rating)
} catch (error) {
console.log(error.message)
}
}
return(
<div className="container">
<div className="form-group">
<h1 className="text-center mt-5">SEARCH</h1>
<form ref={nameForm} className="mt-5">
<InputSearch name={'name'}/>
<div className="d-flex justify-content-center">
<button type="submit" className="d-flex btn btn-primary" onClick={displayComment}>Search</button>
</div>
</form>
<div>
<div className='container'>
<h1>{name}</h1>
<SimpleRating data={ratingValue}
/>
</div>
<div className='container'>
{comments.map(comment => (
<div className="commentSection">
<a>
{comment.problem}
</a><br/>
<a>
Posted on : {comment.date}
</a>
</div>
))}
</div>
</div>
</div>
</div>
)
}
export default HandleSearch;
And this is my Rating component:
import React, { useState } from 'react';
import { render } from 'react-dom';
import ReactStars from 'react-rating-stars-component'
import './style.css'
import HandleSearch from '../pages/handleSearch'
export default function SimpleRating(rating) {
const [ratingValue, setRating] = useState(0)
const options = {
value: ratingValue, //Should use the value from the Search component
a11y: true,
isHalf: true,
edit: false,
};
console.log(options.value)
if (options.value == 0) return null //if rating value = 0 doesn't display the component
return (
<div className="starComponent">
<ReactStars {...options}/>
</div>
);
}
So I trying to pass the value computed in the Search component to the Rating component. Before any query is made with the Search component, the value should be 0 and hidden.
What am I missing ?

Its to do with your props. In your parent component you create a prop called data so in your rating component you need to extract that value from props
// HandleSearch Component
<SimpleRating data={ratingValue}
export default function SimpleRating(props) {
const { data } = props
// You can also just say props.data
... rest of your component
}
Currently you are actually defining the props in your SimpleRating component but you are calling them rating (it doesn't actually matter what you call it but commonly its called props) and that is an object that contains all of the props that you pass into that component.

Related

React component is not rendering after state change with button click while using useContext hook

I am using useContext hook for the first time as I wanted the re-rendering of one component by click of a button component. Here's my code:
QuestionContext.js (for creating context):
import { createContext } from "react";
const QuestionContext = createContext()
export default QuestionContext
SectionState.js (for providing value to children):
import {React, useState} from 'react'
import QuestionContext from './QuestionContext'
import questions from '../data/questions.json'
const SectionState = (props) => {
// set questions from json to an array of 4 elements
const newQuestions = Object.keys(questions.content).map(key => questions.content[key].question)
const newState = {
"qID": 0,
"questionTxt": newQuestions[0],
}
//useState for Question state
const [currentQuestion, setCurrentQuestion] = useState(0)
const [questionCtx, setQuestionCtx] = useState(newState)
const updateQuestion = () => {
if(currentQuestion > newQuestions.length) {
console.log("no more questions")
}
else{
setCurrentQuestion(currentQuestion + 1)
setQuestionCtx(() => ({
"qID": currentQuestion,
"questionTxt": newQuestions[currentQuestion]
}))
}
}
return (
<QuestionContext.Provider value = {{newState, updateQuestion}}>
{props.children}
</QuestionContext.Provider>
)
}
export default SectionState
The following two components are child of <SectionState /> component
Buttons.js:
import React, { useContext } from 'react'
import QuestionContext from '../context/QuestionContext'
const Buttons = () => {
const example = useContext(QuestionContext)
const clickHandler = () => {
example.updateQuestion()
}
return (
<div className='flex flex-row justify-between'>
{/* <button className='btn backdrop-blur-md bg-slate-600 rounded-full xl:w-48 md:w-44 text-slate-50' onClick={ clickHandler }>Prev</button> */}
<button className='btn btn-accent rounded-full xl:w-48 md:w-44' onClick={ clickHandler }>Next</button>
</div>
)
}
export default Buttons
Questions.js
import { React, useContext } from 'react'
import './styles/Questions.css'
import QuestionContext from '../context/QuestionContext'
const Questions = () => {
const newContext = useContext(QuestionContext)
return (
<>
<h1 className='text-4xl text-zinc-50'>{ newContext.newState.questionTxt }</h1>
</>
)
}
export default Questions
Every time I have clicked on the button, I could check in the console that newState state has changed, but this new state won't render in <Questions /> component. I could still see newContext.newState.questionTxt holding the initial value i.e. newQuestions[0]. What am I doing wrong here?
Here's a reproduced link in code sandbox
<QuestionContext.Provider value = {{newState, updateQuestion}}
Here you passed newState and updateQuestion as a value of context. In Button component you update currentQuestion and questionCtx using updateQuestion() but in Questions component, you are using the value of newState as
const newContext = useContext(QuestionContext)
<h1 className='text-4xl text-zinc-50'>{ newContext.newState.questionTxt }</h1>
Here newState is not a state. It is just a variable and it is not updated at all so you don't get an updated value in Question component.
Solution:
So I think you should pass the questionCtx as a value of context Provider like
<QuestionContext.Provider value = {{questionCtx , updateQuestion}}
Use it like
<h1 className='text-4xl text-zinc-50'>{ newContext.questionCtx.questionTxt }</h1>
Working Codesandbox link: https://codesandbox.io/s/react-usecontext-forked-frgtw1?file=/src/context/SectionState.js

Data in React Component Not refreshing when Path/Location changes

I have a react app that has a "Bread Crumb Header" component, the data for this component comes from an API end point.
I use the bread crumb header component inside mulitiple components within the app, and based on the current path/window.location the bread crumb componet will get the data from the API and render the correct HTML via JSX.
The problem I have is when I navigate to diffent paths/window.location's within the application the bread crum component data doesn't update.
This is what the bread crumb component looks like:
import React, { useState, useEffect } from 'react';
import API from "../../API";
import { useLocation } from 'react-router-dom';
import { BreadCrumbTitleSection, SubtitleSection, Subtitle } from './breadCrumbHeaderStyle';
import { Breadcrumb } from 'react-bootstrap';
function BreadCrumbHeader() {
const location = useLocation();
const [breadCrumbData, setBreadCrumbData] = useState([]);
const getBreadCrumbData = async () => {
const breadCrumbHeaderResponse = await API.fetchBreadCrumbHeader(location.pathname);
setBreadCrumbData(breadCrumbHeaderResponse);
};
useEffect(() => {
getBreadCrumbData();
}, []);
return (
<div>
<BreadCrumbTitleSection backgroundUrl={breadCrumbData.BreadCrumbBgImage}>
<div className="container">
<div className="row no-gutters">
<div className="col-xs-12 col-xl-preffix-1 col-xl-11">
<h1 className="h3 text-white">{breadCrumbData.BreadCrumbTitle}</h1>
<Breadcrumb>
{breadCrumbData.BreadCrumbLinks.map(breadCrumbLink => (
<Breadcrumb.Item href={breadCrumbLink.LinkUrl} key={breadCrumbLink.Id} active={breadCrumbLink.IsActive}>
{breadCrumbLink.LinkText}
</Breadcrumb.Item>
))}
</Breadcrumb>
</div>
</div>
</div>
</BreadCrumbTitleSection>
<SubtitleSection>
<Subtitle> {breadCrumbData.SubTitle}</Subtitle>
</SubtitleSection>
</div>
);
}
export default BreadCrumbHeader;
and this is an example of how I am using it inside other components:
import React, { useContext } from 'react';
import { useParams } from "react-router-dom";
import { MenuContext } from '../context/menuContext';
import RenderCmsComponents from '../../components/RenderCmsComponents/';
import BreadCrumbHeader from '../../components/BreadCrumbHeader/';
import { CategorySection, CategoryContainer, CategoryItemCard, CategoryItemCardBody, CategoryItemCardImg, CategoryItemTitle, CategoryRow, AddToCartButton, ProductDescription} from './categoryStyle';
function Category() {
const [categoryItems] = useContext(MenuContext);
const { id } = useParams();
const category = categoryItems.find(element => element.CategoryName.toLowerCase() === id.toLowerCase());
var dynamicProps = [];
{
category && category.Products.map(productItem => (
dynamicProps.push(productItem.ProductOptions.reduce((acc, { OptionName, OptionsAsSnipCartString }, i) => ({
...acc,
[`data-item-custom${i + 1}-name`]: OptionName,
[`data-item-custom${i + 1}-options`]: OptionsAsSnipCartString
}), {}))));
}
return (
<div>
<BreadCrumbHeader /> << HERE IT IS
<CategorySection backgroundurl="/images/home-slide-4-1920x800.jpg" fluid>
<CategoryContainer>
<CategoryRow>
{category && category.Products.map((productItem, i) => (
<CategoryItemCard key={productItem.ProductId}>
<CategoryItemTitle>{productItem.ProductName}</CategoryItemTitle>
<CategoryItemCardBody>
<ProductDescription>{productItem.Description}</ProductDescription>
<div>
<CategoryItemCardImg src={productItem.ProductImageUrl} alt={productItem.ProductName} />
</div>
</CategoryItemCardBody>
<AddToCartButton
data-item-id={productItem.ProductId}
data-item-price={productItem.Price}
data-item-url={productItem.ProductUrl}
data-item-description={productItem.Description}
data-item-image={productItem.ProductImageUrl}
data-item-name={productItem.ProductName}
{...dynamicProps[i]}>
ADD TO CART {productItem.Price}
</AddToCartButton>
</CategoryItemCard>
))}
</CategoryRow>
</CategoryContainer>
</CategorySection>
<RenderCmsComponents />
</div>
);
}
export default Category;
I found this post on stack overflow:
Why useEffect doesn't run on window.location.pathname changes?
I think this may be the solution to what I need, but I don't fully understand the accepted answer.
Can someone breakdown to be how I can fix my issue and maybe give me an explaination and possible some reading I can do to really understand how hooks work and how to use them in my situation.
It seems that you should re-call getBreadCrumbData every time when location.pathname was changed. In the code below I've added location.pathname to useEffect dependency list
const location = useLocation();
const [breadCrumbData, setBreadCrumbData] = useState([]);
const getBreadCrumbData = async () => {
const breadCrumbHeaderResponse = await API.fetchBreadCrumbHeader(location.pathname);
setBreadCrumbData(breadCrumbHeaderResponse);
};
useEffect(() => {
getBreadCrumbData();
}, [location.pathname]); // <==== here

Passing multiple form input fields' data in a child component to its parent in React (hooks)

I have a child component that isn't re-rendering because the state inside its parent isn't updating. I've recently found out that I need to pass data from child to parent, but I'm not sure how to do that. Most tutorials I've found on the subject show you how to pass one field or piece of information over to the parent by sending a function, but I have multiple fields on a form I need to send over to the parent component. I'm not sure how to go about that.
Here's the parent component.
import React, { useState } from "react";
import { useQuery } from "#apollo/client";
import { getStudents } from "../queries";
import StudentDetails from "./StudentDetails";
const StudentList = () => {
const [student, setStudent] = useState("");
const { loading, error, data } = useQuery(getStudents);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error!</p>;
const handleClick = (student)=> {
//console.log(student)
setStudent(student);
};
let filteredStudents = [];
//console.log(data.students)
for(let i = 0; i < data.students.length; i++){
//console.log(data.students[i].class.name)
if(data.students[i].class.name === "1FE1"){
//console.log(data.students[i].name)
filteredStudents.push(data.students[i])
}
}
//console.log(filteredStudents);
return (
<div>
<ul id="student-list">
{data.students.map((student) => (
<li key={student.id} onClick={(e) => handleClick(student)}>{student.name}</li>
))}
</ul>
{
student ? <StudentDetails student={student} />
: <p>No Student Selected</p>
}
</div>
);
};
export default StudentList;
And here is the child component called StudentDetails which displays a student's individual information that isn't re-rendering because StudentList's state isn't changing.
import React from "react";
import { useEffect, useState } from "react";
import { getStudentQuery } from "../queries";
import { useQuery } from "#apollo/client";
import DeleteStudent from "./DeleteStudent"
import EditStudent from "./EditStudent";
const StudentDetails = (props)=> {
console.log(props)
const [astudent, setStudent] = useState(props)
return (
<div id="student-details" >
<h2>Name: {props.student.name}</h2>
<h3>Age: {props.student.age}</h3>
<h3>Class: {props.student.class.name}</h3>
<h3>Test 1 Score: {props.student.test1}</h3>
<DeleteStudent id={props.student.id}/>
<EditStudent id={props.student.id} />
</div>
)
}
export default StudentDetails;
Inside StudentDetails is another child component called "EditStudent" which is where I need to somehow pass the information submitted in the form's fields over to StudentList.
import React, { useEffect, useState } from "react";
import { useMutation } from "#apollo/react-hooks";
//import { getStudents } from "../queries";
import StudentDetails from "./StudentDetails";
import { editStudentMutation, getStudentQuery, getStudents } from "../queries/index";
const EditStudent = (props) => {
console.log(props)
const [name, setName] = useState();
const [age, setAge] = useState();
const [test, setTest] = useState();
const [editStudent] = useMutation(editStudentMutation);
const astudent = props
return (
<form id="edit-student"
onSubmit={(e) => {
e.preventDefault();
editStudent({
variables: {
id: props.id,
name: name,
age: age,
test1: test
},
refetchQueries: [{ query: getStudents}]
});
}}>
<div className="field">
<label>Student Name:</label>
<input type="text"
value={name}
onChange={(e) => setName(e.target.value)}/>
</div>
<div className="field">
<label>Age:</label>
<input type="text"
value={age}
onChange={(e) => setAge(e.target.value)}/>
</div>
<div className="field">
<label>Test One:</label>
<input type="text"
value={test}
onChange={(e) => setTest(e.target.value)}/>
</div>
<button>submit</button>
</form>
)
}
export default EditStudent;
So yeah, I think I understand what I need to do but I don't know where to start on how to pass all the info from EditStudent over to StudentList. As I mentioned, all the tutorials on the subject show how to send one individual piece of information, but not several pieces. Could anyone suggest any pointers on how to achieve this?
I think what you are looking for is lifting a state up; essentially the parent passes a state to the child component and they both can access and change the state. For your case I would suggest passing multiple states down to the child.
Here is an example that does this: enter link description here

React remove item from array by using filter function, why does the uuid keeps changing?

I'm making a To Do List app using React, I made 2 components which is the App component and the ToDoItem component, In the App component I have 2 states one of them is being used to add tasks and the second is to set the items array, In the ToDoItem component I have a state that is being used to mark items (Setting its text decoration to line through).
I'm also using UUID to make a uniqe key to each one of the components,
The problem is that everytime I try to remove an item from the list, it doesn't working and its also changes the uuids
App component:
import React, {useState} from "react";
import ToDoItem from "./ToDoItem";
import { v4 as uuidv4 } from 'uuid';
function App() {
const [item, setItem] = useState("");
const [items, setItems] = useState([]);
function handleChange(event){
const newItem = event.target.value;
setItem(newItem);
}
function addItem(){
if(item.length > 0){
console.log(item + " inserted!");
setItems( (prevItems) =>{
return[...prevItems, item];
});
setItem("");
}
}
function deleteItem(id){
setItems((prevItems) =>{
return prevItems.filter(
(key) => {
return key !== id;
}
)
});
}
return (
<div className="container">
<div className="heading">
<h1>To-Do List</h1>
</div>
<div className="form">
<input type="text" onChange={handleChange} value={item}/>
<button onClick={addItem}>
<span>Add</span>
</button>
</div>
<div>
<ul>
{ items.map((todoItem) => (
<ToDoItem
key={uuidv4()}
id={uuidv4()} //Must be used in order to be able to use it or deleting an item
item={todoItem}
onDelete={deleteItem}
/>
))}
</ul>
</div>
</div>
);
}
export default App;
ToDoItem component:
import React, {useState} from "react";
import { BiTrash } from "react-icons/bi";
function ToDoItem(props){
const [isChecked, setChecked] = useState(false);
function markItem(){
setChecked(prevValue => {
return !prevValue;
});
}
return(
<li ><span onClick={markItem} style={{textDecoration: isChecked ? "line-through" : "none"}}>
{props.item}</span> {isChecked ?
<span className="trash" onClick={() => {
props.onDelete(props.id);
}}>
<BiTrash/></span> : null}</li>
);
}
export default ToDoItem;
On every App render you make a function call (uuid()) which results in new keys (unmount) and new id, you should move the id prop to your todoItem state on creation:
// on creating a todo item
const todoItem = { id: uuid() }

Passing down Props to "Single Page Views" through components

Hey still new to React but I'm grinding my way through it slowly by building my own personal app/platform. I have a quick question of passing down props to single page views. This is my overview page that will pull in all the teams from my database as such:
import React, { useState, useEffect } from 'react';
import firebase from '../../firebase/firebase.utils'
import Button from '../../Components/GeneralComponents/Button.component'
import * as GoIcons from 'react-icons/go';
import TeamList from '../../Components/Teams/TeamList.Component'
function TeamsPage() {
const [teams, setTeams] = useState([]);
const [loading, setLoading] = useState(false);
const ref = firebase.firestore().collection("teams");
function getTeams() {
setLoading(true);
ref.onSnapshot((querySnapshot) => {
const items = [];
querySnapshot.forEach((doc) => {
items.push(doc.data());
});
setTeams(items);
setLoading(false);
console.log(items);
});
}
useEffect(() => {
getTeams();
},[])
if(loading) {
return <h1>Loading...</h1>
}
return (
<div className="content-container">
<h2>Team Page</h2>
<div className="add-section">
<div className="actions">
<Button
className="bd-btn outlined add-team"
><GoIcons.GoGear/>
Add Team
</Button>
</div>
</div>
<TeamList teams={teams} />
</div>
)
}
export default TeamsPage;
This gets passed into my TeamList Component:
import React from 'react';
import { Link } from 'react-router-dom'
import { TeamCard } from './TeamCard.Component';
const TeamList = props => {
return(
<div className='teams-overview'>
{props.teams.map(team => (
<Link to={`/teams/${team.id}`}>
<TeamCard key={team.id} team={team}/>
</Link>
))}
</div>
)
}
export default TeamList;
Which maps through and then list the Team as a card component with a link that is supposed to route to their id and pass through their data.
Now in my single page view of a team I'm struggling to gain access to that prop data:
import React from 'react'
function TeamSinglePage(team) {
return (
<div className="content-container">
<h1>Single Page View</h1>
<p>Welcome, {team.teamName}</p>
</div>
)
}
export default TeamSinglePage;

Resources