problem with class base component and functional component in react - javascript

I have a simple to do app in react every thing is okay when my app.js is a class component but when I changed it to a functional component occured an error = todos.filter is not a function
my files : Todo.js(functional) --> TodoList.js(functional) --> app.js(functional)
function TodoList(props) {
const [statusDone, setDone] = useState(false);
let { todos } = props;
console.log(todos);
let filterTodos = todos.filter((item) => item.done === statusDone);
return (
<>
<nav className="col-6 mb-3">
<div className="nav nav-tabs" id="nav-tab" role="tablist">
<a
className={`nav-item nav-link font-weight-bold ${
!statusDone ? "active" : ""
}`}
id="nav-home-tab"
onClick={() => setDone(false)}
>
undone{" "}
<span className="badge badge-secondary">
{todos.filter((item) => item.done === false).length}
</span>
</a>
<a
className={`nav-item nav-link font-weight-bold ${
statusDone ? "active" : ""
}`}
id="nav-profile-tab"
onClick={() => setDone(true)}
>
done{" "}
<span className="badge badge-success">
{todos.filter((item) => item.done === true).length}
</span>
</a>
</div>
</nav>
{filterTodos.length === 0 ? (
<p>there isn`t any todos</p>
) : (
filterTodos.map((item) => (
<Todo
key={item.key}
item={item}
delete={props.delete}
done={props.done}
edit={props.edit}
/>
))
)}
</>
);
}
main app class
function App() {
const [todos, settodos] = useState([]);
let addTo = (text) => {
settodos((prevState) => {
return {
todos: [prevState.todos, { key: Date.now(), done: false, text }],
};
});
};
return (
<div className="App">
<main>
<section className="jumbotron">
<div className="container d-flex flex-column align-items-center">
<h1 className="jumbotron-heading">Welcome!</h1>
<p className="lead text-muted">
To get started, add some items to your list:
</p>
<FormAddTodo add={addTo} />
</div>
</section>
<div className="todosList">
<div className="container">
<div className="d-flex flex-column align-items-center ">
<TodoList
todos={todos}
// delete={this.deleteTodo.bind(this)}
// done={this.toggleTodo.bind(this)}
// edit={this.editTodo.bind(this)}
/>
</div>
</div>
</div>
</main>
</div>
);
}
I've tried
let filterTodos =Object.values(todos).filter(item => item.done === statusDone)
and error fixed but my code dosen't work true
I hope u understand what I said :)
this functional component is for adding a todo
function FormAddTodo(props) {
const [text, setText] = useState("");
let formHandler = (e) => {
e.preventDefault();
props.add(text);
setText("");
};
let inputHandler = (e) => setText(e.target.value);
return (
<form className="form-inline mb-5" onSubmit={formHandler}>
<div className="row form-group">
<div className="col-8">
<input
type="text"
className=" form-control mx-sm-3"
placeholder="i want to do ..."
value={text}
onChange={inputHandler}
/>
</div>
<div className="col-4">
<button type="submit" className=" btn btn-primary">
add
</button>
</div>
</div>
</form>
);
}

The problem is on addTo function. You are not adding an element to todos array but you are setting todos as an object with a key called todos that contains an array. Try to modify addTo function in this way:
const addTo = (text) => {
let newElement = { key: Date.now(), done: false, text: text };
settodos(prevState => [...prevState, newElement]);
};

You have an error here:
function App() {
const [todos, settodos] = useState([]);
let addTo = (text) => {
settodos((prevState) => {
return {
todos: [prevState.todos, { key: Date.now(), done: false, text }],
};
});
};
your mutation funtion in settodos can try to concat prevState.todos with a new todo.
in fact with a useState setter, you get the value directly:
settodos(currentTodos => ...)
then return the value that you want (you return an object instead of an array)
also, if you want to concat two arrays, use a spread operator:
const newArray = [...someArray, newValue];
so to sum up, here's a fixed version of that piece of code:
function App() {
const [todos, settodos] = useState([]);
let addTo = (text) => {
settodos((prevTodos) => [
...prevTodos,
{ key: Date.now(), done: false, text }
]);
};

Related

How to toggle item by id or index ? React.js

I need to opet child component by clicked item. FIrst check code:
<div className="d-flex">
{boardList.map((list) => (
<div className="card m-3 p-3" key={list.id}>
<div className="d-flex flex-column">
<h6> {list.name} </h6>
<ul className="list-group">
{list.cards.map((card) => (
<li className="list-group-item" key={card.id}>
{card.name}
</li>
))}
</ul>
{isVisible ? (
<TodoForm onCloseForm={onCloseForm} />
) : (
<small
className="mt-2"
onClick={showInput}
>
Add new task +
</small>
)}
</div>
</div>
))}
</div>
This is work but when I click on 'Add new task +' a child component opens up to me everywhere. i want only the component with the selected id or index to open.
also component for this :
const [isVisible, setIsVisible] = useState(false);
const [boardList, setBoardList] = useState([]);
useEffect(() => {
axiosInstance
.get("")
.then((res) => {
setBoardList(res.data);
console.log("resp", boardList);
})
.catch((err) => {
console.log(err);
});
}, []);
const showInput = () => {
setIsVisible(true);
};
const onCloseForm = () => {
setIsVisible(false);
};
All the items of the resultant array from boardList.map are depending on the same state isVisible, that's why when you click on one of them all the items mimic the same behaviour.
What you need is to create a component with its own state to encapsulate this part of your code
{isVisible ? (
<TodoForm onCloseForm={onCloseForm} />
) : (
<small
className="mt-2"
onClick={showInput}
>
Add new task +
</small>
)}
This way every instance of this new component would have its own isVisible so they no longer would affect their siblings state.
The component would look like this.
const NewComponent = () => {
const [isVisible, setIsVisible] = useState(false);
return <>
{isVisible ? (
<TodoForm onCloseForm={onCloseForm} />
) : (
<small className="mt-2" onClick={() => setIsVisible(true)}>
Add new task +
</small>
)}
</>
};

How to add the product to the favorites?

I am currently making a project over the database I created using Mock API. I created a button, created addToFavorites function. When the button was clicked, I wanted the selected product's information to go to the favorites, but I couldn't. I would be glad if you could help me on how to do this.
(Favorites.js empty now. I got angry and deleted all the codes because I couldn't.)
(
Recipes.js
import React, { useState, useEffect } from "react"
import axios from "axios"
import "./_recipe.scss"
import Card from "../Card"
function Recipes() {
const [recipes, setRecipes] = useState([])
const [favorites, setFavorites] = useState([])
useEffect(() => {
axios
.get("https://5fccb170603c0c0016487102.mockapi.io/api/recipes")
.then((res) => {
setRecipes(res.data)
})
.catch((err) => {
console.log(err)
})
}, [])
const addToFavorites = (recipes) => {
setFavorites([...favorites, recipes])
console.log("its work?")
}
return (
<div className="recipe">
<Card recipes={recipes} addToFavorites={addToFavorites} />
</div>
)
}
export default Recipes
Card.js
import React, { useState } from "react"
import { Link } from "react-router-dom"
import { BsClock, BsBook, BsPerson } from "react-icons/bs"
function Card({ recipes, addToFavorites }) {
const [searchTerm, setSearchTerm] = useState("")
return (
<>
<div className="recipe__search">
<input
type="text"
onChange={(event) => {
setSearchTerm(event.target.value)
}}
/>
</div>
<div className="recipe__list">
{recipes
.filter((recipes) => {
if (searchTerm === "") {
return recipes
} else if (
recipes.title.toLowerCase().includes(searchTerm.toLowerCase())
) {
return recipes
}
})
.map((recipe) => {
return (
<div key={recipe.id} className="recipe__card">
<img src={recipe.image} alt="foods" width={350} height={230} />
<h1 className="recipe__card__title">{recipe.title}</h1>
<h3 className="recipe__card__info">
<p className="recipe__card__info--icon">
<BsClock /> {recipe.time} <BsBook />{" "}
{recipe.ingredientsCount} <BsPerson />
{recipe.servings}
</p>
</h3>
<h3 className="recipe__card__desc">
{recipe.description.length < 100
? `${recipe.description}`
: `${recipe.description.substring(0, 120)}...`}
</h3>
<button type="button" className="recipe__card__cta">
<Link
to={{
pathname: `/recipes/${recipe.id}`,
state: { recipe }
}}
>
View Recipes
</Link>
</button>
<button onClick={() => addToFavorites(recipes)}>
Add to favorites
</button>
</div>
)
})}
</div>
</>
)
}
export default Card
Final Output:
I have implemented the addToFavorite() and removeFavorite() functionality, you can reuse it the way you want.
I have to do bit of modification to the code to demonstrate its working, but underlying functionality of addToFavorite() and removeFavotie() works exactly the way it should:
Here is the Card.js where these both functions are implemented:
import React, { useState } from "react";
import { BsClock, BsBook, BsPerson } from "react-icons/bs";
function Card({ recipes }) {
const [searchTerm, setSearchTerm] = useState("");
const [favorite, setFavorite] = useState([]); // <= this state holds the id's of all favorite reciepies
// following function handles the operation of adding fav recipes's id's
const addToFavorite = id => {
if (!favorite.includes(id)) setFavorite(favorite.concat(id));
console.log(id);
};
// this one does the exact opposite, it removes the favorite recipe id's
const removeFavorite = id => {
let index = favorite.indexOf(id);
console.log(index);
let temp = [...favorite.slice(0, index), ...favorite.slice(index + 1)];
setFavorite(temp);
};
// this variable holds the list of favorite recipes, we will use it to render all the fav ecipes
let findfavorite = recipes.filter(recipe => favorite.includes(recipe.id));
// filtered list of recipes
let filtered = recipes.filter(recipe => {
if (searchTerm === "") {
return recipe;
} else if (recipe.title.toLowerCase().includes(searchTerm.toLowerCase())) {
return recipe;
}
});
return (
<div className="main">
<div className="recipe__search">
<input
type="text"
onChange={event => {
setSearchTerm(event.target.value);
}}
/>
</div>
<div className="recipe-container">
<div className="recipe__list">
<h2>all recipes</h2>
{filtered.map(recipe => {
return (
<div key={recipe.id} className="recipe__card">
<img src={recipe.image} alt="foods" width={50} height={50} />
<h2 className="recipe__card__title">{recipe.title}</h2>
<h4 className="recipe__card__info">
<p>
<BsClock /> {recipe.time} <BsBook />{" "}
{recipe.ingredientsCount} <BsPerson />
{recipe.servings}
</p>
</h4>
<h4 className="recipe__card__desc">
{recipe.description.length < 100
? `${recipe.description}`
: `${recipe.description.substring(0, 120)}...`}
</h4>
<button onClick={() => addToFavorite(recipe.id)}>
add to favorite
</button>
</div>
);
})}
</div>
<div className="favorite__list">
<h2>favorite recipes</h2>
{findfavorite.map(recipe => {
return (
<div key={recipe.id} className="recipe__card">
<img src={recipe.image} alt="foods" width={50} height={50} />
<h2 className="recipe__card__title">{recipe.title}</h2>
<h4 className="recipe__card__info">
<p className="recipe__card__info--icon">
<BsClock /> {recipe.time} <BsBook />{" "}
{recipe.ingredientsCount} <BsPerson />
{recipe.servings}
</p>
</h4>
<h4 className="recipe__card__desc">
{recipe.description.length < 100
? `${recipe.description}`
: `${recipe.description.substring(0, 120)}...`}
</h4>
<button onClick={() => removeFavorite(recipe.id)}>
remove favorite
</button>
</div>
);
})}
</div>
</div>
</div>
);
}
export default Card;
Here is the live working app : stackblitz
You can get the previous favourites recipes and add the new ones.
const addToFavorites = (recipes) => {
setFavorites(prevFavourites => [...prevFavourites, recipes])
console.log("its work?")
}

implementing local storage in react application

I am creating a challenge tracking app in React. I would like to, after clicking on the challenge button and approving it, be able to add it and save it to the local storage (as a value to save the name of the chosen challenge) and later to print it in the dashboard.
Could anyone please help me with that.
I have 3 classes I am working now and will paste them below.
ChooseChallenge.js
function Challange() {
const [isPopped, setPop] = useState(false);
const pop = () => {
setPop(!isPopped);
};
return (
//Fragments
<>
{isPopped && <Dialog />}
<div className="chooseChallenge">
{/* <Leaf/> */}
<h1 className="newchallenge">New Challange</h1>
<hr />
<div className="challanges">
<button className="challangeBtn" onClick={pop}>
Eat Vegetarian (31days)
</button>
<button className="challangeBtn" onClick={pop}>
Take the bike to work (14days)
</button>
<button className="challangeBtn" onClick={pop}>
Recycle your plastic bottles (31days)
</button>
<button className="challangeBtn" onClick={pop} >
Use public transport to commute (31days)
</button>
<button className="challangeBtn" onClick={pop}>
Don't fly an airplane (365days)
</button>
</div>
<br />
</div>
</>
);
}
export default Challange;
Dialog.js
function Dialog (){
const [isOpen, setOpennes] = useState(true);
const Close = () => {
setOpennes(false);
}
const [value, setValue] = React.useState(
localStorage.getItem('challengeName') || ''
);
React.useEffect(() => {
localStorage.setItem('challengeName', value);
}, [value]);
const onChange = event => setValue(event.target.value);
return(
<div className={isOpen ? 'dialogBox' : 'dialogHide'}>
<h3 id="header">Do you accept the challange?</h3>
<div className="approvalButtons">
<button className= "approvalButton" onClick = {Close} value={value} onChange={onChange}> Approve </button>
<button className= "approvalButton" onClick = {Close}> Decline </button>
</div>
</div>
)
}
export default Dialog;
Dashboard.js
export default function Dashboard() {
// const challengelist = document.querySelector('#challange-list')
const [challs, setChalls] = useState([]);
useEffect(() => {
const fetchData = async () => {
var challs = [];
await database
.collection("Challenges")
.get()
.then((snapshot) => {
snapshot.docs.forEach((doc) => {
challs.push(doc.data().ChallengeName);
});
});
setChalls(challs);
};
fetchData();
}, []);
return (
<div className="Dashboard">
<Header />
<div className="circle">
<img id="leafpicture" src={leafpic} alt="eco-picture" />
<div className="textIn">
<h1> You saved </h1>
<h5>0.00 CO2</h5>
</div>
</div>
<div>
<ul id="challange-list">
{challs.map((ch) => (
<li key={ch}>{ch}</li>
))}
</ul>
</div>
<div className="progressbar">
<h3>Track your challenges!</h3>
{testData.map((item, idx) => (
<ProgressBar
key={idx}
bgcolor={item.bgcolor}
completed={item.completed}
/>
))}
</div>
<br />
</div>
);
}
on dialog.js the value of the button starts with an empty string and this value never changes, so you are always storing and empty string.

Using React Hooks How Can I Have two search inputs that work together to filter results

I apologize if this is unclear. Let me know if any of this need clarification.
I have two search input. One that sorts by name and another that sorts by tags. Filtering the names was easy enough since I was grabbing that data from an API. However, filtering the data by tags is proving difficult. What would be the best way to set this up?
I have three main components: Search.js Profile.js and Tags.js. Search just passes the user input down. Profile loops over the APi data and filters by name. The Tags component allows the user to add and remove tags. This is placed inside the .map in profile. Since the tags component is inserted into the profile it creates the desired effect of allowing each one to have it's own set of tags, but I can't figure out to transfer the tags info in to state and then filter the profiles that have the searched tag.
Profile Component
const createProfile = (profile) => {
const gradesToNum = profile.grades.map((num) => parseInt(num, 10));
const getAverage = gradesToNum.reduce((a, b) => a + b) / gradesToNum.length;
const getAllGrades = profile.grades.map(renderGrades);
return (
<div key={profile.id} className="profileWrapper">
<div className="profileCard">
<div className="studentImg">
<img src={profile.pic} alt={profile.firstName} />
</div>
<div className="studentBio">
<h3>
{profile.firstName} {profile.lastName}
</h3>
<ul className="studentInfo">
<li>Email: {profile.email}</li>
<li>Company: {profile.company}</li>
<li>Skill: {profile.skill}</li>
<li>Average: {getAverage}%</li>
</ul>
<div className={shownGrades[profile.id] ? 'show' : 'hide'}>
<ul>{getAllGrades}</ul>
<Tags />
</div>
</div>
</div>
<button className="expand-btn" onClick={() => toggleGrade(profile.id)}>
{shownGrades[profile.id] ? (
<i className="fas fa-minus"></i>
) : (
<i className="fas fa-plus"></i>
)}
</button>
</div>
);
};
const getProfile = () =>
props.students.filter(props.filterByName).map(createProfile);
return <section className="wrapper">{getProfile()}</section>;
**Tags Component**
```const Tags = (props) => {
const [tags, setTags] = useState([]);
const addTag = (e) => {
if (e.key === 'Enter' && e.target.value.length > 0) {
setTags([...tags, e.target.value]);
e.target.value = '';
}
};
const removeTags = (indexToRemove) => {
setTags(
tags.filter((x, index) => {
return index !== indexToRemove;
})
);
};
return (
<div className="tags-input">
<ul>
{tags.map((tag, index) => {
return (
<li key={index}>
<span>{tag}</span>
<i className="fas fa-times" onClick={() => removeTags(index)}></i>
</li>
);
})}
</ul>
<input
type="text"
placeholder="press enter to add tag"
onKeyUp={addTag}
id="tag-input"
/>
</div>
);
};```
If you have more complex state handling it's a good practice to use React's useReducer instead of useState for your TagsComponent. https://reactjs.org/docs/hooks-reference.html#usereducer
You can simplify your TagsComponent like:
const initialState = [];
function reducer(state, action) {
switch (action.type) {
case 'addTag':
return [...state, action.payload];
case 'removeTag':
return state.filter(tag => tag !== action.payload)
default:
throw new Error();
}
}
const Tags = (props) => {
const [state, dispatch] = useReducer(reducer, initialState);
const handleAddTag = event => {
if (e.key === 'Enter' && e.target.value.length > 0) {
dispatch({type: 'addTag', payload: e.target.value});
e.target.value = '';
}
}
};
return (
<div className="tags-input">
<ul>
{tags.map((tag, index) => {
return (
<li key={index}>
<span>{tag}</span>
<i className="fas fa-times" onClick={() => dispatch({type: 'removeTag', payload: index})}></i>
</li>
);
})}
</ul>
<input
type="text"
placeholder="press enter to add tag"
onKeyUp={handleAddTag}
id="tag-input"
/>
</div>
);
};

How could i pass a value from a child component to parent, when i have multiple child components?

I creat multiple components, they have the same hierarchy, and inside they i also call other component call , now i want to create functions on my which will update the values i'm passing as props to my component. I manage to pass the functions as props, but can't manage to pass the value from child as parameters to the functions, so i could update only the props especific to that child.
App
function App() {
// eslint-disable-next-line
const [content, setContent] = useState(images)
const [count, setCount] = useState(content.votes)
console.log(count)
const upVote = (id) => {
alert('up =>', id)
}
const downVote = () => {
alert('down')
}
return (
<div className="App">
<div className="grid">
{content.map((picture, index) => {
return <Card key={index} picture={picture} teste={[upVote, downVote]}/>
})
}
</div>
</div>
)
}
Card
function Card({ picture, teste }) {
return (
<div className="card">
<div className="container">
<img
width="300"
alt={`id: ${picture.id}`}
src={picture.src}
className="image"
/>
<Options votes={0} originalPost={picture.original_post} teste={teste[0]}/>
</div>
</div>
)
}
Options
function Options({ votes, originalPost, teste }) {
const [count, setCount] = useState(votes)
const [styling, setStyling] = useState('#696969')
function countStyle(count) {
if (count > 0){
setStyling('#008000')
} else if (count < 0) {
setStyling('#B22222')
} else {
setStyling('#696969')
}
}
return (
<div>
<button onClick={() => teste(count)} className="buttons">teste</button>
<button title="Down vote" onClick={() => {
setCount(count - 1)
countStyle(count-1)
// style(count - 1)
}} className="buttons">-</button>
<span title="Vote counter" style={{color: styling}} className="counter">{count}</span>
<button title="Up vote" onClick={() => {
setCount(count + 1)
// style(count + 1)
countStyle(count +1)
}} className="buttons">+</button><br></br>
<a href={originalPost}
target="_blank"
title="Click to check the original post"
rel="noopener noreferrer"
className="link">Original post</a>
</div>
)
}
I would start by consolidating your state into the App component. Save the votes on your content array on each picture object. Pass the upvote and downvote functions down to each children and call them from your button clicks. I would also calculate the styling based on the props, rather than use state.
App
function App() {
let initialstate = images.map(image => {
image.votes = 0;
return image;
});
const [content, setContent] = useState(initialstate);
const upVote = index => {
setContent(content[index].votes + 1);
};
const downVote = index => {
setContent(content[index].votes - 1);
};
return (
<div className="App">
<div className="grid">
{content.map((picture, index) => {
return <Card key={index} picture={picture} index={index} upVote={upVote} downVote={downVote} />;
})}
</div>
</div>
);
}
Card
function Card({ index, picture, ...props }) {
const upVote = () => props.upVote(index);
const downVote = () => props.downVote(index);
return (
<div className="card">
<div className="container">
<img
width="300"
alt={`id: ${picture.id}`}
src={picture.src}
className="image"
/>
<Options votes={picture.votes} originalPost={picture.original_post} upVote={upVote} downVote={downVote}/>
</div>
</div>
)
Options
function Options({ votes, originalPost, upVote, downVote }) {
let styling = '#696969';
if (count > 0) {
styling = '#008000';
} else if (count < 0) {
styling = '#B22222';
} else {
styling = '#696969';
}
return (
<div>
<button title="Down vote" onClick={downVote} className="buttons">
-
</button>
<span title="Vote counter" style={{ color: styling }} className="counter">
{votes}
</span>
<button title="Up vote" onClick={upVote} className="buttons">
+
</button>
<br></br>
<a
href={originalPost}
target="_blank"
title="Click to check the original post"
rel="noopener noreferrer"
className="link"
>
Original post
</a>
</div>
);
}

Categories