React functional component with mapped Child functional component with onClick function - javascript

Hi I have mapped some json data named "projectsData" and I am trying to "bind" an onClick event with a setState hook. The mapping works except for the "onClick" does not work when clicking the grid item. In my case I want to update filterproject value with the project.id value from that target.
Right now when I click an item it does nothing.
How do I successfully map a function to "onClick" while using functional components?
Below is the parent Component
import React, { useEffect, useState } from "react";
import projectsData from '../data/projectsData';
import Project from './Projects';
const App = (props) => {
const [projects] = useState(() => (projectsData.map((project) => <Project id={project.id} project={project} onClick={() => {setFilterProject(project.id)}}/>)));
const [filterproject, setFilterProject] = useState(null);
return (
<body>
<div id='sepLine'>
<div id="visHolder">
<div id="visContainer" style={{position: "relative", width: "840px", height: "1823px"}} >
{projects}
</div>
</div>
</div>
</body>
);
}
export default App;
And here is the Child Component - "Project"
import React, { useRef } from "react";
const Project = (props) => {
const {projectClick, project} = props;
return (
<div className={`lineDiv gridItem y${project.start}-${project.end} ${project.kind}`} style={{positon: "absolute"}} onClick={projectClick}>
<h5>{project.title}</h5>
<br></br>
<p className="year">
<span className="yearsstart">{project.start}</span> - <span className="yearsend">{project.end}</span>
<br></br>
<span className="kind">{project.kind}</span>
</p>
</div>
)
}
export default Project
below is a screen grab of Console showing one of the mapped projects and it's onClick parameters. I can see it but when I click nothing happens. Any help would be great!

You pass click handler to a prop called onClick when setting initial state
const [projects] = useState(() => projectsData.map((project) => (
<Project
id={project.id}
project={project}
onClick={() => {setFilterProject(project.id)}}
/>
));
but access it as projectClick in the component
const { projectClick, project } = props;
...
<div
className={`lineDiv gridItem y${project.start}-${project.end} ${project.kind}`}
style={{positon: "absolute"}}
onClick={projectClick}
>
...
</div>
Fix by accessing the correct prop
const { onClick, project } = props;
...
<div
className={`lineDiv gridItem y${project.start}-${project.end} ${project.kind}`}
style={{positon: "absolute"}}
onClick={onClick}
>
...
</div>

Related

How to toggle class in react, but one component at once(all with the same classes)

let me explain my situation.
I am building a MERN project to my portfolio and I am trying to make a button toggle between the name of an item and a inputfield. So when the user click the pen (edit), it will add a class with the displain:none; in the div with the text coming from the MongoDB data base to hide it and will remove it from the div with the input. I could manage to do it. BUT since the amount of items can inscrease, clicking in one of them cause the toggle in all of them.
It was ok until I send some useState as props to the component.
This is my code from the App.jsx
import React, {useState, useEffect} from "react";
import Axios from "axios";
import "./App.css";
import ListItem from "./components/ListItem";
function App() {
//here are the use states
const [foodName, setFoodName] = useState("");
const [days, setDays] = useState(0);
const [newFoodName, setNewFoodName] = useState("");
const [foodList, setFoodList] = useState([]);
//here is just the compunication with the DB of a form that I have above those components
useEffect(() => {
Axios.get("http://localhost:3001/read").then((response) => {
setFoodList(response.data);
});
}, []);
const addToList = () => {
Axios.post("http://localhost:3001/insert", {
foodName: foodName,
days: days,
});
};
const updateFood = (id) => {
Axios.put("http://localhost:3001/update", {
id: id,
newFoodName: newFoodName,
});
};
return (
<div className="App">
//Here it starts the app with the form and everything
<h1>CRUD app with MERN</h1>
<div className="container">
<h3 className="container__title">Favorite Food Database</h3>
<label>Food name:</label>
<input
type="text"
onChange={(event) => {
setFoodName(event.target.value);
}}
/>
<label>Days since you ate it:</label>
<input
type="number"
onChange={(event) => {
setDays(event.target.value);
}}
/>
<button onClick={addToList}>Add to list</button>
</div>
//Here the form finishes and now it starts the components I showed in the images.
<div className="listContainer">
<hr />
<h3 className="listContainer__title">Food List</h3>
{foodList.map((val, key) => {
return (
//This is the component and its props
<ListItem
val={val}
key={key}
functionUpdateFood={updateFood(val._id)}
newFoodName={newFoodName}
setNewFoodName={setNewFoodName}
/>
);
})}
</div>
</div>
);
}
export default App;
Now the component code:
import React from "react";
//Material UI Icon imports
import CancelIcon from "#mui/icons-material/Cancel";
import EditIcon from "#mui/icons-material/Edit";
//import CheckIcon from "#mui/icons-material/Check";
import CheckCircleIcon from "#mui/icons-material/CheckCircle";
//App starts here, I destructured the props
function ListItem({val, key, functionUpdateFood, newFoodName, setNewFoodName}) {
//const [foodList, setFoodList] = useState([]);
//Here I have the handleToggle function that will be used ahead.
const handleToggle = () => {
setNewFoodName(!newFoodName);
};
return (
<div
className="foodList__item"
key={key}>
<div className="foodList__item-group">
<h3
//As you can see, I toggle the classes with this conditional statement
//I use the same classes for all items I want to toggle with one click
//Here it will toggle the Food Name
className={
newFoodName
? "foodList__item-newName-delete"
: "foodList__name"
}>
{val.foodName}
</h3>
<div
className={
newFoodName
? "foodList__item-newName-group"
: "foodList__item-newName-delete"
}>
//Here is the input that will replace the FoodName
<input
type="text"
placeholder="The new food name..."
className="foodList__item-newName"
onChange={(event) => {
setNewFoodName(event.target.value);
}}
/>
//Here it will confirm the update and toggle back
//Didn't implement this yet
<div className="foodList__icons-confirm-group">
<CheckCircleIcon
className="foodList__icons-confirm"
onClick={functionUpdateFood}
/>
<small>Update?</small>
</div>
</div>
</div>
//here it will also desappear on the same toggle
<p
className={
newFoodName
? "foodList__item-newName-delete"
: "foodList__day"
}>
{val.daysSinceIAte} day(s) ago
</p>
<div
className={
newFoodName
? "foodList__item-newName-delete"
: "foodList__icons"
}>
//Here it will update, and it's the button that toggles
<EditIcon
className="foodList__icons-edit"
onClick={handleToggle}
/>
<CancelIcon className="foodList__icons-delete" />
</div>
</div>
);
}
export default ListItem;
I saw a solution that used different id's for each component. But this is dynamic, so if I have 1000 items on the data base, it would display all of them, so I can't add all this id's.
I am sorry for the very long explanation. It seems simple, but since I am starting, I spent the day on it + searched and tested several ways.
:|

Importing a filter variable from one Component to Another Component in REACT

So I have these 2 components:
First One MAIN PAGE
`
import {useEffect, useState} from 'react';
import Navbar from "./navbar";
import Modal from "./Modal";
import '../styles/home.css'
import FavoriteCrypto from "./favoriteCrypto";
export default function MainPage() {
const[data, setData] = useState([])
const[input, setInput] = useState("");
const [openModal, setOpenModal] = useState(false)
const [modalArr, setModalArr] = useState([])
const[favorites, setFavorites] = useState([])
const url = "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=100&page=1&sparkline=false"
useEffect(()=>{
fetch(url)
.then((res)=>{
return res.json()
})
.then((data)=>{
setData(data)
})
},[])
let searchBar = data.filter((e)=>{
return e.id.toLowerCase().includes(input.toLowerCase())
})
// add to favorite
function addToFav(id){
if(!favorites.includes(id)){
setFavorites(favorites.concat(id))
}
}
function openModalFunc(id) {
setOpenModal(true);
if(!modalArr.includes(id)) {
setModalArr(modalArr.concat(id))
}
}
function closeModalFunc(id) {
setOpenModal(false);
setModalArr([]);
}
let modalRender = data.filter(data => modalArr.includes(data.id));
let favoriteRender = data.filter(data => favorites.includes(data.id))
console.log(favoriteRender)
return(
<div>
<Navbar input={input} setInput={setInput}/>
<div className='general-info'>
<h4>Coin</h4>
<h4 className='p'>Price</h4>
<h4 className='c'>Change</h4>
<h4 className='mc'>Market Cap</h4>
<h4 className='s'>Supply</h4>
</div>
<Modal addFavorite = {addToFav} modalArr={modalRender} close = {closeModalFunc} open = {openModal}/>
{searchBar.map((e)=>(
<div
onClick={()=>{
openModalFunc(e.id);
}}
className='all_coins_wrapper'>
<div className='coins-wrapper'>
<div className='coins-label'>
<img src={e.image} alt=""/>
<div className='general_info'>
<div>{e.name}</div>
<div>{e.symbol.toUpperCase()}</div>
</div>
</div>
<p className='price-main'>${e.current_price}</p>
</div>
<div className='left-part'>
<p className='change'>{e.price_change_percentage_24h}</p>
<div className='marcap'>{e.market_cap}</div>
<div className='circ'>{e.circulating_supply}</div>
</div>
</div>
)
)}
</div>
)
}
SECOND ONE :
`
import React from "react";
import Navbar from "./navbar";
import MainPage from "./home";
export default function FavoriteCrypto({favorite}){
return(
<div>
</div>
)
}
I want to import these variable '
let favoriteRender = data.filter(data => favorites.includes(data.id))
from the first component to the second one in order to display on the second page the favoirite coins'
I tried to copy paste the code from the first component to the second component and to import the variable, but that didnt work. I am using react for a week now.So sorry if this question is already ask.But I cant solve this issue.
You don't need to export that variable in order to pass data between components. You can use props in-order to do so.
Here is the link to the docs.
And here is an example of doing so:
// COMPONENT
const MyNameComponent = (props) => <h1>{props.name}</h1>;
// USAGE
const App = () => {
const name = "John Doe";
return <MyNameComponent name={name} />
}
As a solution to your problem could be:
<FavoriteCrypto favourite={favouriteRender} />
and using it inside the component to display it. You can align the data according to your wish. Read the docs for more info 👍.

When I am using onChage here, it takes only second change. The first change I've tried in the input is not taking

Why the input only taking inputs from second input only?
import React, { useState } from "react";
import Item from "./Components/Item";
import "./ToDo.css";
function ToDo() {
let toDoIs = document.getElementById("toDoInput");
const [ToDo, setToDoIs] = useState("d");
const [ToDoArray, setToDoArray] = useState([]);
return (
<div>
<h1>ToDo</h1>
<input
id="toDoInput"
onChange={() => {
setToDoIs(toDoIs.value);
}}
type="text"
/>
<button
onClick={() => {
setToDoArray([...ToDoArray, { text: ToDo }]);
toDoIs.value = "";
}}
>
Add
</button>
<Item push={ToDoArray} />
</div>
);
}
export default ToDo;
Why the second input only works, which means whenever I use submit the value from second input only stored and displayed. I don't know why this happens.
There's a few problems here...
Don't use DOM methods in React. Use state to drive the way your component renders
Your text input should be a controlled component
When updating state based on the current value, make sure you use functional updates
import { useState } from "react";
import Item from "./Components/Item";
import "./ToDo.css";
function ToDo() {
// naming conventions for state typically use camel-case, not Pascal
const [toDo, setToDo] = useState("d");
const [toDoArray, setToDoArray] = useState([]);
const handleClick = () => {
// use functional update
setToDoArray((prev) => [...prev, { text: toDo }]);
// clear the `toDo` state via its setter
setToDo("");
};
return (
<div>
<h1>ToDo</h1>
{/* this is a controlled component */}
<input value={toDo} onChange={(e) => setToDo(e.target.value)} />
<button type="button" onClick={handleClick}>
Add
</button>
<Item push={toDoArray} />
</div>
);
}
export default ToDo;

React style object not being applied

Having issues with React style not being applied. I have no idea why it is not working as it was before.
See code below:
Accordion.js
import React, {useState} from 'react'
import { risk_assessment } from '../data/questions';
import AccordionItem from '../components/AccordionItem';
import Question from '../components/Question';
const Accordion = props => {
const [active, setActive] = useState("0")
return (
<ul className="accordion">
{risk_assessment.map((question, index) => (
<AccordionItem
key={index}
itemTitle={question.question}
itemContent={<Question options={question.options} name={question.name} />}
toggle={() => setActive(index)}
active={active == index} />
))}
</ul>
)
}
export default Accordion
AccordionItem.js
import React, {useRef, useEffect} from 'react'
const AccordionItem = ({ itemTitle, itemContent, toggle, active }) => {
const accordionContent = useRef()
let contentHeight = {}
useEffect(() => {
contentHeight = active ? {height: accordionContent.current.scrollHeight} : {height: "0px"}
})
return (
<li className="accordion_item">
<button className="button" onClick={toggle}>
{itemTitle}
<span className="control">{active ? "—" : "+"}</span>
</button>
<div className="answer_wrapper" ref={accordionContent} style={contentHeight} >
<div className="answer">{itemContent}</div>
</div>
</li>
)
}
export default AccordionItem
Question.js simply renders the data inside the Accordion Item.
Here is the output from Chrome developer tools.
I have tried messing with the useEffect hook to no success. Changed it to run on every render, only on the first render, added the ref as a dependency etc.
I need to use the useRef hook to get the height of the content area dynamically.
Any help would be appreciated.
In your case when the component re-renders the value of your variable will be lost. Try putting contentHeight in a state.
const [contentHeight, setContentHeight] = useState({})
useEffect(() => {
setContentHeight(active ? {height: accordionContent.current.scrollHeight} : {height: "0px"});
}, [active])
You can find more information in this post.

reactjs dynamically adding a form input

I am working with a form in react, and what I would like is that when I click a button, I add a new component which is just an input to the screen. It all mostly works, as planned. The issue is with the following: the layout is that I have one main component, which then displays a child component. That child component is called from a map of a useState. (More after code snippet)
This is the code of the main component:
import React, { useState } from "react";
import SingleProfile from "./individual_profile";
const ProfileInformation = (props) => {
console.log("proflie render");
const [ProfilesBoolean, setProfilesBoolean] = useState(false);
const [profiles, setProfiles] = useState(props.Data['profiles'])
const FieldAdd = (event)=>{
event.preventDefault();
const copy = profiles;
copy.push({Network:'',url:''})
return(copy)
}
function CreateInput(){
return profiles.map((data, index) =><SingleProfile index={index} data={data} />)
}
const accordion = (event) => {
const NextElement = event.target.nextElementSibling;
if (!event.target.className.includes("display")) {
NextElement.style.maxHeight = NextElement.scrollHeight + "px";
} else {
NextElement.style.maxHeight = 0;
}
};
return (
<div className="AccordionItem">
<div
className={
ProfilesBoolean ? "AccordionHeader-display" : "AccordionHeader"
}
onClick={(e) => setProfilesBoolean(!ProfilesBoolean)}
id="ProfileForm"
>
Profiles
</div>
<div className="AccordionContent">
<div className="AccordionBody">
{
profiles.map((data, index) => (
<SingleProfile index={index} data={data} />
))
}
<button id="ProfileAdd" onClick={(e) => {setProfiles(FieldAdd(e))}}>
Add a profile
</button>
</div>
</div>
</div>
);
};
export default ProfileInformation;
When I click the button and onClick fires FieldAdd() the useState updates, with a new empty object as expected. However, it does not appear inside my <div className="AccordionBody"> as I would expect it to.
The following code is used to display components, by opening and closing the child div. When it is open is when you see the child components and the add button. If I click the div, to close and then click again to re-open it, the new child component appears.
<div
className={ProfilesBoolean ? "AccordionHeader-display" : "AccordionHeader"}
onClick={(e) => setProfilesBoolean(!ProfilesBoolean)}
id="ProfileForm"
>
Profiles
</div>;
Is it possible to have the child component appear without having to close and re-open the div?
Your clickHandler FieldAdd is incorrect. You are mutating the state directly which will not cause re-render.
use setProfiles to update the state in the clickHandler. Like this
const FieldAdd = (event)=>{
setProfiles(prev => [...prev, {Network:'',url:''}])
}
Trigger the onClick like this
<button id="ProfileAdd" onClick={(e) => {FieldAdd(e)}}>
Add a profile
</button>
...

Categories