Delete task crud in react - reactjs

I manage to delete the tasks (you can see it in the console.log) but I don't know how to render the result. I really appreciate your help. Link CodeSanbox: https://codesandbox.io/s/trello-task-forked-2xsh8?file=/src/App.js
const addItem = (e) => {
e.preventDefault();
const item = { id: uuidv4(), content: text };
const requestedColumnId = Object.entries(columns).find(
(i) => i[1].name === "Requested"
)[0];
const column = columns[requestedColumnId];
setColumns({
...columns,
[requestedColumnId]: {
...column,
items: [...column.items, item]
}
});
setText("");
};
const deleteItem = id => {
const requestedColumnId = Object.entries(columns).find(
(i) => i[1].name === "Requested"
)[0];
const column = columns[requestedColumnId];
const arrFiltered = column.items.filter(item => item.id !== id)
console.log('arrFiltered', arrFiltered)
setColumns({
...columns,
[requestedColumnId]: {
...column,
items: [...column.items]
}
});
}

Here is a straight forward solution. You did it everything correctly but you missed something. Just update your delete function to the following
const deleteItem = (id) => {
const requestedColumnId = Object.entries(columns).find(
(i) => i[1].name === "Requested"
)[0];
const column = columns[requestedColumnId];
setColumns({
...columns,
[requestedColumnId]: {
...column,
items: [...column.items.filter((item) => item.id !== id)]
}
});
};
Your mistake is that you're filtering the array upon deletion. But you're not updating the main item. So I solved it by adding the array filter in to the main item and removing your filter. Just like this.
setColumns({
...columns,
[requestedColumnId]: {
...column,
items: [...column.items.filter((item) => item.id !== id)]
}
});

Related

Reacts generate two input feilds dynamically instead of one

So this is my app which i have created in react and store data in firestore, i have a form in which ingredients is an array, i can dynamically add the input feilds and when i submit the form it gets submiited and the data gets stored in firebase. My problem is when i click the add feild button instead of one feild two feilds are simultaneously created and i am unable to understand how to do that so if anyone can explain what to do thanks .
code :
function App() {
const [recipes, setRecipes] = useState([])
const [form, setForm] = useState({
ingredients: [],
})
const [popupActive, setPopupActive] = useState(false)
const recipesCollectionRef = collection(db, "recipes")
useEffect(() => {
onSnapshot(recipesCollectionRef, snapshot => {
setRecipes(snapshot.docs.map(doc => {
return {
id: doc.id,
viewing: false,
...doc.data()
}
}))
})
}, [])
const handleView = id => {
const recipesClone = [...recipes]
recipesClone.forEach(recipe => {
if (recipe.id === id) {
recipe.viewing = !recipe.viewing
} else {
recipe.viewing = false
}
})
setRecipes(recipesClone)
}
const handleSubmit = e => {
e.preventDefault()
if (
!form.ingredients ||
) {
alert("Please fill out all fields")
return
}
addDoc(recipesCollectionRef, form)
setForm({
ingredients: [],
})
setPopupActive(false)
}
const handleIngredient = (e, i) => {
const ingredientsClone = [...form.ingredients]
ingredientsClone[i] = e.target.value
setForm({
...form,
ingredients: ingredientsClone
})
}
const handleIngredientCount = () => {
setForm({
...form,
ingredients: [...form.ingredients, ""]
})
{ recipe.viewing && <div>
<h4>Ingredients</h4>
<ul>
{ recipe.ingredients.map((ingredient, i) => (
<li key={i}>{ ingredient }</li>
))}
</ul>
As far as I have understood. Just do like below,
const handleIngredientCount = () => {
setForm({
...form,
ingredients: [...form.ingredients, "", ""],
})
}
You will be created with two input fields simultaneously instead of one when you click the add ingredient button.

Array containing objects is duplicating them rather than overwriting their values

I'm creating a shopping cart app. When the user changes the quantity of an item, the price of the item should be updated along with it. However, in trying to implement this, I've encountered a bug where the items in the shopping cart are being duplicated rather than updated. Any help would be appreciated. Here is my code:
const [cart, setCart] = useState([]);
const handleQuantityChange = (e, product) => {
setCart((prevState) => [
...prevState,
...prevState.map((item) => {
if (item.id === product.id) {
return {
...item,
price: item.originalPrice * e.target.value,
quantity: e.target.value,
};
} else {
return item;
}
}),
]);
}
[...prevState, ...prevState.map()] is duplicating your list twice (one is the prevState, one is prevState.map())
You should modify your code like this
const [cart, setCart] = useState([]);
const handleQuantityChange = (e, product) => {
setCart((prevState) => [
...prevState.map((item) => {
if (item.id === product.id) {
return {
...item,
price: item.originalPrice * e.target.value,
quantity: e.target.value,
};
} else {
return item;
}
}),
]);
}
Another way without prevState but cart state
const [cart, setCart] = useState([]);
const handleQuantityChange = (e, product) => {
const updatedCart = cart.map((item) => {
if (item.id === product.id) {
return {
...item,
price: item.originalPrice * e.target.value,
quantity: e.target.value,
};
} else {
return item;
}
});
setCart(updatedCart);
}
[...prevState, ...prevState.map()] is duplicated one.
You can find the corresponding item in prevState by find function and update its price and quantity.
You can update setCart function call like the following.
setCart(prevState => {
const newState = prevState? [...prevState] : [];
const item = newState.find(x => x.id === product.id);
let qty = parseFloat(e.target.value);
qty = isNaN(qty) ? 0 : qty;
item.price = item.originalPrice * qty;
item.quantity = qty;
return newState;
});

Increment button using map method in react

Below code of incrementing the button. But am facing the issue in the increment function to increment the value.
const [items, setItems] = useState([
{itemName:'item1', quantity:2, isSelected:false},
{itemName:'item2', quantity:5, isSelected:false},
{itemName:'item3', quantity:7, isSelected:false}
]);
const increment = (index) => {
setItems([...items,
index.quantity++
]) }
<button onClick={() => increment(index)}>increment</button>
You are adding an item to the items array instead of editing the desired item, remember that you should also treat state immutably:
const increment = (index) => {
setItems((items) => {
const prevItem = items[index];
return Object.assign([], items, {
[index]: { ...prevItem, quantity: prevItem.quantity + 1 },
});
});
};

React hook useEffect failed to read new useState value that is updated with firebase's firestore realtime data

I have an array of data object to be rendered. and this array of data is populated by Firestore onSnapshot function which i have declared in the React hook: useEffect. The idea is that the dom should get updated when new data is added to firestore, and should be modified when data is modified from the firestore db.
adding new data works fine, but the problem occurs when the data is modified.
here is my code below:
import React, {useState, useEffect} from 'react'
...
const DocList = ({firebase}) => {
const [docList, setDocList] = useState([]);
useEffect(() => {
const unSubListener = firebase.wxDocs()
.orderBy("TimeStamp", "asc")
.onSnapshot({
includeMetadataChanges: true
}, docsSnap => {
docsSnap.docChanges()
.forEach(docSnap => {
let source = docSnap.doc.metadata.fromCache ? 'local cache' : 'server';
if (docSnap.type === 'added') {
setDocList(docList => [{
source: source,
id: docSnap.doc.id,
...docSnap.doc.data()
}, ...docList]);
console.log('document added: ', docSnap.doc.data());
} // this works fine
if (docSnap.type === 'modified') {
console.log('try docList from Lists: ', docList); //this is where the problem is, this returns empty array, i don't know why
console.log('document modified: ', docSnap.doc.data()); //modified data returned
}
})
})
return () => {
unSubListener();
}
}, []);
apparently, i know the way i declared the useEffect with empty deps array is to make it run once, if i should include docList in the deps array the whole effect starts to run infinitely.
please, any way around it?
As commented, you could have used setDocList(current=>current.map(item=>..., here is working example with fake firebase:
const firebase = (() => {
const createId = ((id) => () => ++id)(0);
let data = [];
let listeners = [];
const dispatch = (event) =>
listeners.forEach((listener) => listener(event));
return {
listen: (fn) => {
listeners.push(fn);
return () => {
listeners = listeners.filter((l) => l !== fn);
};
},
add: (item) => {
const newItem = { ...item, id: createId() };
data = [...data, newItem];
dispatch({ type: 'add', doc: newItem });
},
edit: (id) => {
data = data.map((d) =>
d.id === id ? { ...d, count: d.count + 1 } : d
);
dispatch({
type: 'edit',
doc: data.find((d) => d.id === id),
});
},
};
})();
const Counter = React.memo(function Counter({ up, item }) {
return (
<button onClick={() => up(item.id)}>
{item.count}
</button>
);
});
function App() {
const [docList, setDocList] = React.useState([]);
React.useEffect(
() =>
firebase.listen(({ type, doc }) => {
if (type === 'add') {
setDocList((current) => [...current, doc]);
}
if (type === 'edit') {
setDocList((current) =>
current.map((item) =>
item.id === doc.id ? doc : item
)
);
}
}),
[]
);
const up = React.useCallback(
(id) => firebase.edit(id),
[]
);
return (
<div>
<button onClick={() => firebase.add({ count: 0 })}>
add
</button>
<div>
{docList.map((doc) => (
<Counter key={doc.id} up={up} item={doc} />
))}
</div>
</div>
);
}
ReactDOM.render(<App />, 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>
You can do setDocList(docList.map... but that makes docList a dependency of the effect: useEffect(function,[docList]) and the effect will run every time docList changes so you need to remove the listener and idd it every time.
In your code you did not add the dependency so docList was a stale closure. But the easiest way would be to do what I suggested and use callback for setDocList: setDocList(current=>current.map... so docList is not a dependency of the effect.
The comment:
I don't think setDocList, even with the the prevState function, is guaranteed to be up to date by the time you get into that if statement
Is simply not true, when you pass a callback to state setter the current state is passed to that callback.
Based on #BrettEast suggestion;
I know this isn't what you want to hear, but I would probably suggest using useReducer reactjs.org/docs/hooks-reference.html#usereducer, rather than useState for tracking an array of objects. It can make updating easier to track. As for your bug, I don't think setDocList, even with the the prevState function, is guaranteed to be up to date by the time you get into that if statement.
I use useReducer instead of useState and here is the working code:
import React, {useReducer, useEffect} from 'react'
import { withAuthorization } from '../../Session'
import DocDetailsCard from './Doc';
const initialState = [];
/**
* reducer declaration for useReducer
* #param {[*]} state the current use reducer state
* #param {{payload:*,type:'add'|'modify'|'remove'}} action defines the function to be performed and the data needed to execute such function in order to modify the state variable
*/
const reducer = (state, action) => {
switch (action.type) {
case 'add':
return [action.payload, ...state]
case 'modify':
const modIdx = state.findIndex((doc, idx) => {
if (doc.id === action.payload.id) {
console.log(`modified data found in idx: ${idx}, id: ${doc.id}`);
return true;
}
return false;
})
let newModState = state;
newModState.splice(modIdx,1,action.payload);
return [...newModState]
case 'remove':
const rmIdx = state.findIndex((doc, idx) => {
if (doc.id === action.payload.id) {
console.log(`data removed from idx: ${idx}, id: ${doc.id}, fullData: `,doc);
return true;
}
return false;
})
let newRmState = state;
newRmState.splice(rmIdx,1);
return [...newRmState]
default:
return [...state]
}
}
const DocList = ({firebase}) => {
const [state, dispatch] = useReducer(reducer, initialState)
useEffect(() => {
const unSubListener = firebase.wxDocs()
.orderBy("TimeStamp", "asc")
.onSnapshot({
includeMetadataChanges: true
}, docsSnap => {
docsSnap.docChanges()
.forEach(docSnap => {
let source = docSnap.doc.metadata.fromCache ? 'local cache' : 'server';
if (docSnap.type === 'added') {
dispatch({type:'add', payload:{
source: source,
id: docSnap.doc.id,
...docSnap.doc.data()
}})
}
if (docSnap.type === 'modified') {
dispatch({type:'modify',payload:{
source: source,
id: docSnap.doc.id,
...docSnap.doc.data()
}})
}
if (docSnap.type === 'removed'){
dispatch({type:'remove',payload:{
source: source,
id: docSnap.doc.id,
...docSnap.doc.data()
}})
}
})
})
return () => {
unSubListener();
}
}, [firebase]);
return (
<div >
{
state.map(eachDoc => (
<DocDetailsCard key={eachDoc.id} details={eachDoc} />
))
}
</div>
)
}
const condition = authUser => !!authUser ;
export default React.memo(withAuthorization(condition)(DocList));
also according to #HMR, using the setState callback function:
here is the updated code which also worked if you're to use useState().
import React, { useState, useEffect} from 'react'
import { withAuthorization } from '../../Session'
import DocDetailsCard from './Doc';
const DocList = ({firebase}) => {
const [docList, setDocList ] = useState([]);
const classes = useStyles();
useEffect(() => {
const unSubListener = firebase.wxDocs()
.orderBy("TimeStamp", "asc")
.onSnapshot({
includeMetadataChanges: true
}, docsSnap => {
docsSnap.docChanges()
.forEach(docSnap => {
let source = docSnap.doc.metadata.fromCache ? 'local cache' : 'server';
if (docSnap.type === 'added') {
setDocList(current => [{
source: source,
id: docSnap.doc.id,
...docSnap.doc.data()
}, ...current]);
console.log('document added: ', docSnap.doc.data());
}
if (docSnap.type === 'modified') {
setDocList(current => current.map(item => item.id === docSnap.doc.id ? {
source: source,
id: docSnap.doc.id,
...docSnap.doc.data()} : item )
)
}
if (docSnap.type === 'removed'){
setDocList(current => {
const rmIdx = current.findIndex((doc, idx) => {
if (doc.id === docSnap.doc.id) {
return true;
}
return false;
})
let newRmState = current;
newRmState.splice(rmIdx, 1);
return [...newRmState]
})
}
})
})
return () => {
unSubListener();
}
}, [firebase]);
return (
<div >
{
docList.map(eachDoc => (
<DocDetailsCard key={eachDoc.id} details={eachDoc} />
))
}
</div>
)
}
const condition = authUser => !!authUser ;
export default React.memo(withAuthorization(condition)(DocList));
Thanks hope this help whoever is experiencing similar problem.

Can't display elements of array React

I can see my array in state, but I don't know why elements of array doesn't display on the app interface.
const [members, setMembers] = useState([])
useEffect( () => {
getMembers();
}, [props.event])
const getMembers = () => {
let new_members = [];
console.log(props.event)
props.event && props.event.uczestnicy.map(member => {
member.get().then(doc => {
let new_member;
new_member = {
...doc.data(),
id: doc.id
}
new_members.push(new_member)
})
setMembers(new_members)
})
console.log(new_members)
console.log(members)
}
[...]
{members && members.map(member => {
console.log('mem',member)
return(
<div key={member.id}>
{member.nick}
</div>
)
})}
So I can see this array in Components using React Developer Tools, but even console.log doesn't see it in the moment of performing.
And console.log(new_members) and console.log(members) result :
Your member values are fetch asynchronously, so its ideal if you set state only after all the values are resolved. For this you can use a Promise.all
const getMembers = async () => {
let new_members = [];
console.log(props.event)
if(props.event) {
const val = await Promise.all(props.event.uczestnicy.map(member => {
return member.get().then(doc => {
let new_member;
new_member = {
...doc.data(),
id: doc.id
}
return new_member
})
});
setMembers(values);
console.log(values);
}
}

Resources