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

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>

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:

React Pass the ID of clicked Element to another Component

My App.js have this structure.
return (
<Container fluid="true" className="h-100">
<Header />
<Row className="contentRow">
<CustomerList />
<DetailPage />
</Row>
</Container>
);
There are many elements in CustomerList. With a click I want to send the ID of the element to DetailPage and display the details of the associated element. But I am still quite new in react and don't really know how to pass the data. Or if I even need to change something in the structure of the components.
You need to define a new state variable in your component.
Then please pass it with the setter function into CustomerList.
Define state variable.
const [id, setId] = useState(null);
Then pass setter function into <CustomerList />
<CustomerList setId={setId} />
// on CustomerList click event
const onClick = (event) => {
// your logic and use setId from props.
// This is just an example.
props.setId(event.target.value);
}
Finally, pass id state variable into <DetailPage /> so that your DetailPage component uses in its props.
<DetailPage id={id} />
Usage in Detailpage:
const DetailPage = (props) => {
const id = props.id;
// use id for your purpose.
}
You can use the event.target.id property. add an onclick function:
onClick={(e) => handleClick(e)}
handleClick = (e) => {
//access e.target.id here
}
See if this help you:
import React, { useState } from "react";
const Header = () => <div />;
const CustomerList = ({ onChange }) => (
<ul>
{["item1", "item2", "item3", "item4"].map((item) => (
<li onClick={() => onChange(item)} key={item}>
{item}
</li>
))}
</ul>
);
const DetailPage = ({ selectedItem }) => <div>{selectedItem}</div>;
const Component = () => {
const [selectedItem, setSelectedItem] = useState(null);
const handleChange = (item) => {
setSelectedItem(item);
};
return (
<div> {/* Container */}
<Header />
<div className="contentRow"> {/* Row */}
<CustomerList onChange={handleChange} />
<DetailPage selectedItem={selectedItem} />
</div>
</div>
);
};
When you click some item, we set the state in parent component, and then send to DetailPage, in DetailPage, you can use this selectedItem to show the info.You also can replace ["item1", "item2", "item3", "item4"] with an array of objects.
App.js
import "./styles.css";
import React, { useState } from "react";
import CustomersList from "./CustomersList";
import { DetailPage } from "./DetailPage";
export default function App() {
const [listOfElements, setListOfElements] = useState([
{ name: "abc", id: "0" },
{ name: "def", id: "1" },
{ name: "ghi", id: "2" },
{ name: "jkl", id: "3" },
{ name: "mno", id: "4" }
]);
const [selectedId, setSelectedId] = useState(1);
const [customerDEatiledinfo, setCuatomerDetailedInfo] = useState({
name: "sample"
});
const idSelectedHandler = (id) => {
const idd = +id;
const newState = listOfElements[idd];
setCuatomerDetailedInfo(newState);
};
return (
<div className="App">
<CustomersList customers={listOfElements} selectId={idSelectedHandler} />
<DetailPage customer={customerDEatiledinfo} />
</div>
);
}
CustomersList.js
export const CustomersList = (props) => {
const onClickHandler = (id) => {
props.selectId(id);
};
return (
<div>
{props.customers.map((customer) => {
return (
<div key={customer.id} onClick={()=>onClickHandler(customer.id)}>
{customer.name}
</div>
);
})}
</div>
);
};
export default CustomersList;
DeatilPage.js
export const DetailPage = (props) => {
return <div style={{ color: "blue" }}>
<br/>
DetailPage
<p>{props.customer.name}</p></div>;
};

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

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();
};

Filter through posts via OnClick function

I'm trying to filter through some posts based on their category if a button is clicked. For example I have a button that when clicked the only posts that show up are related to software projects.
I have set up a function called searchHandler that I've passed through to my SidebarOptions component, which has the onclick event. But when I pass it through nothing happens.
Here is the code in the (parent) Home Component where the searchHandler is:
function Home() {
const [posts, setPosts] = useState([]);
const [filteredPosts, setFilteredPosts] = useState(null);
const searchHandler = (event) => {
const { value } = event.target;
setFilteredPosts(
value
? posts.filter(
(post) =>
post.question.question.includes(value)
)
: null
);
};
useEffect(() => {
db.collection("questions")
.orderBy("timestamp", "desc")
.onSnapshot((snapshot) =>
setPosts(
snapshot.docs.map((doc) => ({
id: doc.id,
question: doc.data(),
}))
)
);
}, []);
return (
<div className="home">
<div></div>
<Header searchHandler={searchHandler} />
<div className="home__content">
<Sidebar searchHandler={searchHandler} />
<Feed posts={filteredPosts || posts} />
<Widget />
</div>
</div>
);
}
Here is the (child) Sidebar component that receives it:
import React from "react";
import "../Style/Sidebar.css";
import SidebarOptions from "./SidebarOptions";
function Sidebar({ searchHandler }) {
return (
<div className="sidebar">
<SidebarOptions searchHandler={searchHandler} />
</div>
);
}
export default Sidebar;
And here is the (grandchild)SidebarOptions that the function is finally sent to:
function SidebarOptions({ searchHandler }) {
return (
<div className="sidebarOptions">
<div className="sidebarOption" onChange={() => searchHandler}>
<img
src="https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d"
srcset="https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d"
alt="Software Projects"
/>
<p>Software Projects</p>
</div>
);
};
I think you need to revisit your SideBarOptions component. I wonder if the onChange handler makes sense on a div. I think it should be input rather than a div if you want your user to type. Also, you need to call your handler with the value that is typed, here you are not calling the handler (notice the missing () after searchHandler in your code for SideBarOptions). Also, it will be better to add something like a debounce so that the filter is not triggered for every character that a user types. It should ideally be triggered once a user stops typing, debounce is precisely that.
Putting some code snippet below based on my guess about how it might work.
const SideBarOptions = ({ searchHandler }) => {
const [filterText, setFilterText] = useState("");
const handleFilter = () => {
searchHandler(filterText);
}
return (
<div className="sidebarOptions">
<input name="filterText" value={filterText} onChange={(e) => setFilterText(e.target.value)} />
<div className="sidebarOption" onChange={() => searchHandler}>
<img src="https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d" srcset="https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d"
alt="Software Projects"
/>
<p>Software Projects</p>
<button onClick={handleFilter}>Filter</button>
</div>
</div>
);
}
So I was able to solve this by making a new function called categoryfilter in the Home component that went through the options and looked for the category of the posts in the database:
const categoryFilter = (category = "All") => {
const filtered =
category === "All"
? posts
: posts.filter(({ question }) => question.option === category);
setFilteredPosts(filtered);
};
I then passed that code as a prop to the sidebarOptions div after cleaning up the code a bit and used it to filter the posts based on the category name:
function SidebarOptions({ categoryFilter }) {
const categories = [
//Add all projects
{
name: "All",
imgUrl: "",
},
{
name: "Software Project",
imgUrl:
"https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d",
},
{
name: "Engineering Project",
imgUrl:
"https://c.pxhere.com/photos/a7/72/gears_cogs_machine_machinery_mechanical_printing_press_gears_and_cogs_technology-818429.jpg!d",
},
];
return (
<div className="sidebarOptions">
{categories.map((category) => (
<div
className="sidebarOption"
onClick={() => categoryFilter(category.name)}
>
{category.imgUrl && (
<img
src={category.imgUrl}
srcSet={category.imgUrl}
alt={category.name}
/>
)}
<p>{category.name}</p>
</div>
))}
<div className="sidebarOption">
<Add />
<p>Suggest Project Space</p>
</div>
</div>
);
}
export default SidebarOptions;

How to update a table data based on user inputs from 2 different components

I am a beginner in react. And I am working on a project that can render tabular data from an API, and user should be able to filter the table based on selections in the form of checkboxes. I have forms (checkboxes) in 2 different components.
I am trying to figure out what is the correct way to handle user inputs from either of the components and show the filtered data which is rendered in a different component.
I have a component that renders a "datatable" after fetching data from an API.
Table component:
import React, { useState, useEffect, useMemo } from "react"
import DataService from "./service"
import { MDBDataTable } from "mdbreact"
import { Row, Col, Card, CardBody, CardTitle, CardSubtitle } from "reactstrap"
const SampleList = props => {
const [samples, setSamples] = useState([])
useEffect(() => {
retrieveSamples()
}, [])
const retrieveSamples = () => {
DataService.sampleList()
.then(response => {
setSamples(response.data)
})
.catch(e => {
console.log(e)
})
}
const data = {
columns: [
{
label: "Sample",
field: "sample",
},
{
label: "Group",
field: "group",
},
],
rows: samples,
}
return (
<React.Fragment>
<Row>
<Col className="col-12 pt-4">
<Card>
<CardBody>
<CardTitle>Samples</CardTitle>
<CardSubtitle className="mb-3">
Please apply any filter to view the samples
</CardSubtitle>
<MDBDataTable responsive striped bordered data={data} />
</CardBody>
</Card>
</Col>
</Row>
</React.Fragment>
)
}
export default SampleList
and I have 2 different components that handle user inputs.
Component 1 with checkboxes:
import React, { useState } from "react"
const tissue = [
"Flower",
"Stem",
"Shoot",
"Root",
]
function TissueOptions(props) {
const [formData, updateFormData] = useState([])
const handleChange = event => {
let all_checked = []
if (event.target.checked) {
console.log("Checked:" + event.target.value)
updateFormData({
...formData,
[event.target.id]: event.target.value.trim(),
})
} else {
console.log("unchecked: " + event.target.value.trim())
let newSelection = {}
Object.entries(formData).forEach(([key, value]) => {
if (value !== event.target.value.trim()) {
newSelection[key] = value
}
})
updateFormData(newSelection)
}
}
const handleSubmit = event => {
event.preventDefault()
// how to filter data from the table component
}
return (
<>
<form onSubmit={handleSubmit}>
<ul>
<p>Tissues</p>
{tissue.map((name, index) => (
<div className="form-check mb-3">
<input
className="form-check-input"
type="checkbox"
value={name.toLowerCase()}
id={index + name}
onChange={handleChange}
/>
<label className="form-check-label" htmlFor={index + name}>
{name}
</label>
</div>
))}
</ul>
<button>Filter</button>
</form>
</>
)
}
export default TissueOptions
Component 2 with other filter options: (this is a child component of a separate unrelated component)
import React, { useState } from "react"
const genotype = ["Wild type", "Mutant", "Transgenic", "Hybrid", "Other", "ND"]
const treatment = ["Treated", "Untreated", "ND"]
function SidebarOptions(props) {
const [formData, updateFormData] = useState([])
const handleChange = event => {
let all_checked = []
if (event.target.checked) {
console.log("Checked:" + event.target.value)
updateFormData({
...formData,
[event.target.id]: event.target.value.trim(),
})
} else {
console.log("unchecked: " + event.target.value.trim())
let newSelection = {}
Object.entries(formData).forEach(([key, value]) => {
if (value !== event.target.value.trim()) {
newSelection[key] = value
}
})
updateFormData(newSelection)
}
}
const handleSubmit = event => {
event.preventDefault()
// how to filter data from the table component in combination with "filter component 1"
}
return (
<>
<form onSubmit={handleSubmit}>
<ul>
<p>Genotype</p>
{genotype.map((name, index) => (
<div className="form-check mb-3">
<input
className="form-check-input"
type="checkbox"
value={name.toLowerCase()}
id={index + name}
onChange={handleChange}
/>
<label className="form-check-label" htmlFor={index + name}>
{name}
</label>
</div>
))}
</ul>
<ul>
<p>Treatment</p>
{treatment.map((name, index) => (
<div className="form-check mb-3">
<input
className="form-check-input"
type="checkbox"
value={name.toLowerCase()}
id={index + name}
onChange={handleChange}
/>
<label className="form-check-label" htmlFor={index + name}>
{name}
</label>
</div>
))}
</ul>
<button>Filter</button>
</form>
</>
)
}
export default SidebarOptions
It would be great if someone could help me figure out an efficient way to filter and display data present on the 'table component' by using the filters which reside in different unrelated components.
Thanks in advance.
I could think of 3 ways you can approach this.
Hoist the data from SampleList to a higher component where SidebarOptions and TissueOptions also exist. Which would look like this.
const [sample, setSample] = useState([])
// Handle loading of data
useEffect(()=>{},[])
// Filter function
const handleFilter = (*expected params*) => {
// code to handle filter changes
setSample(filteredList)
}
return (
<SampleTableWithFilter>
<SidebarOptions onSubmit={handleFilter}/>
<SampleList listData={sample} />
<TissueOptions onSubmit={handleFilter}/>
</SampleTableWithFilter>
)
You could look into implementing react-redux or Redux-saga if you think the data will be used in many more other components. Will entail a bit more learning curve and tutorials to watch but it should help.
You could also check Context API (link) from react to handle these.
Won't be adding samples of the last two since these are something you should look into as to how you would implement it.

Categories

Resources