Can't delete an item using useContext and useReducer - reactjs

Please help guys. So I've started to learn about useReducer and useContext and I'm stuck at deleting an object. I've tried to log the result in the console and it prints the expected array that I want. However, when I return the array from the reducer, and whenever I delete an object, it clears the array and the button is left. So I've got this 2 js files below.
useContext & useReducer
import { createContext, useReducer } from "react";
const initialState = {
items: [],
};
const reducer = (state, action) => {
switch (action.type) {
case "ADD_ITEM":
return { ...state, items: [action.payload, ...state.items] };
case "DEL_ITEM":
return {
...state,
items: [state.items.filter((item) => item.id !== action.payload)],
};
default:
return state;
}
};
export const ItemContext = createContext(initialState);
export const ItemProvider = (props) => {
const [state, dispatch] = useReducer(reducer, initialState);
const addItem = (item) => {
dispatch({ type: "ADD_ITEM", payload: item });
};
const delItem = (id) => {
dispatch({ type: "DEL_ITEM", payload: id });
};
return (
<ItemContext.Provider value={{ items: state.items, addItem, delItem }}>
{props.children}
</ItemContext.Provider>
);
};
import React, { useContext, useState } from "react";
import { ItemContext } from "../Context/ItemContext";
const Test2 = () => {
const { items } = useContext(ItemContext);
const { addItem } = useContext(ItemContext);
const { delItem } = useContext(ItemContext);
const [name, setName] = useState("");
const [price, setPrice] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
const newItem = {
id: items.length + 1,
name: name,
price: price,
};
addItem(newItem);
setName("");
setPrice("");
console.log(newItem);
};
const handleDelete = (id) => {
delItem(id);
};
return (
<div>
<div>
<form>
<label>Product Name:</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<label>Product Price:</label>
<input
type="text"
value={price}
onChange={(e) => setPrice(e.target.value)}
/>
<button onClick={handleSubmit}>Submit</button>
</form>
</div>
<div>
{items.map((item) => (
<div key={item.id}>
<li>
<p>{item.name}</p>
<p>{item.price}</p>
</li>
<button onClick={() => handleDelete(item.id)}>X</button>
</div>
))}
</div>
</div>
);
};
export default Test2;

The Array.filter() already returns a new array, so when you're putting it into [], you are creating an array which contains an array as your items as the first element.
case "DEL_ITEM":
return {
...state,
items: state.items.filter((item) => item.id !== action.payload),
};
is therefore correct.

Related

task.name not showing in React Firebase app

I'm working on a to-do list app in React that connects to a Firestore Database and I'm able to send the data correctly to Firebase, but my {task.name} is not displaying. The list numbers and the button are loading, but not {task.name}. The tasks are added to Firebase with the handleAdd function and the tasks are supposed to be loaded with the useEffect function. Here is the code for the main to-do.js file:
import React, { useEffect } from "react";
import { useState } from "react";
import { v4 as uuidv4 } from "uuid";
import { db } from "./utils/firebase";
import { collection, addDoc, Timestamp, query, orderBy, onSnapshot } from "firebase/firestore";
export default function Todo() {
const [name, setName] = useState("");
const [isDoubleClicked, setDoubleClicked] = useState(false);
const [tasks, setTasks] = useState([]);
function handleChange(event) {
event.preventDefault();
setName(event.target.value);
}
const handleAdd = async (e, id) => {
e.preventDefault();
try {
await addDoc(collection(db, 'tasks'), {
name: name,
id: uuidv4(),
completed: false,
created: Timestamp.now(),
})
} catch (err) {
alert(err)
}
}
useEffect(() => {
const q = query(collection(db, 'tasks'), orderBy('created', 'name'))
onSnapshot(q, (querySnapshot) => {
setTasks(querySnapshot.docs.map(doc => ({
id: doc.id,
data: doc.data()
})))
})
console.log(tasks);
}, [])
function handleClick(event) {
if (event.detail === 2) {
console.log("double click");
setDoubleClicked(current => !current);
event.currentTarget.classList.toggle('double-clicked');
}
}
return (
<div>
<div>
<div>
<h1>To Do List App</h1>
<p>Double click to mark an item off, click on "X" to delete an item, and drag items to reorder.</p>
</div>
<input
type="text"
value={name}
onChange={handleChange}
/>
<button
type="submit"
onClick={handleAdd}
>
Add
</button>
</div>
<ol>
{tasks.map((task => (
<li
id={task.id}
key={task.id}
completed={task.data.completed}
onClick={handleClick}
>
{task.name} <button>x</button>
</li>
)))}
</ol>
</div>
);
};
as far as I can tell you are setting task the wrong way, instated of mapping to data stay consistent with the original uploading logic, to avoid such cases you can type your state for the sake of consistency.
useEffect(() => {
const q = query(collection(db, 'tasks'), orderBy('created', 'name'))
onSnapshot(q, (querySnapshot) => {
setTasks(querySnapshot.docs.map(doc => ({
name: doc.data().name,
id: doc.data().id,
completed: doc.data().completed,
created: doc.data().created,
})))
I figured this out. I had to change {task.name} to {task.data.name}. I also updated the useEffect function. Here is the code:
import React, { useEffect } from "react";
import { useState } from "react";
import { v4 as uuidv4 } from "uuid";
import { db } from "./utils/firebase";
import { collection, addDoc, Timestamp, query, orderBy, onSnapshot } from "firebase/firestore";
export default function Todo() {
const [name, setName] = useState("");
const [isDoubleClicked, setDoubleClicked] = useState(false);
const [tasks, setTasks] = useState([]);
function handleChange(event) {
event.preventDefault();
setName(event.target.value);
}
const handleAdd = async (e, id) => {
e.preventDefault();
try {
await addDoc(collection(db, 'tasks'), {
name: name,
id: uuidv4(),
completed: false,
created: Timestamp.now(),
})
} catch (err) {
alert(err)
}
}
useEffect(() => {
const q = query(collection(db, 'tasks'), orderBy('created'))
onSnapshot(q, (querySnapshot) => {
setTasks(querySnapshot.docs.map(doc => ({
data: doc.data()
})))
})
console.log(tasks);
}, [])
function handleClick(event) {
if (event.detail === 2) {
console.log("double click");
setDoubleClicked(current => !current);
event.currentTarget.classList.toggle('double-clicked');
}
}
return (
<div>
<div>
<div>
<h1>To Do List App</h1>
<p>Double click to mark an item off, click on "X" to delete an item, and drag items to reorder.</p>
</div>
<input
type="text"
value={name}
onChange={handleChange}
/>
<button
type="submit"
onClick={handleAdd}
>
Add
</button>
</div>
<ol>
{tasks.map((task => (
<li
id={task.data.id}
key={task.data.id}
completed={task.data.completed}
onClick={handleClick}
>
{task.data.name} <button>x</button>
</li>
)))}
</ol>
</div>
);
};

How to filter or search for services in another component (React)

NOTE: My Page Card component is working correctly. How can I filter the card page component in the Search component?
I'm new to react, and I don't quite understand how I can accomplish this task.
In the Search component I put it in a fixed way, as I can't filter a component using another.
The Code is summarized for ease.
Card Page
import React, {
useEffect,
useState
} from "react";
import classes from "./boxService.module.css";
import axios from "axios";
function BoxService() {
const [test, SetTest] = useState([]);
useEffect(() => {
axios
.get("http://localhost:8080/api/test")
.then((response) => {
SetTest(response.data);
})
.catch(() => {
console.log("Error!");
});
}, []);
return ({
test.map((test, key) => {
<div className={classes.box}
return (
<Grid item xs = {2} key={key} >
<div className={test.name} < div >
<p className={test.description}</p>
</Grid>
);
})}
);
}
export default BoxService;
Seach Page
import React, {
useState,
useEffect
} from "react";
import axios from "axios";
function Search() {
const [searchTerm, setSearchTerm] = useState("");
const [test, SetTest] = useState([]);
//Chamada API
useEffect(() => {
axios
.get("http://localhost:8080/api/test")
.then((response) => {
SetTest(response.data);
})
.catch(() => {
console.log("Error");
});
}, []);
return (
<div>
<input type = "text"
placeholder = "Search..."
onChange = {
(event) => {
setSearchTerm(event.target.value);
}
}/>
{
test.filter((val) => {
if (searchTerm === "") {
return val;
} else if (
val.nome.toLowerCase().includes(searchTerm.toLowerCase())
) {return val;}
}).map((val, key) => {
return ( <div className = "user"
key = {key} >
<p> {val.name} </p> </div>
);
})
} </div>
);
}
export default Search;
Here is an example of how it should/could look like:
import React from "react";
function SearchBox({ setSearchTerm, searchTerm }) {
const handleFilter = (e) => {
setSearchTerm(e.target.value);
};
return (
<>
filter
<input type="search" onChange={handleFilter} value={searchTerm} />
</>
);
}
export default function App() {
const [searchTerm, setSearchTerm] = React.useState("");
const [filteredResults, setFilteredResults] = React.useState([]);
const [results, setResults] = React.useState([]);
React.useEffect(() => {
const fetchdata = async () => {
const randomList = await fetch(`https://randomuser.me/api/?results=50`);
const data = await randomList.json();
const { results } = data;
setResults(results);
};
fetchdata();
}, []);
React.useEffect(() => {
const filterResults = results.filter((item) =>
item.name.last.toLowerCase().includes(searchTerm.toLowerCase())
);
setFilteredResults(filterResults);
}, [searchTerm, results]);
return (
<div className="App">
<SearchBox setSearchTerm={setSearchTerm} searchTerm={searchTerm} />
<div>
<ul>
{filteredResults.map(({ name }, idx) => {
return (
<li key={idx}>
{name.first} {name.last}
</li>
);
})}
</ul>
</div>
</div>
);
}

How to capture logged user in the array of users?

in my CRUD app, the user on the first screen enters their username and then can create, edit, and delete posts. I have an array of users and an array of posts (I'm using Redux). When a user creates a post, their username needs to appear in a sector within the div to identify who posted the post, but I still can't find a way to get the right user who is logged in to show in the div. How could I capture the specific user who is currently logged in?
I thought of doing something like: when the user typed their username and went to the posts screen, all posts would be linked to this user, but I don't know how to do that
postsSlice.js:
import { createSlice } from "#reduxjs/toolkit";
const postsSlice = createSlice({
name: "posts",
initialState: [],
reducers: {
addPost (state, action) {
state.push(action.payload);
},
editPost(state, action) {
const { id, title, content } = action.payload;
const existingPost = state.find((post) => post.id === id);
if (existingPost) {
existingPost.title = title
existingPost.content = content
}
},
postDeleted(state, action) {
const { id } = action.payload;
const existingPost = state.some((post) => post.id === id);
if (existingPost) {
return state.filter((post) => post.id !== id);
}
},
},
});
export const { addPost, editPost, postDeleted } = postsSlice.actions
export default postsSlice
usersSlice.js:
import { createSlice } from "#reduxjs/toolkit";
const userSlice = createSlice({
name: "user",
initialState: [],
reducers: {
saveUser (state, action) {
state.push(action.payload)
},
}
});
export const { saveUser, replaceUsers } = userSlice.actions;
export default userSlice
mainscreen.js:
import React, { useState, useEffect } from "react";
import "../_assets/App.css";
import "../_assets/mainscreen.css";
import { MdDeleteForever } from "react-icons/md";
import { FiEdit } from "react-icons/fi";
import { useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { addPost } from '../redux/postsslice'
import Modal from "../components/modal.jsx";
import EditModal from '../components/editmodal.jsx';
function MainScreen() {
const dispatch = useDispatch();
const navigate = useNavigate();
const user = useSelector((state) => state.user)
const posts = useSelector((state) => state.loadPosts)
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [buttonGreyOut, setButtonGreyOut] = useState("#cccccc");
useEffect(() => {
if (title && content !== "") {
setButtonGreyOut("black");
} else {
setButtonGreyOut("#cccccc");
}
},[title, content]);
const handleSubmitSendPost = (e) => {
e.preventDefault();
dispatch(
addPost({
id: new Date(),
title,
content,
user
})
)
setTitle('')
setContent('')
};
const handleChangeTitle = (text) => {
setTitle(text);
};
const handleChangeContent = (text) => {
setContent(text);
};
const handleButton = (e) => {
e.preventDefault()
navigate("/")
}
const [openEditModal, setOpenEditModal] = useState();
const [openModal, setOpenModal] = useState();
console.log({ posts })
return (
<div className="containerMainScreen">
<button onClick={handleButton}>Back</button>
{openModal && <Modal deleteId={openModal} closeModal={setOpenModal} />}
{openEditModal && <EditModal editId={openEditModal} closeModal={setOpenEditModal} />}
<div className="bar">
<h1>Codeleap</h1>
</div>
<div className="boxPost">
<h2 style={{ fontWeight: 700 }}>What's on your mind?</h2>
<h2>Title</h2>
<form onSubmit={handleSubmitSendPost}>
<input
type="text"
placeholder="Hello World"
name="name"
value={title}
onChange={(e) => handleChangeTitle(e.target.value)}
></input>
<h2>Content</h2>
<textarea
placeholder="Content"
name="content"
value={content}
onChange={(e) => handleChangeContent(e.target.value)}
></textarea>
<button
className="createButton"
type="submit"
style={{ backgroundColor: buttonGreyOut }}
disabled={!title || !content}
>
CREATE
</button>
</form>
</div>
{posts.slice().reverse().map((post) => (
<div className="boxPost" key={post.id}>
<div className="bar">
<h1>{post.title}</h1>
<MdDeleteForever
className="icon"
onClick={() => {
setOpenModal(post.id);
}}
/>
<FiEdit
onClick={() => {
setOpenEditModal(post.id);
}}
style={{ color: "white", fontSize: "45px", paddingLeft: "23px" }}
/>
</div>
<div id="postowner">
<h3>#{user}</h3>
<br></br>
<textarea style={{ border: "none" }} value={post.content}></textarea>
</div>
</div>
))}
</div>
);
}
export default MainScreen;
Signup:
import React, {useState, useEffect} from "react";
import "../_assets/signup.css";
import "../_assets/App.css";
import { useDispatch } from 'react-redux';
import userSlice from '../redux/userslice';
import { useNavigate } from "react-router-dom";
function Signup() {
const navigate = useNavigate();
const dispatch = useDispatch();
const [name, setName] = useState('')
const [buttonGrey, setButtonGrey] = useState('#cccccc')
useEffect(() => {
if (name!== '') {
setButtonGrey("black")
}
else {
setButtonGrey('#cccccc')
}
}, [name])
const handleSubmitForm= (e) => {
e.preventDefault()
dispatch(userSlice.actions.saveUser(name));
navigate("/main")
}
const handleChangeName = (text) => {
setName(text)
}
return (
<div className="container">
<div className="LoginBox">
<form onSubmit={handleSubmitForm}>
<h2>Welcome to codeleap network</h2>
<text>Please enter your username</text>
<input type="text" name="name" value={name} onChange = {e => handleChangeName(e.target.value)} placeholder="Jane Doe" />
<div className="button">
<button type="submit" style={{backgroundColor: buttonGrey}} disabled={!name} >
ENTER
</button>
</div>
</form>
</div>
</div>
);
}
export default Signup;
You can store a current user and maintain a list of all users that ever "signed in".
user.slice.js
const initialState = {
currentUser: '',
users: [],
}
const userSlice = createSlice({
name: "user",
initialState,
reducers: {
saveUser(state, action) {
// person that just signed in
state.currentUser = action.payload;
// list of unique user names
state.users = Array.from(new Set([...state.users, action.payload]));
},
replaceUsers: (state, action) => action.payload
}
});
export const { saveUser } = userSlice.actions;
export default userSlice;
MainScreen.js
Add an author property to each added post, and access the post.author property when mapping the posts.
function MainScreen() {
const dispatch = useDispatch();
const { currentUser } = useSelector((state) => state.user); // <-- current user
const posts = useSelector((state) => state.loadPosts);
...
const handleSubmitSendPost = (e) => {
e.preventDefault();
dispatch(
addPost({
id: uuidV4(),
title,
content,
author: currentUser // <-- current user to post
})
);
setTitle("");
setContent("");
};
...
if (currentUser === "") {
return <Navigate to="/" />;
} else {
return (
<div className="containerMainScreen">
...
{posts.map((post) => (
<div className="boxPost" key={post.id}>
<div className="bar">
<h1>{post.title}</h1>
<MdDeleteForever
className="icon"
onClick={() => {
setOpenModal(post.id);
}}
/>
<FiEdit
onClick={() => {
setOpenEditModal(post.id);
}}
style={{
color: "white",
fontSize: "45px",
paddingLeft: "23px"
}}
/>
</div>
<div id="postowner">
<h3>#{post.author}</h3> // <-- user in post object
<br></br>
<textarea
style={{ border: "none" }}
value={post.content}
></textarea>
</div>
</div>
))}
</div>
);
}
}
export default MainScreen;

React re-renders unnecessary components

I'm new to React, so please explain why this is happens(I guess because I'm re-creating the array of objects, but I'm not sure and don't know how to fix this) and how to fix this this.
I think it's better to show the code, rather than talk about it, so:
App.js
import { useState } from "react";
import Input from "./Input";
let lastId = 2;
function App() {
const [inputsValues, setInputsValues] = useState([
{ id: 1, value: "" },
{ id: 2, value: "" },
]);
const handleChange = (e, id) => {
setInputsValues((prevState) => {
const newState = [...prevState];
const index = newState.findIndex((input) => input.id === id);
newState[index].value = e.target.value;
return newState;
});
};
const addNewInput = () => {
setInputsValues((prevState) => {
const newState = [...prevState];
newState.push({ id: ++lastId, value: "" });
return newState;
});
};
return (
<div className="App">
<div>
<button onClick={addNewInput}>Add new input...</button>
</div>
{inputsValues.map((input) => (
<div className="input-wrap">
<Input
key={input.id}
id={input.id}
value={input.value}
handleChange={handleChange}
/>
</div>
))}
</div>
);
}
export default App;
Input.js
import { useRef } from "react";
const Input = ({ id, value, handleChange }) => {
const renderAmount = useRef(0);
return (
<>
<div> Rendered: {renderAmount.current++}</div>
<input
id={id}
type="text"
value={value}
onChange={(e) => handleChange(e, id)}
/>
</>
);
};
export default Input;
Thanks
Seems like you just have to wrap your Input component in memo so that it only rerenders if its props change. So i imported the memo function in the top and called it on the right side of the assignment, so that it 'wraps' your component and makes it memoized.
import { useRef, memo } from "react";
const Input = memo(({ id, value, handleChange }) => {
const renderAmount = useRef(0);
return (
<>
<div> Rendered: {renderAmount.current++}</div>
<input
id={id}
type="text"
value={value}
onChange={(e) => handleChange(e, id)}
/>
</>
);
});
export default Input;

why my useReducer function is not working.when I use it The page show but function is not working

Hello Everyone . I'm New in react .Every thing is okay But when I'm start using Reducer. It's Not working properly. It's Adding function or deleting function is not working. Why This problem. before using reducer I'm working with context api . and the same project I want work with reducer. but it's not working properly. The page is show but It's Add or remove function is not working
BookContext.jsx
import React, { createContext, useReducer, useEffect } from 'react';
// import { v4 as uuid } from 'uuid' // This is update vertion
import { bookReducer } from '../BookReducer/Reducer';
export const BookContext = createContext();
const BookContextProvider = (props) => {
const [books, dispatch] = useReducer(bookReducer, []);
useEffect(() => {
localStorage.setItem('books', JSON.stringify(books))
}, [books])
return (
<div>
<BookContext.Provider value={{ books, dispatch }}>
{props.children}
</BookContext.Provider>
</div>
);
};
export default BookContextProvider;
Reducer.jsx
import { v4 as uuid } from 'uuid' // This is update vertion
export const bookReducer = (state, action) => {
switch (action.type) {
case ' ADD_BOOK':
return [...state, {
title: action.book.title,
author: action.book.author,
id: uuid()
}]
case 'REMOVE_BOOK':
return state.filter(book => book.id !== action.id)
default:
return state;
}
}
BookForm.jsx
import React, { useContext, useState } from 'react';
import { BookContext } from '../Context/BookContext';
const NewBookform = () => {
const { dispatch } = useContext(BookContext);
const [title, setitle] = useState('');
const [author, setauthor] = useState('');
const Handelesubmit = (e) => {
e.preventDefault()
dispatch({
type: 'ADD_BOOK', book: {
title, author
}
})
setitle(' ');
setauthor(' ');
}
return (
<div>
<form onSubmit={Handelesubmit}>
<input type='text' placeholder='Book Title' value={title} onChange
={(e) => setitle(e.target.value)} required />
<br></br>
<input type='text' placeholder='Book author' value={author} onChange
={(e) => setauthor(e.target.value)} required />
<br></br>
<input type='submit' value=" ADD Book" />
</form>
</div>
);
};
export default NewBookform;
Bookdetails
import React, { useContext } from 'react';
import { BookContext } from '../Context/BookContext';
const Bookldetails = ({ book }) => {
const { dispatch } = useContext(BookContext)
return (
<li onClick={() => dispatch({ type: 'REMOVE_BOOK', id: book.id })}>
<div className="title">{book.title}</div>
<div className="id">{book.author}</div>
</li>
);
};
export default Bookldetails;
Your remove function works just fine, here is your code:
const { createContext, useReducer, useContext } = React;
const uuid = ((id) => () => ++id)(0);
const BookContext = createContext();
const bookReducer = (state, action) => {
switch (action.type) {
case ' ADD_BOOK':
return [
...state,
{
title: action.book.title,
author: action.book.author,
id: uuid(),
},
];
case 'REMOVE_BOOK':
return state.filter((book) => book.id !== action.id);
default:
return state;
}
};
const BookContextProvider = (props) => {
const [books, dispatch] = useReducer(bookReducer, [
{ id: uuid(), title: 'title 1', author: 'author 1' },
{ id: uuid(), title: 'title 2', author: 'author 2' },
]);
//this is not doing anything, you are never reading it
// useEffect(() => {
// localStorage.setItem('books', JSON.stringify(books));
// }, [books]);
return (
<div>
<BookContext.Provider value={{ books, dispatch }}>
{props.children}
</BookContext.Provider>
</div>
);
};
const Bookldetails = ({ book }) => {
const { dispatch } = useContext(BookContext);
return (
<li
onClick={() =>
dispatch({ type: 'REMOVE_BOOK', id: book.id })
}
>
<div className="title">{book.title}</div>
<div className="id">{book.author}</div>
</li>
);
};
const App = () => {
const { books } = useContext(BookContext);
return (
<ul>
{books.map((book) => (
<Bookldetails key={book.id} book={book} />
))}
</ul>
);
};
ReactDOM.render(
<BookContextProvider>
<App />
</BookContextProvider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Resources