I know I need to incorporate a boolean into this, I am just not entirely sure how.
I want to click on the button event and toggle between the data "price" (data.js) put into order of cheapest price and STOP at 5 objects. But then click the same button and return the data to the previous dataspace default
Can anyone help?
App.js
import "./App.css";
import Header from "./Header";
import FeedContainer from "./FeedContainer";
import { useState } from "react";
import FeedData from "./data/data";
function App() {
const [dataspace, setDataspace] = useState(FeedData);
const topFivePrice = () => {
setDataspace([...dataspace].sort((a, b) => (a.price > b.price ? 1 : -1)));
};
return (
<>
<Header />
<div className="container">
<button onClick={topFivePrice}>TOP 5 prices</button>
<FeedContainer dataspace={dataspace} />
</div>
</>
);
}
export default App;
data.js
const FeedData = [
{ name: 'Item A', price: 125, id:1 },
{ name: 'Item B', price: 230, id:2 },
{ name: 'Item C', price: 295, id:3 },
{ name: 'Item D', price: 245, id:4 },
{ name: 'Item E', price: 900, id:5 },
{ name: 'Item F', price: 875, id:6 },
{ name: 'Item G', price: 235, id:7 },
{ name: 'Item H', price: 400, id:8 },
]
export default FeedData
FeedCard.jsx
import React from 'react'
import Card from './components/shared/Card'
import PropTypes from 'prop-types'
function FeedCard({item}) {
// const handleClick = (item) => {
// console.log(item)
// }
return (
<Card >
<div style={{textAlign:'center'}}>
Name : {item.name}<br/>
The Price is £{item.price}
</div>
</Card>
)
}
export default FeedCard
FeedContainer.jsx
import React from 'react'
import FeedCard from './FeedCard'
import Card from './components/shared/Card'
const FeedContainer = ({dataspace}) => {
return (
<div className='feedback-list'>
{dataspace.map((item ) => (
<FeedCard key ={item.id} item={item} />
))}
</div>
)
}
export default FeedContainer
Toggle views
We primarily just need to toggle between a sorted view and the default view. That can be done with a simple boolean state variable to track the current view.
const [isSortedView, setIsSortedView] = useState(false);
// ...
<button onClick={() => setIsSortedView((prev) => !prev)}>TOP 5 prices</button>
Update data view depending on toggle
Then we need to display the correct view to the user depending on the state of the toggle. This is a derived state value, so this is a textbook use-case of the useMemo hook.
const dataspace = useMemo(() => (
isSortedView
? FeedData
: [...FeedData].sort((a, b) => (a.price > b.price ? 1 : -1)).slice(0,5)
), [isSortedView]);
Avoid resorting every time the user clicks
You also don't need to recalculate the top 5 prices every time the user clicks the button; only when the data changes (which in this app is only on initial load). We can do that with another useMemo hook.
const top5Prices = useMemo(() => (
[...FeedData].sort((a, b) => (a.price > b.price ? 1 : -1)).slice(0,5)
), []);
Putting it all together
And then the final code updates look like:
function App() {
const [isSortedView, setIsSortedView] = useState(false);
const top5Prices = useMemo(() => (
[...FeedData].sort((a, b) => (a.price > b.price ? 1 : -1)).slice(0,5)
), []);
const dataspace = useMemo(() => (
isSortedView ? FeedData : top5Prices
), [isSortedView]);
return (
<>
<Header />
<div className="container">
<button onClick={() => setIsSortedView((prev) => !prev)}>TOP 5 prices</button>
<FeedContainer dataspace={dataspace} />
</div>
</>
);
}
Like mentioned in the comments this is a good example for using useMemo.
We create a variable topFiveData which only updates when dataspace changes. We add a extra state for the toggle functionality and based on the showTopFivePrice we decide what data to pass to the FeedContainer
function App() {
const [dataspace, setDataspace] = useState(FeedData);
const [showTopFivePrice, setShowTopFivePrice] = useState(false);
const topFiveData = useMemo(() => {
return [...dataspace]
.sort((a, b) => (a.price > b.price ? 1 : -1))
.slice(0, 5);
}, [dataspace]);
const toggleTopFivePrice = () => {
setShowTopFivePrice((prev) => !prev);
};
return (
<>
<Header />
<div className="container">
<button onClick={toggleTopFivePrice}>TOP 5 prices</button>
<FeedContainer dataspace={showTopFivePrice ? topFiveData : dataspace} />
</div>
</>
);
}
Related
I'm still a beginner in ReactJS.
I have a list where I can go adding new lines. To add a line I use the plus button, and to remove the line I use the minus button.
I would like to know, how can I show the plus button only on the last item in my list? So that the plus button doesn't repeat itself unnecessarily.
Could you tell me how can I remove that unnecessary plus buttons? Thank you in advance.
Here's my code I put into codesandbox.
import React from "react";
import "./styles.css";
import List from "./List/List";
const App = () => {
const [data, setData] = React.useState([
[
{
label: "Name",
value: "",
name: "00"
},
{
label: "Last Name",
value: "",
name: "01"
}
]
]);
const handleOnChange = (e, row, col) => {
const newData = data.map((d, i) => {
if (i === row) {
d[col].value = e.target.value;
}
return d;
});
setData(newData);
};
const addRow = () => {
console.log(data);
setData([
...data,
[
{
label: "Name",
value: "",
name: `${data.length}0`
},
{
label: "Last Name",
value: "",
name: `${data.length}1`
}
]
]);
};
const removeRow = (index) => {
const _data = [...data];
_data.splice(index, 1);
setData(_data);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<List
data={data}
addRow={addRow}
removeRow={removeRow}
handleOnChange={handleOnChange}
/>
</div>
);
};
export default App;
import React from "react";
import AddCircleIcon from "#material-ui/icons/AddCircle";
import RemoveCircleIcon from "#material-ui/icons/RemoveCircle";
import TextField from "#material-ui/core/TextField";
import "./styles.scss";
const List = ({ data, handleOnChange, addRow, removeRow }) => {
return (
<div className="container">
{data.map((items, i1) => (
<div key={i1} className="content">
<div className="content-row">
{items.map((item, i2) => (
<TextField
key={i2}
label={item.label}
value={item.value}
onChange={(e) => handleOnChange(e, i1, i2)}
variant="outlined"
name={item.name}
/>
))}
</div>
<div>
<AddCircleIcon onClick={addRow} />
{data.length > 1 && (
<RemoveCircleIcon onClick={() => removeRow(i1)} />
)}
</div>
</div>
))}
</div>
);
};
export default List;
You will need to add a condition when rendering your plus button:
So in your List Component:
Replace this:
<AddCircleIcon onClick={addRow} />
TO
{i1 === data.length - 1 && <AddCircleIcon onClick={addRow} />}
Working example
I have an array of 12 objects of a list of movies and i want to create pagination for them using React paginate by displaying 4 items for each page, I already display the items on the UI but the numbers of pages didn't work even when I click on the Next button.
it is my first experience with pagination in react.
import './App.css';
import React,{useState} from 'react';
import MoviesData from './components/Data/MoviesData';
import ReactPaginate from 'react-paginate';
function App() {
const [categories, setCategories] = useState(allCategories);
const [moviesList, setMoviesList] = useState(MoviesData.slice(0, 4));
const [pageNumber, setPageNumber] = useState(0);
const moviePerPage = 4;
//to determinate how many pages we going to have
const pageCount = Math.ceil(moviesList.length / moviePerPage);
const changePage = ({ selected }) => {
setPageNumber(selected);
};
return (
<main>
<MoviesCards moviesList ={moviesList} removeMovie={removeMovie} />
<ReactPaginate
previousLabel={"Previous"}
nextLabel={"Next"}
pageCount={pageCount}
onPageChange={changePage}
containerClassName={"paginationBttns"}
previousLinkClassName={"previousBttn"}
nextLinkClassName={"nextBttn"}
disabledClassName={"paginationDisabled"}
activeClassName={"paginationActive"}
/>
</main>
)
}
export default App;
This component where i display my items :
import React from 'react';
import SingleMovieCard from '../SingleMovieCard/SingleMovieCard';
const MoviesCards = ({moviesList}) => {
return (
<section>
<div className="title">
<h2>Welcome to the box office</h2>
</div>
<div >
{moviesList.map((singleMovie)=> {
return(
<SingleMovieCard singleMovie={singleMovie}
key={singleMovie.id}
removeMovie={removeMovie} />
)
})}
</div>
</section>
)
}
export default MoviesCards;
There is some problems. I will list it here:
const pageCount = Math.ceil(moviesList.length / moviePerPage);
should be:
const pageCount = Math.ceil(MoviesData.length / moviePerPage);
That's because your page count is relative to whole movie count not only the page count, which is your state storing
Another problem is with your page management, the first is ok, however every time you change your page you need to set it with the new list of movies. That means you need an useEffect to track changes in pageNumber and then set your moviesList, something like this:
useEffect(() => {
setMoviesList(MoviesData.slice(pageNumber * 4, (pageNumber + 1) * 4));
}, [pageNumber]);
I've done a simple case:
import React, { useEffect, useState } from "react";
import ReactPaginate from "react-paginate";
const MoviesData = [
{ title: "Foo", category: "horror" },
{ title: "Foo1", category: "horror" },
{ title: "Foo2", category: "horror" },
{ title: "Foo3", category: "horror" },
{ title: "Bar", category: "horror" },
{ title: "Bar1", category: "horror" },
{ title: "Bar2", category: "horror" },
{ title: "Bar3", category: "horror" }
];
const SingleMovieCard = ({ singleMovie }) => {
return <div>{singleMovie.title}</div>;
};
const MoviesCards = ({ moviesList }) => {
return (
<section>
<div className="title">
<h2>Welcome to the box office</h2>
</div>
<div>
{moviesList.map((singleMovie) => {
return (
<SingleMovieCard singleMovie={singleMovie} key={singleMovie.id} />
);
})}
</div>
</section>
);
};
function App() {
const [pageNumber, setPageNumber] = useState(0);
const [moviesList, setMoviesList] = useState(MoviesData.slice(0, 4));
const moviePerPage = 4;
const pageCount = Math.ceil(MoviesData.length / moviePerPage);
useEffect(() => {
setMoviesList(MoviesData.slice(pageNumber * 4, (pageNumber + 1) * 4));
}, [pageNumber]);
const changePage = ({ selected }) => {
console.log(selected);
setPageNumber(selected);
};
return (
<main>
<MoviesCards moviesList={moviesList} />
<ReactPaginate
previousLabel={"Previous"}
nextLabel={"Next"}
pageCount={pageCount}
onPageChange={changePage}
containerClassName={"paginationBttns"}
previousLinkClassName={"previousBttn"}
nextLinkClassName={"nextBttn"}
disabledClassName={"paginationDisabled"}
activeClassName={"paginationActive"}
/>
</main>
);
}
export default App;
I rendered a list of buttons using Array.map method in a function component. When I tried to pass state to each mapped array items, the rendered results changed all array items at once, instead of one by one.
Here is my code. Am I doing something wrong? Sorry if the question has been solved in other thread or I used the wrong method. This is my first React project and I am still learning. It would be very appreciated if someone could advise. Thank you!
import React, { useState } from "react"
export default function Comp() {
const [isActive, setActive] = useState(false)
const clickHandler = () => {
setActive(!isActive)
console.log(isActive)
}
const data = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
]
const renderList = items => {
return items.map(item => (
<li key={item.id}>
<button onClick={clickHandler}>
{item.name} {isActive ? "active" : "not active"}
</button>
</li>
))
}
return (
<ul>{renderList(data)}</ul>
)
}
Put the individual item into a different component so that each has its own active state:
export default function Comp() {
const data = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
]
const renderList = items => (
items.map(item => <Item key={item.id} name={item.name} />)
);
return (
<ul>{renderList(data)}</ul>
)
}
const Item = ({ name }) => {
const [isActive, setActive] = useState(false);
const clickHandler = () => {
setActive(!isActive);
};
return (
<li>
<button onClick={clickHandler}>
{name} {isActive ? "active" : "not active"}
</button>
</li>
);
};
You need to set the active-id in handling the click-event. That will in-turn render active/non-active conditionally:
Notice the flow (1) > (2) > (3)
function Comp() {
const [activeId, setActiveId] = React.useState(null);
const clickHandler = (item) => {
setActiveId(item.id) // (2) click-handler will set the active id
}
const data = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
]
const renderList = items => {
return items.map(item => (
<li key={item.id}>
<button onClick={() => clickHandler(item)}> // (1) passing the clicked-item so that we can set the active-id
{item.name} {item.id === activeId ?
"active" : "not active" // (3) conditionally render
}
</button>
</li>
))
}
return (
<ul>{renderList(data)}</ul>
)
}
Good Luck...
Component
Here is an example of code. What I want is to toggle the active state ( true / false) for each individual list item when I clicked it. I don't want to change them all at once.
Any help with this? Thanks in advance.
import React, { useState } from "react";
const App = () => {
const [active, setActive] = useState({});
const items = [
{ name: 'Item 1' },
{ name: 'Item 2' },
{ name: 'Item 3' }
];
handleClick = (index) => {
...
}
const list = items.map( (item, index) => {
return(
<li
key={index}
onClick={() => handleClick(index)}
className={active ? "active" : null}
>
{item.name}
</li>
)
})
return {
<>
<ul>{list}</ul>
</>
}
}
export default App;
First option - Introducing isActive property for each elements
I would introduce in the items an active property for each item and manipulate them based on the clicked item. Which needs to be added to useState where you can update the isActive property with .map().
Similarly like the following:
const [items, setItems] = useState([
{ name: 'Item 1', isActive: true },
{ name: 'Item 2', isActive: false },
{ name: 'Item 3', isActive: false }
]);
handleClick = (index) => {
setItems(prevItems =>
prevItems.map((e, i) => ({...e, isActive: i === index}))
);
}
const list = items.map( (item, index) => {
return(
<li
key={index}
onClick={() => handleClick(index)}
className={item.isActive ? 'active' : null}
>
{item.name}
</li>
)
})
return {
<>
<ul>{list}</ul>
</>
}
Second option - store the index for the clicked active element
Storing the index for the clicked element helps you the identify in .map() which one is the active one. So with a simple check with i === index you can add active class to the <li> element.
You can create a state for index with useState as the following:
const [index, setIndex] = useState(0);
const items = [
{ name: 'Item 1' },
{ name: 'Item 2' },
{ name: 'Item 3' }
];
handleClick = (i) => {
setIndex(i);
}
const list = items.map( (item, i) => {
return(
<li
key={i}
onClick={() => handleClick(i)}
className={i === index ? 'active' : null}
>
{item.name}
</li>
)
})
return {
<>
<ul>{list}</ul>
</>
}
I hope this helps!
Added comments inline.
const App = () => {
// active is active item, initially '' means, nothing selected.
const [active, setActive] = useState("");
const items = [{ name: "Item 1" }, { name: "Item 2" }, { name: "Item 3" }];
const list = items.map(({ name }) => (
<li
key={name /* avoid using index for key */}
onClick={() => setActive(name)}
className={active === name ? "active" : ""}
>
{name}
</li>
));
// return the Element, not {}
return (
<>
<ul>{list}</ul>
</>
);
};
export default App;
I have a list of objects that I'd like to pass to a map function which passes each object as props to a component to be rendered.
I have a menu and clicking each item calls setActiveItem() updating activeItem which is being managed by useState hook.
I'm trying to filter the list of objects based on this activeItem value. I've created a base case trying to replicate the problem but my base case works flawlessly though it'll at least clarify what I'm trying to do so here it is:
import React, { useState } from 'react';
import { Menu } from 'semantic-ui-react';
const [ALL, NUMBER, LETTER] = ['All', 'Number', 'Letter'];
const data = [
{
tags: [ALL, NUMBER],
value: '1'
},
{
tags: [ALL, LETTER],
value: 'a'
},
{
tags: [ALL, NUMBER],
value: '2'
},
{
tags: [ALL, LETTER],
value: 'b'
},
{
tags: [ALL, NUMBER],
value: '3'
},
{
tags: [ALL, LETTER],
value: 'c'
},
{
tags: [ALL, NUMBER],
value: '4'
},
{
tags: [ALL, LETTER],
value: 'd'
}
];
const renderData = (allValues, filterTag) => {
let filteredList = allValues.filter(val => {
return val['tags'].includes(filterTag);
});
return (
<div>
{filteredList.map(object_ => {
return object_.value;
})}
</div>
);
};
const BaseCase = props => {
const [activeItem, setActiveItem] = useState(ALL);
return (
<div>
<Menu inverted stackable fluid widths={4}>
<Menu.Item
name={ALL}
active={activeItem === ALL}
onClick={(e, { name }) => setActiveItem(name)}
/>
<Menu.Item
name={NUMBER}
active={activeItem === NUMBER}
onClick={(e, { name }) => setActiveItem(name)}
/>
<Menu.Item
name={LETTER}
active={activeItem === LETTER}
onClick={(e, { name }) => setActiveItem(name)}
/>
</Menu>
<div>{renderData(data, activeItem)}</div>
</div>
);
};
export default BaseCase;
Clicking number only shows numbers and everything else works as expected. Now for my component that isn't working. I have my data in a separate file like so:
import { BASH, DATA_SCIENCE, WEB_DEV, ALL } from '../constants';
const data = [
{
tags: [ALL],
title: 'Concussion App for Athletes',
.
.
.
},
{
tags: [DATA_SCIENCE, ALL],
title: 'Deep Learning: Exploring Car Value with an ANN',
...
},
.
.
.
];
export default data;
Here's my component. There's some commented out code that I tried but that also gave incorrect components being displayed.
import React, { useState } from 'react';
import ProjectCardContainer from '../../containers/ProjectCardContainer';
import { Menu } from 'semantic-ui-react';
import { ALL, BASH, DATA_SCIENCE, WEB_DEV } from './constants';
import data from './project_data';
import './Projects.scss';
const styles = {
container: {
display: 'flex',
justifyContent: 'space-around'
},
columns: {
display: 'flex',
flexDirection: 'column',
marginTop: '11px'
}
};
const renderColumn = (projectList, filterTag) => {
let projects = projectList.filter(proj => {
return proj['tags'].includes(filterTag);
});
return (
<div style={styles.columns}>
{projects.map(project => {
return <ProjectCardContainer project={project} />;
})}
</div>
);
};
const Projects = () => {
const [activeItem, setActiveItem] = useState(ALL);
// const [, updateState] = React.useState();
// const forceUpdate = useCallback(() => updateState({}), []);
// useEffect(() => {
// setTimeout(forceUpdate, 100);
// }, [activeItem]);
return (
<div>
<div className='second-nav-container'>
<Menu inverted stackable fluid widths={4}>
<Menu.Item
name={ALL}
active={activeItem === ALL}
onClick={(e, { name }) => setActiveItem(name)}
/>
<Menu.Item
name={WEB_DEV}
active={activeItem === WEB_DEV}
onClick={(e, { name }) => setActiveItem(name)}
/>
<Menu.Item
name={DATA_SCIENCE}
active={activeItem === DATA_SCIENCE}
onClick={(e, { name }) => setActiveItem(name)}
/>
<Menu.Item
name={BASH}
active={activeItem === BASH}
onClick={(e, { name }) => setActiveItem(name)}
/>
</Menu>
</div>
<div style={styles.container}>{renderColumn(data, activeItem)}</div>
</div>
);
};
export default Projects;
Basically the rendered list of components usually isn't correct except maybe when the page is refreshed and the default value from useState() is used. Selecting from the menu doesn't show the components of the correct category.
I believe the problem is that the render function is getting called before activeItem is updated but I'm not sure how to work around that issue. I'm somewhat new to using hooks but this seems like a problem that must come up a lot.
Anyone Have any ideas how I can use a menu like this to filter data then only show specific components based on filtered data?
The problem in the end was I wasn't providing a unique key while rendering lists of components. The solution is to provide a unique key like so:
const renderColumn = (projectList, filterTag) => {
let projects = projectList.filter(proj => {
return proj['tags'].includes(filterTag);
});
return (
<div style={styles.columns}>
{projects.map(project => {
return <ProjectCardContainer key={project.title} project={project} />;
})}
</div>
);
};
In my case I know the titles will be unique so this works.
I don't think we need to mess around too much with complicated state management. I updated the base case to meet your needs:
Constants.js:
export const [ALL, DATA_SCIENCE, WEB_DEV, BASH] = ['All', 'DATA_SCIENCE', 'WEB_DEV', 'BASH'];
data.js:
import {ALL, DATA_SCIENCE, WEB_DEV, BASH} from './Constants';
const data = [
{
tags: [ALL],
title: 'Concussion App for Athletes',
},
{
tags: [DATA_SCIENCE, ALL],
title: 'Deep Learning: Exploring Car Value with an ANN',
},
{
tags: [BASH, ALL],
title: 'Bash 101'
},
{
tags: [WEB_DEV, ALL],
title: 'Web Development Book'
},
{
tags: [WEB_DEV, ALL],
title: 'Fundamentals of web design'
}
]
export default {data};
BaseCase.js:
import React, { useState } from 'react';
import { Menu } from 'semantic-ui-react';
import data from './data';
import {ALL, DATA_SCIENCE, WEB_DEV, BASH} from './Constants';
const renderData = (allValues, filterTag) => {
let filteredList = Object.values(allValues.data).filter(val => {
return val['tags'].includes(filterTag);
});
return (
<div>
{filteredList.map(object_ => {
return <p>{object_.title}</p>;
})}
</div>
);
};
const BaseCase = props => {
const [activeItem, setActiveItem] = useState(ALL);
const newData = data;
return (
<div>
<Menu inverted stackable fluid widths={4}>
<Menu.Item
name={ALL}
active={activeItem === ALL}
onClick={(e, { name }) => setActiveItem(name)}
/>
<Menu.Item
name={DATA_SCIENCE}
active={activeItem === DATA_SCIENCE}
onClick={(e, { name }) => setActiveItem(name)}
/>
<Menu.Item
name={WEB_DEV}
active={activeItem === WEB_DEV}
onClick={(e, { name }) => setActiveItem(name)}
/>
<Menu.Item
name={BASH}
active={activeItem === BASH}
onClick={(e, { name }) => setActiveItem(name)}
/>
</Menu>
<div>{renderData(newData, activeItem)}</div>
</div>
);
};
export default BaseCase;
At return <p>{object_.title}</p>; render out your component like <ProjectCardContainer project={object_} />