How to render a button in parent component based on children action in react js? - javascript

I have a list of movie cards when a user clicks on them, they become selected and the id of each movie card is transferred to an array named "selectedList".
I want to add a "let's go" button below the movie card but conditionally.
I mean when the array is empty the button should not be displayed and when the user clicked on at least a movie the button displays. the array should be checked each time and whenever it becomes equal to zero the button should disappear.
the thing is all the movie cards are the children of this page and I want to render the parent component based on children's behavior.
MY MAIN PAGE:
export default function Index(data) {
const info = data.data.body.result;
const selectedList = [];
return (
<>
<main className={parentstyle.main_container}>
<NavBar />
<div className={style.searchbar_container}>
<SearchBar />
</div>
<div className={style.card_container}>
{info.map((movie, i) => {
return (
<MovieCard
movieName={movie.name}
key={i}
movieId={movie._id}
selected={selectedList}
isSelected={false}
/>
);
})}
</div>
</main>
<button className={style.done}>Let's go!</button>
</>
);
}
**MOVIE CARD COMPONENT:**
export default function Index({ selected, movieName, movieId, visibility }) {
const [isActive, setActive] = useState(false);
const toggleClass = () => {
setActive(!isActive);
};
const pushToSelected = (e) => {
if (selected.includes(e.target.id)) {
selected.splice(selected.indexOf(e.target.id), 1);
console.log(selected);
} else {
selected.push(e.target.id);
console.log(selected);
}
toggleClass();
};
return (
<div>
<img
className={isActive ? style.movie_selected : style.movie}
src={`images/movies/${movieName}.jpg`}
alt={movieName}
id={movieId}
onClick={pushToSelected}
/>
<h3 className={style.title}>{movieName}</h3>
</div>
);
}

You can use conditional rendering for that:
{selectedList.length > 0 && <button className={style.done}>Let's go!</button>}
Plus, you should change your selectedList to a state, and manage the update via the setSelectedList function:
import { useState } from 'react';
export default function Index(data) {
const info = data.data.body.result;
const [selectedList, setSelectedList] = useState([]);
Add the method to the MovieCard as a property:
<MovieCard
movieName={movie.name}
key={i}
movieId={movie._id}
selected={selectedList}
setSelected={setSelectedList}
isSelected={false}
/>;
And update the list in the pushToSelected method:
export default function MovieCard({
selected,
setSelected,
movieName,
movieId,
visibility
}) {
const pushToSelected = (e) => {
if (selected.includes(e.target.id)) {
selected.splice(selected.indexOf(e.target.id), 1);
console.log(selected);
} else {
selected.push(e.target.id);
console.log(selected);
}
setSelected([...selected]);
toggleClass();
};

Related

Filtering "All" category by props with React

I'm having a filter logic on the page. Clicking on different buttons I'm filtering the initial array. How can I display all the items in the array to the sibling component by clicking the "All" button in the filtering component. Need to pass function to the parent component if I'm getting it right.
https://codesandbox.io/s/trusting-moon-djocul?file=/src/components/Filters.js.
-----
Parent component
-----
const ShopPage = () => {
const [data, setData] = useState(Categories);
const filterResult = (catItem) => {
if (!catItem) {
console.log(Categories);
setData(Categories);
} else {
const result = Categories.filter(
(curData) => curData.category === catItem
);
setData(result);
}
};
return (
<>
<div className={styles.wrapper}>
<Filters filterResult={filterResult} />
<Products products={data} />
</div>
</>
);
};
export default ShopPage;
-----
Child component
-----
const Filters = ({ filterResult }) => {
return (
<>
<div className={styles.filterbtns}>
<div onClick={() => filterResult("Cap")} className={styles.filterbtn}>
Cap
</div>
<div onClick={() => filterResult("Shirt")} className={styles.filterbtn}>
Shirt
</div>
<div
onClick={() => filterResult("Jogging")}
className={styles.filterbtn}
>
Jogging
</div>
// needed to change the useState data of the compnonent and show all of the items
<div onClick={() => filterResult()} className={styles.filterbtn}>
All
</div>
</div>
</>
);
};
export default Filters;
**
Consider passing null as filterResult parameter from the all button:
<div onClick={() => filterResult(null)} className={styles.filterbtn}>
All
</div>
This can be captured in the filterResult function where you set the result back to the original Categories if no filter category was passed:
const result = catItem
? Categories.filter((curData) => curData.category === catItem)
: Categories;
Updated SandBox:

filtering object of array by id - REACT

I'm having a big struggle with filtering an object of an array of objects by its ID in React. Let me explain:
The App is a Notes app, that stores every note you create with its Title, Text(name) and created date. The key is the ID.
Now I'm trying to create a popup modal every time I click on a note, which I managed to do ok, except for one thing: when the modal appears, it doesn't show only the selected note but all the notes list. I've tried with different array methods to filter the note I need, but didn't succeed.
This is the App.js file:
import React, { useState } from 'react';
import './App.css';
import Form from './components/Form';
import List from './components/List';
import { v4 as uuidv4 } from 'uuid';
import Modal from 'react-modal';
import ModalList from './components/ModalList';
Modal.setAppElement('#root');
function App() {
/*HOOKS */
const [list, setList] = useState([]);
const [modalList, setModalList] = useState([]);
//for modal:
let subtitle;
const [modalIsOpen, setIsOpen] = React.useState(false);
/*FUNCTIONS */
//add new notes
function handleAdd(title, name) {
if (name) {
const newList = list.concat({ title: title, name: name, id: uuidv4(), date: getCurrentDate() });
setList(newList);
console.log(newList);
const newModalList = list.concat({ title: title, name: name, id: uuidv4(), date: getCurrentDate() });
setModalList(newModalList);
}
else { alert("You should complete the notes field") }
}
//get the date for adding the note
function getCurrentDate() {
let newDate = new Date()
let date = newDate.getDate();
let month = newDate.getMonth() + 1;
let year = newDate.getFullYear();
let hours = newDate.getHours();
let minutes = newDate.getMinutes();
return `${month < 10 ? `0${month}` : `${month}`}/${date}/${year}, ${hours}:${minutes < 10 ? `0${minutes}` : `${minutes}`} hs.`
}
//deleting a note
function del(x) {
if (window.confirm("Do you really want to delete this item? The action is permanent.")) {
const newList = list.filter((item) => item.id !== x);
setList(newList);
}
}
//opening a modal
function openModal() {
setIsOpen(true);
}
//after opening a modal
function afterOpenModal() {
// references are now sync'd and can be accessed.
subtitle.style.color = '#f00';
}
//closing a modal
function closeModal() {
setIsOpen(false);
}
/*APP */
return (
<>
<div>
{/* MODAL */}
<Modal
isOpen={modalIsOpen}
onAfterOpen={afterOpenModal}
onRequestClose={closeModal}
style={customStyles}
contentLabel="Example Modal"
>
{modalList.map((item) => { return <ModalList key={item.id} item={item} quit={closeModal} /> })}
</Modal>
</div>
{/* FORM */}
<div className='App'>
<Form handleNew={handleAdd} />
</div>
{/* NOTES LIST */}
<div className='notes-list'>
{list.map((item) => { return <List key={item.id} item={item} quit={del} addModal={openModal} /> })}
</div>
</>
);
}
export default App;
And this is the ModalList.jsx file:
const ModalList = (props) => {
const { item, quit} = props;
/*LIST */
return (
<li ><button className='delete' onClick={()=>quit(item.id)}>x</button><p className='note-title'>{item.title}</p><p>{item.date}</p><p className='note-name'>{item.name}</p> </li>
);
}
export default ModalList;
I know I have to someway filter the object by its ID so that only appears what I clicked and not all the existing elements in the list, but I'm not finding the way.
Thanks a lot!
You are using Array.map here which is doing what it's supposed to do (listing the items), instead you should be using Array.filter which would return the ModalItem you need
{list.map((item) => { return <List key={item.id} item={item} quit={del} addModal={openModal} /> })}
openModal needs pass the item you clicked as a parameter and pass it to the callback.
Something like:
{list.map((item) => { return <List key={item.id} item={item} quit={del} addModal={() => openModal(item)} /> })}
Then openModal function needs to pass that parameter to the Modal component. To achieve that you can store it in your modalList for instance via setModalList([item])

Why onclick function isn't rendering (React.js)?

I’m working on building an e-commerce website in React. In which, there will be a navigation bar with all the categories and once a user clicked on a specific category, it will render all the products that belongs to the checked category in the same page. In the project I have two .js files which are NavBar.js where it contains all the stuff for the navigation bar and AllItems.js where all products are rendered.
My problem now, is that onclick in NavBar.js doesn't render AllItems.js. It works with console.log("sss") but it doesn't work with <AllItems/>
Here is my code in NavBar.js
function NavBar() {
const [allCategories, setAllCategories] = useState([])
const [currency, setCurrency] = useState([])
useEffect(() => {
fetchAnyQuery(
`
query{
categories{
name
}
}
`).then(data => {
setAllCategories( data.data.categories )
})
},[])
// for currency options
useEffect(() => {
fetchAnyQuery(`
query{
currencies
}
`
).then(data => {
setCurrency(data.data.currencies)
})
},[])
return (
<nav id = "NavBar">
<div id="NavBar-content">
<div className = "leftSide" id = "leftNav">
{allCategories.map((allCategories, index) => {
if (index == 0){
// return a checked tab
return(
<>
<input className='radio-category' id={allCategories.name} type='radio' name="nav" onClick={
function (){
console.log("ssss");
<AllItems/>
}
} checked/>
<label htmlFor={allCategories.name} className='category-label'><h5 className="tab-text">{allCategories.name.toUpperCase()}</h5></label>
</>
)
}
else {
// return unchecked tab
return(
<>
<input className='radio-category' id={allCategories.name} type='radio' name="nav" onClick={ function (){changeCategoryState(allCategories.name); <AllItems/>} } />
<label htmlFor={allCategories.name} className='category-label'><h5 className="tab-text">{allCategories.name.toUpperCase()}</h5></label>
</>
)
}
})}
</div>
<div className = "centerSide">
{/*<a href="/">*/}
{/* /!*<img src={logo} />*!/*/}
{/* Logo*/}
{/*</a>*/}
<button onClick={function (){ console.log(getCategoryState()) }}>
Abo Kalb
</button>
</div>
<div className = "rightSide">
<select className="currencySelector" id="currencySelector">
{currency.map((currency, index) =>{
return(
<option value={ JSON.stringify(currency.indexOf(index)) } > {getSymbolFromCurrency(currency.toString()) + " " + currency.toString()} </option>
)
})}
</select>
</div>
</div>
</nav>
);
}
export default NavBar;
Also, here is my code for AllItems.js file:
function AllItems() {
// The state that I want to use in NavBar.js
// const [category, setCategory] = useState([getCategoryState()])
const [products, setProducts] = useState([])
useEffect(() => {
fetchAnyQuery(
`
query{
categories{
name
products{
name
id
}
}
}
`
).then(data => {
// Here I'm trying to do all the required stuff
// console.log(category)
})
},[])
console.log("All Items RENDERED!!")
return (
<>
<h1>{ getCategoryState() }</h1>
<div className="itemContainer" id="itemContainer">
</div>
</>
)
}
export default AllItems;
From what I understood, you want to render different categories' data based on which category is clicked, but you can not call a component on the click, that's not how React works. Instead set a state and render the component conditionally according to the state value, and set the state when the component needs to be rendered.
Assuming that you're fetching your items from a server, you will have to store that data in a state
const [allItems, setAllTems] = useState([])
Then add a state that will help you render your items conditionally
const [showAllItems, setShowAllItems] = useState(false)
In your JSX, use && to render your data when the state gets all the data
<> {showAllItems && <AllItems>} </>
if you're having troubles understanding how this works, I suggest you checking React documentations, it explains very well how you can manipulate the state
Use a parent component to manage the state. When you update the radio input in Nav update the state, and then filter the data based on that selection.
const { useState } = React;
function Example({ data }) {
const [ items, setItems ] = useState(data);
const [ selection, setSelection ] = useState('');
// When you click a button, set the selection state
function handleClick(e) {
setSelection(e.target.dataset.type);
}
// `filter` out the items you want to display
// based on the selection
function filteredItems() {
return items.filter(item => item.type === selection);
}
return (
<div>
<Nav>
Dairy: <input onClick={handleClick} type="radio" data-type="dairy" name="food" />
Meat: <input onClick={handleClick} type="radio" data-type="meat" name="food" />
Vegetable: <input onClick={handleClick} type="radio" data-type="vegetable" name="food" />
</Nav>
<Items items={filteredItems()} />
</div>
);
};
// `map` over the filtered data
function Items({ items }) {
return items.map(item => <div>{item.name}</div>);
}
function Nav({ children }) {
return children;
}
const data = [
{ name: 'cow', type: 'meat' },
{ name: 'bree', type: 'dairy' },
{ name: 'chicken', type: 'meat' },
{ name: 'sprout', type: 'vegetable' }
];
ReactDOM.render(
<Example data={data} />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

React: How can I remove a specific div element after clicking its associated button?

I have this component which adds a div and its elements to the dom on button click. The adding part works fine as expected but the issue arises when I want to delete.
Right now when I click on the delete button, it does remove the item but it doesn't remove that specific item which the button is associated with. It just removes the div from the top or bottom.
I have been trying to remove that specific div whose button has been clicked to remove. How can I achieve that?
Here's the CodeSandbox.
And here's the code:
import { useState } from "react";
const App = () => {
const [ counter, setCounter ] = useState( 1 );
const handleAddDiv = () => {
setCounter( counter + 1 );
};
const handleRemoveDiv = () => {
setCounter( counter - 1 );
};
return (
<div className="App">
{
Array.from(Array( counter )).map(( item, idx ) => (
<div>
<div>
<input type="text" />
<button onClick={handleRemoveDiv}>Remove</button>
</div>
</div>
))
}
<button onClick={handleAddDiv}>Add</button>
</div>
);
}
export default App;
This is not prefered react way of doing things, but this will work:
import "./styles.css";
import { useState } from "react";
const App = () => {
const [counter, setCounter] = useState(1);
const handleAddDiv = () => {
setCounter(counter + 1);
};
const removeNode = (idx) => document.getElementById(`id-${idx}`).remove();
return (
<div className="App">
{Array.from(Array(counter)).map((item, idx) => (
<div key={idx} id={`id-${idx}`}>
<div>
<input type="text" />
<button onClick={() => removeNode(idx)}>Remove</button>
</div>
</div>
))}
<button onClick={handleAddDiv}>Add</button>
</div>
);
};
export default App;
Generaly if you would like to have it made correactly then you would want to map on a real array and have every item in array eighter having an unique id or simply use map index and then based on which item you click write a function to remove from that array our specific element.
Map over an array of unique Ids
First of all, you should map over an array of items instead of an integer value.
So, on click of add button, you should push a unique ID to the array of items where each ID would denote an item being rendered in your app.
Now, when you click on remove button, you would need to remove that ID from the array of items, which would result in "deletion" of that div from the app.
In my case, I have considered timestamp as a unique ID but should explore other options for generating unique IDs. Working with indices is anti pattern in React especially when you are mapping over an array in JSX as you would encounter issues at one point of time. So, it's a good idea to maintain unique Ids.
Note: Damian's solution is not ideal as DOM Manipulation is avoided in React.
const { useState, useCallback } = React;
const Item = ({ id, removeDiv }) => {
const clickHandler = useCallback(() => {
removeDiv(id);
}, [id, removeDiv]);
return (
<div>
<input type="text" />
<button onClick={clickHandler}>Remove</button>
</div>
);
};
const App = () => {
const [items, setItems] = useState([]);
const addDiv = useCallback(() => {
// using timestamp as a unique ID
setItems([...items, new Date().getTime()]);
}, [items]);
const removeDiv = useCallback((itemId) => {
// filter out the div which matches the ID
setItems(items.filter((id) => id !== itemId));
}, [items]);
return (
<div className="app">
{items.map((id) => (
<Item key={id} id={id} removeDiv={removeDiv} />
))}
<button onClick={addDiv}>Add</button>
</div>
);
};
ReactDOM.render(<App />,document.getElementById("react"));
.app {
text-align: center;
font-family: sans-serif;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I would use an array state instead of a counter state, because otherwise you don't know which element has to be removed.
import { useState } from "react";
import "./styles.css";
let counter = 1;
export default function App() {
const [array, setArray] = useState([0]);
const handleAddDiv = () => {
setArray((prev) => [...prev, counter++]);
};
const handleRemoveDiv = (idx) => {
var arrayCopy = [...array];
arrayCopy.splice(idx, 1);//remove the the item at the specific index
setArray(arrayCopy);
};
return (
<div className="App">
{array.map((item, idx) => (
<div key={item}>
<div>
<input type="text" />
<button onClick={()=>handleRemoveDiv(idx)}
>Remove</button>
</div>
</div>
))}
<button onClick={handleAddDiv}>Add</button>
</div>
);
}
When I am adding a new item, I give it the value counter++, because I will use it as a key, and a key should be unique.

Reactjs send data through pages and components

I have a page called: List.js which renders the component: Box for every item on the List page. My List.js also consists of a component called: MainSpace. Now every Box component will consists of a button with an onClick function. Depending on which Box component you click, I want to fill the MainSpace with data coming from that specific Box component, but I can't seem to figure out how to do this.
render() {
for (let index in data) {
list.push(
<Box data={data[index]}/>
);
}
return (
<div>
{list}
<MainSpace>
</div>
);
}
Now the Box component looks something like this:
class Box extends Component {
fillMainSpace(){
//Send this.prop.data to the list.js file and fill the MainSpace component from there I think?
}
render(){
return (
<div>
<span>{this.props.data.text}</span>
<button onClick={this.fillMainSpace}></button>
</div>
);
}
}
Mainspace doesn't have much. I just want to load for example the this.props.data.image from the clicked button from the Box component. Even console.log(this.props.data); would be sufficient. Calling the <MainSpace> component in every <Box> component is not an option, since that would render a lot of unnecessary extra components.
So my question being:
How would I be able to access the this.props.data of the clicked button of a <Box> component and use it in my <MainSpace> component?
EDIT:
Adding the MainSpace Component:
class MainSpace extends Component {
render(){
return (
<div>
{this.props.data.image}
</div>
);
}
}
You can save selected item in state like selectedItem and then pass that data to MainSpace
const List = props => {
const [selectedItem, setSelectedItem] = useState(props.data[0]);
const onSelect = item => {
setSelectedItem(item);
};
return (
<div calssName="list">
{props.data.map(item => <Box data={item} onSelect={onSelect} />)}
<MainSpace data={selectedItem} />
}
}
const Box = props => {
const { data, onSelect } = props;
return (
<div>
<span>{data.someKey}</span>
<button onClick={() => onSelect(data)} >Select</button>
</div>
);
};
Using class components
class Box extends Component {
fillMainSpace = () => {
const { onSelect, data } = this.props;
onSelect(data);
}
render(){
const { data } = this.props;
return (
<div>
<span>{data.text}</span>
<button onClick={this.fillMainSpace} }></button>
</div>
);
}
}
class List extends React.Component {
state = { selectedItem: this.props.data[0] };
onSelect = item => {
this.setState({ selectedItem: item });
}
render() {
const { data } = this.props;
const { selectedItem } = this.state;
const list = data.map(item => <Box data={item} onSelect={this.onSelect} />);
return (
<div>
{list}
<MainSpace data={selectedItem}>
</div>
);
}
}

Categories

Resources