I am creating a flashcard app in React using hooks and having trouble deleting a deck of flashcards. I am able to render Bootstrap cards on the page with the flashcards name, description, card count, and the buttons as desired. However, I am unable to delete a deck of cards as I'm being told setFlashcards is not a function. Here is my code:
App.js
function Layout() {
const [flashcards, setFlashcards] = useState([])
useEffect(() => {
axios
.get('http://localhost:5000/decks?_embed=cards')
.then(res => {
setFlashcards(res.data.map((questionItem, index) => {
return {
id: `${index}-${Date.now()}`,
name: questionItem.name,
description: questionItem.description,
cards: questionItem.cards.length,
}
}))
})
}, [])
return (
<div>
<Header />
<ShowAllDecks flashcards={flashcards} setFlashcards={setFlashcards} />
<NotFound />
</div>
)
}
ShowAllDecks.js
function ShowAllDecks({ flashcards, setFlashcards }) {
return (
<div className='container'>
<button>Create New</button>
{flashcards.map((flashcard) => {
return <Deck flashcards={flashcards} flashcard={flashcard} key={flashcard.id} />
})}
</div>
)
}
Deck.js
function Deck({ flashcard, flashcards, setFlashcards }) {
const deleteHandler = () => {
setFlashcards(flashcards.filter(el => el.id !== flashcard.id))
}
return (
<div className='container'>
<div className='card'>
<div className='card-body'>
<h3 className='card-title'>{flashcard.name}</h3>
<p className='card-text'>{flashcard.description}</p>
<p className='card-text'>{flashcard.cards} cards</p>
<button>View</button>
<button>Study</button>
<button onClick={deleteHandler}>Delete</button>
</div>
</div>
</div>
)
}
Example of a deck with one card:
[
{
'id': 1,
'name': 'A Deck Name'
'description': 'A Deck Description',
'cards': [
{
'id': 1,
'front': 'Front of card',
'back': 'Back of card',
'deckId': 1
}
]
}
]
I'm assuming you're running call to get the flashcards in App.js because you're going to want to pass it to other components? Might be best to use Context too if you're going to drill the props down to other components a lot. Otherwise if it's only going to be for showing All decks then you can run the fetch inside the AllDecks component. Anyway I've changed your code below keeping it in App.js:
App.js
function Layout() {
const [flashcards, setFlashcards] = useState([])
const deleteHandler = (id) => {
setFlashcards(flashcards.filter(deck => deck.id !== id));
}
useEffect(() => {
axios
.get('http://localhost:5000/decks?_embed=cards')
.then(res => {
setFlashcards(res.data.map((questionItem, index) => {
return {
id: `${index}-${Date.now()}`,
name: questionItem.name,
description: questionItem.description,
cards: questionItem.cards.length,
}
}))
})
}, [])
return (
<div>
<Header />
<ShowAllDecks flashcards={flashcards} deleteHandler={deleteHandler} />
<NotFound />
</div>
)
}
ShowAllDecks.js
function ShowAllDecks({ flashcards, deleteHandler }) {
return (
<div className='container'>
<button>Create New</button>
{flashcards.map((flashcard) => {
return <Deck flashcard={flashcard} key={flashcard.id} deleteHandler={deleteHandler} />
})}
</div>
)
}
Deck.js
function Deck({ flashcard, deleteHandler }) {
return (
<div className='container'>
<div className='card'>
<div className='card-body'>
<h3 className='card-title'>{flashcard.name}</h3>
<p className='card-text'>{flashcard.description}</p>
<p className='card-text'>{flashcard.cards} cards</p>
<button>View</button>
<button>Study</button>
{/*
** this will now run the deleteHandler in the App component and pass
** the id of the current deck to it. It will then run the state hook
** and set the new flashcards without this deck
*/}
<button onClick={() => deleteHandler(flashcard.id)}>Delete</button>
</div>
</div>
</div>
)
}
Related
I have two components.
First is called: BucketTabs
Second is called:BucketForms
To have a better idea. Below pictures illustrate it.
When I switching tab, different form will be showed below.
Q: Whenever I switch from one tab to other tab, and then switch back, the content in the previous BucketForms will be gone. But, gone data are supposed to be stored into a state of that BucketForms.
In fact, I've memo the BucketForms already, so I've expected the content(data) would not be gone.
What's the problem and how could I prevent the data to be gone after switching tab.
My BucketTabs:
import { BucketForms } from '~components/BucketForms/BuckForms'
export const BucketTabs: React.FC = () => {
const items = useMemo<ContentTabsItem[]>((): ContentTabsItem[] => {
return [
{
title: '1',
renderContent: () => <BucketForms key="1" bucketCategory="1" />,
},
{
title: '2',
renderContent: () => <BucketForms key="2" bucketCategory="2" />,
},
]
}, [])
return (
<div className="row">
<div className="col">
<ContentTabs items={tabs} kind="tabs" />
</div>
</div>
)
}
BucketForms
function PropsAreEqual(prev, next) {
const result = prev.bucketCategory === next.bucketCategory;
return result;
}
interface IData {
portfolioValue?: number
}
export const BucketForms: React.FC<IProps> = React.memo(props => {
const { bucketCategory } = props
const [data, setData] = useState<IData>({
})
const view = ({
portfolioValue,
}: IData) => {
return (
<>
<div className="row portfolio">
<FormNumericInput
key="input-portfolio-value"
name="portfolioValue"
required
value={portfolioValue}
/>
</div>
</>
)
}
return (
<Form
onChange={e => {
setData({ ...data, ...e, })
}}
>
{view(data)}
</Form>
)
}, PropsAreEqual)
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;
I am fetching random users from an API and putting them in a useState object array, this works as I checked in console. The issue is I want to display a new user on button click but it doesn't work, however if I write the same JSX code outside of an onClick, it works, let me demonstrate:
This doesn't work
const addUser = () => {
object.map((item) => {
return (
<div>
<h1>{item.firstName}</h1>
<h1>{item.age}</h1>
<h1>{item.gender}</h1>
<img src={item.img} alt="" />
</div>
)
})
}
return (
<div className="App">
<button onClick={addUser}
>+</button>
</div>
);
This works:
return (
<div className="App">
{object.map((item) => {
return (
<div>
<h1>{item.firstName}</h1>
<h1>{item.age}</h1>
<h1>{item.gender}</h1>
<img src={item.img} alt="" />
</div>
)
})}
</div>
);
Here is the full code if it matters:
function App() {
const URL = "https://randomuser.me/api/"
const [object, setObject] = useState([{ firstName: 'jon', age: 20, gender: 'male', img: 'none' }])
useEffect(() => {
fetch(URL)
.then(res => res.json())
.then(data => {
const result = data.results[0]
const obj = {
firstName: result.name.first,
age: result.dob.age,
gender: result.gender,
img: result.picture.large
}
setObject(prevData => prevData.concat(obj))
})
}, [])
const addUser = () => {
object.map((item) => {
return (
<div>
<h1>{firstName}</h1>
<h1>{item.age}</h1>
<h1>{item.gender}</h1>
<img src={item.img} alt="" />
</div>
)
})
}
console.log(object[0])
console.log(object[1])
return (
<div className="App">
<button onClick={() => addUser}
>+</button>
</div>
);
}
export default App;
Two problems with your code are
You are expecting a button onClick function to return DOM elements and somehow render them in your app.
If you really want to show users only when the button is clicked, you
can set a flag that turns true when clicked on the button and then
show users list, returning DOM elements on button onClick won't
render them.
Not wrapping addUser in return.
const addUser = () => {
return (
// existing code
)
}
For code, you can check here
How could I combine those two functions (handleSelectAll and handleSelectNone)? Toggle (on/off) wouldn't work here as in most cases some options would be 'checked' so you won't know whether to toggle it ALL or NONE so there need to be 2 separate buttons (at least that's what I think). What I was thinking was that the function can be shared
const handleSelectAll = () => {
setCategories(oldCats => oldCats.map(category => {
return {
...category,
selected: true
}
}))
}
const handleSelectNone = () => {
setCategories(oldCats => oldCats.map(category => {
return {
...category,
selected: false
}
}))
}
and then the buttons in a component:
const Categories = (props) => {
return(
<div className="categories">
<h2>Categories</h2>
<form className="check-form">
{props.categories.map(category => (
<Category key={category.id} category={category} {...props} />
))}
</form>
<button onClick={props.handleSelectAll}>
Select All
</button>
<button onClick={props.handleSelectNone}>
Select None
</button>
</div>
);
};
Would there be a way of just defining one function for both buttons?
I usually do like this, keep things simple and legible, nothing too fancy:
const handleSelect = (selected = false) => {
setCategories((oldCats) =>
oldCats.map((category) => {
return {
...category,
selected,
}
})
)
}
const handleSelectAll = () => {
return handleSelect(true)
}
const handleSelectNone = () => {
return handleSelect()
}
(the render part continues as is)
Doing like so avoids you creating that extra template function passing an argument and creating a new function on every render
Yes. You can wrap the call for props.handleSelectAll and props.handleSelectNone with a function and pass the new value as argument:
const Categories = (props) => {
return(
<div className="categories">
<h2>Categories</h2>
<form className="check-form">
{props.categories.map(category => (
<Category key={category.id} category={category} {...props} />
))}
</form>
<button onClick={()=>props.handleSelect(true)}>
Select All
</button>
<button onClick={()=>props.handleSelect(false)}>
Select None
</button>
</div>
);
};
[ ()=>props.handleSelect(true) is an arrow function that calls handleSelect on click ]
And the new function will be:
const handleSelect= (newValue) => {
setCategories(oldCats => oldCats.map(category => {
return {
...category,
selected: newValue
}
}))
}
You can make it one function using a parameter, such as
const handleChangeAll = (selected) => {
setCategories(oldCats => oldCats.map(category => {
return {
...category,
selected: selected
}
}))
}
Then you can call this function with a parameter in each button like this:
const Categories = (props) => {
return(
<div className="categories">
<h2>Categories</h2>
<form className="check-form">
{props.categories.map(category => (
<Category key={category.id} category={category} {...props} />
))}
</form>
<button onClick={() => props.handleChangeAll(true)}>
Select All
</button>
<button onClick={() => props.handleChangeAll(false)}>
Select None
</button>
</div>
);
};
selected: !category.selected
const handleSelectNone = () => {
setCategories(oldCats => oldCats.map(category => {
return {
...category,
selected: !category.selected
}
}))
}
I am taking a React course and we are asked to pass a single JavaScript object as props to a React app. Below is my code:
import React from 'react';
import ReactDOM from 'react-dom';
const App = ( ) => {
const course = {
name: 'Half Stack application development',
parts: [
{
name: 'Fundamentals of React',
exercises: 10
},
{
name: 'Using props to pass data',
exercises: 7
},
{
name: 'State of a component',
exercises: 14
}
]
}
const Header = ( ) => {
return (
<div>
<h1>{course.name}</h1>
</div>
)
}
const Content = ( ) => {
return (
<div>
<Part name={course.parts} exercises={course.parts} />
<Part name={course.parts} exercises={course.parts} />
<Part name={course.parts} exercises={course.parts} />
</div>
)
}
const Part = ( ) => {
return (
<div>
<p>{course.parts} {course.parts}</p>
</div>
)
}
const Total = () => {
return (
<div>
<p>Number of exercises {course.parts + course.parts + course.parts}</p>
</div>
)
}
return (
<div>
<Header course={{course}} />
<Content course={course} />
<Total course={course} />
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
It is returning an error --> Objects are not valid as a React child.
I couldn't use this with the arrow function. I tried props but couldn't fix it. Please can someone help me to refactor and fix my code.
Here is a code that should work as desired: Code. Your course.parts is an array and that is one of the reasons why some errors occured. However, there were some more problems related to props and I would suggest reading React documentation.
You can also avoid hard-coded values in Content component by using map() function:
const Content = () => {
return (
<div>
{course.parts.map(singlePart => {
return <Part singlePart={singlePart} />;
})}
</div>
);
};
Many useful array functions are described here.
Try to use Props this way:
const App = ( ) => {
const course = {
name: 'Half Stack application development',
parts: [
{
name: 'Fundamentals of React',
exercises: 10
},
{
name: 'Using props to pass data',
exercises: 7
},
{
name: 'State of a component',
exercises: 14
}
]
}
const Header = ({course}) => {
return (
<div>
<h1>{course.name}</h1>
</div>
)
}
const Content = ({course}) => {
//use parts.map(el => <Part part={el}) instead
return (
<div>
<Part part={course.parts[0]} />
<Part part={course.parts[1]}/>
</div>
)
}
const Part = ({part}) => {
return (
<div>
<p>{part.name} {part.exercises}</p>
</div>
)
}
const Total = ({course}) => {
// dont forget to refactor counting exercises with reduce function or something prettier
return (
<div>
<p>Number of exercises {course.parts[0].exercises + course.parts[1].exercises + course.parts[2].exercises}</p>
</div>
)
}
return (
<div>
<Header course={course} />
<Content course={course} />
<Total course={course} />
</div>
)
}
Also, you could have problem with
<Header course={{course}} />
because you pass Object {course: {name: '...', parts: [...]}} as props, not {name: '..', parts: [..]}
Finally, you can move out your Header, Content, Total components from App component.