I have a problem with update my parent component when I try delete item from database.
I create simply get items from my database:
const filter = useFilter((query) => query.eq("createDate", date), [date]);
const [{ data, fetching, error }] = useSelect("payments_subitems", {
filter,
});
and I return by simply Map function.
BUT, I create another component to delete items from my database:
<RemoveItem id={date} />
const supabaseClient = useSupabaseClient();
const removeAction = async () => {
try {
const { data, error } = await supabaseClient
.from("payments_items")
.delete()
.eq("date", id);
if (error) throw error;
} catch (error: any) {
alert(error.message);
}
};
and I return simple button:
<Button
className="w-full text-white bg-red-600 hover:bg-red-700"
onClick={() => removeAction()}
>
{title}
</Button>
Delete action works correctly... but when I delete an item- the parent component doesn't change the list of items in my accordion listing :(
Anybody can help me?
I try to use useEffect but doesn't work :/ I don't know what I'm doing wrong :(
My parent component:
export function GetPaymentsSubitems({ date }: GetPaymentsSubitemsProps) {
const filter = useFilter((query) => query.eq("createDate", date), [date]);
const [{ data, fetching, error }] = useSelect("payments_subitems", {
filter,
});
return (
<>
{data?.map(({ title, value }, index) => {
return (
<div key={index} className="flex justify-between">
<div>{title}</div>
<div>{value}</div>
</div>
);
})}
</>
);
}
export function GetPaymentsAccordion({
userId,
children,
}: GetPaymentsAccordionProps) {
const payments: any = useSelector<any>((state) => state.payments);
const countReduxPayments = payments.payments.length;
const filter = useFilter((query) => query.eq("author", userId), [userId]);
const [{ data, fetching, error }] = useSelect("payments_items", {
filter,
pause: !userId,
});
const [expanded, setExpanded] = useState<string | false>(false);
const handleChange =
(panel: string) => (event: React.SyntheticEvent, isExpanded: boolean) => {
setExpanded(isExpanded ? panel : false);
};
if (error)
return (
<Alert severity="error">
<AlertTitle>Błąd</AlertTitle>
{error.message}
</Alert>
);
if (fetching)
return (
<div className="flex items-center justify-center">
<CircularProgress />
</div>
);
if (data?.length === 0 && countReduxPayments === 0)
return (
<Alert severity="info">
<AlertTitle>Brak danych</AlertTitle>
Nie posiadasz żadnych płatności
</Alert>
);
return (
<>
{data?.map(({ date }, index) => {
return (
<React.Fragment key={index}>
<Accordion
expanded={expanded === date}
onChange={handleChange(date)}
key={index}
>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
{date}
</AccordionSummary>
<AccordionDetails>
<GetPaymentsSubitems date={date} />
<RemoveItem id={date} />
</AccordionDetails>
</Accordion>
{children && children}
</React.Fragment>
);
})}
</>
);
}
Thanks!
You can add reexecute param under useSelect
const filter = useFilter((query) => query.eq("createDate", date), [date]);
const [{ data, fetching, error }, reexecute] = useSelect("payments_subitems", {
filter,
});
And then add another prop with this function execution to refresh your data like below
<RemoveItem id={date} refresh={() => { reexecute() }}/>
And the final step is adding that refresh() prop into removeAction
const supabaseClient = useSupabaseClient();
const removeAction = async () => {
try {
const { data, error } = await supabaseClient
.from("payments_items")
.delete()
.eq("date", id);
refresh(); //refresh your data here
if (error) throw error;
} catch (error: any) {
alert(error.message);
}
};
Related
So I'm using NextJS and tailwind to create a notification provider for my app, however when I display multiple notifications and the top one gets removed the one underneath it takes over it's fade value, how do I fix this?
import { createContext, useState, useContext } from "react";
const Context = createContext();
const Provider = ({ children }) => {
const [notifications, setNotifications] = useState([]);
const exposed = {
addNotification: (type, text, autoClose) => {
const id = Math.random()
setNotifications(notifications => [...notifications, { id, type, text, autoClose, fade: false }]);
if (autoClose) {
setTimeout(() => {
removeNotification(id);
}, 5000);
}
},
};
const removeNotification = (id) => {
setNotifications(notifications => notifications.map(n => n.id === id ? { ...n, fade: true } : n));
setTimeout(() => {
setNotifications(notifications => notifications.filter(n => n.id !== id));
}, 1000);
}
return (
<Context.Provider value={exposed}>
{children}
{notifications.length > 0 ? <div className="z-50 fixed top-5 right-5 text-right">
{notifications.map((notification, index) => {
switch (notification.type) {
case 'info':
return <Info text={notification.text} key={index} remove={() => removeNotification(notification.id)} fade={notification.fade} />
/* other cases */
}
})}
</div> : null}
</Context.Provider>
);
}
function Info({ remove, text, fade }) {
return (
<div className={`flex items-center w-fit mt-2 mr-0 ml-auto transition-opacity ease-in duration-1000 ${!fade?'opacity-100':'opacity-0'}`}>
{/* content */}
</div>
)
}
export const useProvider = () => useContext(Context);
export default Provider;
There might be other issues to be addressed, but for a list with dynamically changing items, consider to avoid using index as unique key to prevent potential conflicts when the list changes.
Perhaps try use notification.id as key in the posted example:
<Context.Provider value={exposed}>
{children}
{notifications.length > 0 ? (
<div className="z-50 fixed top-5 right-5 text-right">
{notifications.map((notification, index) => {
switch (notification.type) {
case "info":
return (
<Info
text={notification.text}
// 👇 Use id as unique keys
key={notification.id}
remove={() => removeNotification(notification.id)}
fade={notification.fade}
/>
);
/* other cases */
}
})}
</div>
) : null}
</Context.Provider>
** As you can see i am taking input from the user and display and wanted data display on screen according to the year which you select( filter data according to year) and if there is no item i wanted to display found no expense **
this is my Expenses item code
const ExpenseAll = (props) => {
const [filteredYear, setFilteredYear] = useState("2020");
const filterChangeHandler = (selectedYear) => {
setFilteredYear(selectedYear);
};
const filteredExpenses = props.items.filter((expense) => {
return expense.date.getFullYear().toString() === filteredYear;
});
return (
<div>
<Card className="expenses">
<ExpensesFilter
selected={filteredYear}
onChangeFilter={filterChangeHandler}
/>
<ExpensesList items={filteredExpenses} />
</Card>
</div>
);
};
this is my condition filter code which is not working showing empty screen
if (props.items.length === 0) {
return <h2 className="expenses-list__fallback"> Found no Expense</h2>;
}
return (
<ul className="expenses-list">
{props.items.map((expense) => (
<ExpenseItem
key={expense.id}
title={expense.title}
amount={expense.amount}
date={expense.date}
/>
))}{" "}
;
</ul>
);
}
Try to store filteredExpenses in a state with default value:
const ExpenseAll = (props) => {
const [filteredExpenses, setFilteredExpenses] = useState([]);
const [filteredYear, setFilteredYear] = useState('2020');
const filterChangeHandler = (selectedYear) => {
setFilteredYear(selectedYear);
};
useEffect(() => {
const filtered = props.items.filter((expense) => {
return expense.date.getFullYear().toString() === filteredYear;
});
setFilteredExpenses(filtered)
}, []);
return (
<div>
<Card className='expenses'>
<ExpensesFilter
selected={filteredYear}
onChangeFilter={filterChangeHandler}
/>
<ExpensesList items={filteredExpenses} />
</Card>
</div>
);
};
Im using Redux-toolkit (already using it for a while). When I reset my redux store, and load my website for the first time the SharedList component does not update if I update the store.
But the strange thing... after refreshing the page, everything works perfect!
Im using the [...array] and Object.assign({}) methods. Also console logged, and the objects are NOT the same.
this is the reducer: (Only updates the ReactJS component after I refresh my page)
CREATE_SHARED_LIST_ITEM_LOCAL: (
proxyState,
action: PayloadAction<{
active: boolean;
checked: boolean;
id: string;
text: string;
}>
) => {
const state = current(proxyState);
const [groupIndex, group] = getCurrentGroupIndex(state);
const { id, text, active, checked } = action.payload;
const listItemIndex = group.shared_list.findIndex((item) => {
return item.id === id;
});
const group_id =
proxyState.groups[groupIndex].shared_list[listItemIndex].group_id;
const sharedItem: SharedListItem = {
text,
id,
checked,
active,
group_id,
};
proxyState.groups[groupIndex].shared_list[listItemIndex] = Object.assign(
{},
sharedItem
);
},
This is the ReactJS component
const GroceryList: React.FC = () => {
const [update, setUpdate] = useState(false);
const handleUpdate = () => {};
const dispatch = useDispatch();
const listItems = useSelector(selectSharedList);
const { setTitle } = useContext(HeaderContext);
const editItem = async (
id: string,
checked: boolean,
text: string,
active: boolean
) => {
dispatch(MUTATE_SHARED_LIST_LOCAL({ id, text, active, checked }));
};
const fetchList = async () => {
await dispatch(QUERY_SHARED_LIST({}));
};
useEffect(() => {
setTitle("Lijst");
fetchList();
}, []);
const list = listItems.filter((item) => {
return item.active;
});
return (
<>
<Card
// style={{
// paddingBottom: 56,
// }}
>
<CardContent>
<List>
{list.length === 0 ? (
<Box textAlign="center" justifyContent="center">
{/* <Center> */}
<Box>
<EmptyCartIcon style={{ width: "45%" }} />
</Box>
{/* </Center> */}
<Typography>De gedeelte lijst is leeg</Typography>
</Box>
) : null}
{list.map((item, index) => {
const { checked, text, id, active } = item;
return (
<ListItem dense disablePadding key={"item-" + index}>
<Checkbox
checked={checked}
onClick={() => {
editItem(id, !checked, text, active);
}}
/>
<EditTextSharedListItem item={item} />
<Box>
<IconButton
onClick={() => {
editItem(id, checked, text, false);
}}
>
<DeleteOutlineRoundedIcon />
</IconButton>
</Box>
</ListItem>
);
})}
</List>
</CardContent>
</Card>
<AddSharedListItem />
</>
);
};
This is the selector
export const selectSharedList = (state: RootState) => {
const group = selectCurrentGroup(state);
return group.shared_list.filter((item) => {
return item.active;
});
};
Hello everyone :D I need your advise/tip. Right now I have a APIDataTable component. It has its rows, columns and etc. This component is responsible to show/build data table on frontend with search bar in it above the table. I have an search bar, which is not functional right now. I want it to search data from data table. What should I start from? How can i make it perform search in Table. Thank you for any advise and tip <3
import React, { useEffect, useState } from "react";
import { plainToClassFromExist } from "class-transformer";
import { Pagination } from "../../models/Pagination";
import {
DataTable,
DataTableHead,
DataTableHeadCell,
DataTableBody,
DataTableRow,
DataTableCell,
} from "../DataTable";
import { request } from "../../api";
import "./index.css";
import { MenuSurface } from "../MenuSurface";
import { IconButton } from "../IconButton";
import { Checkbox } from "../Checkbox";
import { Dialog } from "../Dialog";
import { GridCell, GridRow } from "../Grid";
import { Button } from "../Button";
export class Column<T> {
label: string;
width?: number;
filter?: JSX.Element;
render: (row: T) => JSX.Element | string;
constructor(column: Partial<Column<T>>) {
Object.assign(this, column);
}
}
type APIDataTableProps<T> = {
apiPath?: string;
params?: string;
columns?: Column<T>[];
type: Function;
onRowClick?: (row: T) => void;
};
export const APIDataTable = <T extends object>({
apiPath,
params,
columns,
type,
onRowClick,
}: APIDataTableProps<T>) => {
const [data, setData] = useState<Pagination<T>>(null);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(15);
const [isLoading, setIsLoading] = useState(false);
const [isDialogOpen, setDialogOpen] = useState(false);
const [isMenuSurFaceOpen, setMenuSurfaceOpen] = useState(false);
const [hiddenColumns, setHiddenColumns] = useState<number[]>(
JSON.parse(localStorage.getItem(`hiddenColumns-${apiPath + params}`)) || []
);
const fetchData = async () => {
const urlSearchParams = new URLSearchParams(params);
urlSearchParams.set("page", page.toString());
urlSearchParams.set("page_size", pageSize.toString());
const url = `${apiPath}?${urlSearchParams}`;
const response = await request(url);
const data = plainToClassFromExist(new Pagination<T>(type), response, {
excludeExtraneousValues: true,
});
setData(data);
setIsLoading(false);
};
useEffect(() => {
if (!!apiPath) {
setIsLoading(true);
fetchData();
}
}, [page, pageSize]);
const headCells = columns
.filter((e, i) => !hiddenColumns?.includes(i))
.map((column) => (
<DataTableHeadCell key={column.label} width={column.width}>
{column.label}
</DataTableHeadCell>
));
const rows = data?.results?.map((row, index) => (
<DataTableRow
key={"row-" + index}
onClick={() => !!onRowClick && onRowClick(row)}
>
{columns
.filter((e, i) => !hiddenColumns?.includes(i))
.map((column) => {
return (
<DataTableCell key={column.label} width={column.width}>
<div className="data-table-cell-text">{column.render(row)}</div>
</DataTableCell>
);
})}
</DataTableRow>
));
let uncheckedCheckboxes = hiddenColumns;
const onCheckboxChange = (index: number, value: boolean) => {
if (!value) {
uncheckedCheckboxes.push(index);
//setHiddenColumns(uncheckedCheckboxes);
} else {
const array = [...uncheckedCheckboxes];
array.splice(array.indexOf(index), 1);
uncheckedCheckboxes = array;
}
};
const [isOpen, setIsOpen] = useState(false);
return (
<div className="data-table-container">
<div className="search-test">
<div className="mdc-menu-surface--anchor">
<label
className="mdc-text-field mdc-text-field--filled mdc-text-field--no-label mdc-text-field--with-leading-icon mdc-text-field--with-trailing-icon"
htmlFor="input"
id="search-menu-surface"
>
<IconButton density={-1} icon="search" />
<input
className="mdc-text-field__input "
type="text"
placeholder="Поиск"
id="searchinput"
/>
<IconButton
density={-1}
icon="arrow_drop_down"
onClick={() => {
setMenuSurfaceOpen(true);
}}
/>
</label>
<MenuSurface
isOpen={isMenuSurFaceOpen}
onClose={() => setMenuSurfaceOpen(false)}
fullwidth
>
<div className="data-table-filters-container">
{columns.map(
(column) =>
!!column.filter && (
<div className="data-table-filter">
<div className="data-table-filter-label mdc-typography--subtitle1">
{column.label}
</div>
<div className="data-table-column-filter">
{column.filter}
</div>
</div>
// <GridRow>
// <GridCell span={3}>{column.label}</GridCell>
// <GridCell span={3}>{column.filter}</GridCell>
// </GridRow>
)
)}
{/* <GridCell span={2}> */}
{/* <Button label="Поиск" raised /> */}
{/* <Button
label="Отмена"
raised
onClick={() => {
setIsOpen(false);
}}
/> */}
{/* </GridCell> */}
</div>
</MenuSurface>
</div>
<IconButton
onClick={() => {
setDialogOpen(true);
}}
density={-1}
icon="settings"
/>
<Dialog
isOpen={isDialogOpen}
onOkClick={() => {
localStorage.setItem(
`hiddenColumns-${apiPath + params}`,
JSON.stringify(uncheckedCheckboxes)
);
setDialogOpen(false);
setHiddenColumns(uncheckedCheckboxes);
}}
onCloseClick={() => setDialogOpen(false)}
>
<div
style={{
display: "flex",
flexDirection: "column",
}}
>
{columns.map((column, index) => (
<Checkbox
label={column.label}
onChange={(value) => onCheckboxChange(index, value)}
defaultChecked={!uncheckedCheckboxes.includes(index)}
/>
))}
</div>
</Dialog>
</div>
<DataTable
pagination={true}
count={data?.count}
rowsNumber={data?.results.length}
page={page}
next={data?.next}
previous={data?.previous}
isLoading={isLoading}
onNextClick={() => setPage(page + 1)}
onPreviosClick={() => setPage(page - 1)}
>
<DataTableHead>{headCells}</DataTableHead>
<DataTableBody>{rows}</DataTableBody>
</DataTable>
</div>
);
};
I'm guessing that you want to search bar to effectively filter out rows that don't match. in this case what you want to do is add a filter to the search text (naturally you'll add a state for the search value, but it looks like you'll have that handled.
You'll add your filter here const rows = data?.results?.filter(...).map
You filter function will look something like this
const rows = data?.results.filter((row) => {
// In my own code if I have other filters I just make them return false
// if they don't match
if (
searchText &&
!(
// exact match example
row.field === searchText ||
// case-insensitive example
row.otherField?.toLowerCase().includes(searchText)
// can continue with '||' and matching any other field you want to search by
)
)
return false;
return true;
}).map(...)
I'm trying to figure out how to edit a todo item in my react app using hooks, but I can't seem to figure out how to write the code.
Most of the solutions I've seen online are using class components and it's not written with the same logic as my app.
Here is my current code
function TodoList() {
const [todos, setTodos] = useState([]);
const addTodo = todo => {
if (!todo.text || /^\s*$/.test(todo.text)) {
return;
}
const newTodos = [todo, ...todos];
setTodos(newTodos);
console.log(newTodos);
};
const removeTodo = id => {
const removedArr = [...todos].filter(todoId => todoId.id !== id);
setTodos(removedArr);
};
const completeTodo = id => {
let updatedTodos = todos.map(todo => {
if (todo.id === id) {
todo.isComplete = !todo.isComplete;
}
return todo;
});
setTodos(updatedTodos);
};
const editTodo = e => {
setTodos(e.target.value);
};
return (
<>
<TodoForm onSubmit={addTodo} />
{todos.map(todo => (
<div>
<div
key={todo.id}
className={todo.isComplete ? 'complete' : ''}
key={todo.id}
onClick={() => completeTodo(todo.id)}
>
{todo.text}
</div>
<FaWindowClose onClick={() => removeTodo(todo.id)} />
</div>
))}
</>
);
}
Here is the code from the other component
function TodoForm(props) {
const [input, setInput] = useState('');
const handleChange = e => {
setInput(e.target.value);
};
const handleSubmit = e => {
e.preventDefault();
props.onSubmit({
id: Math.floor(Math.random() * 10000),
text: input,
complete: false
});
setInput('');
};
return (
<form onSubmit={handleSubmit}>
<input
placeholder='todo...'
value={input}
onChange={handleChange}
name='text'
/>
<button onClick={handleSubmit}>add todo</button>
</form>
);
}
So right now everything works where I can add todos and delete todos + cross out todos. Only thing missing is being able to edit them.
I saw some suggestions about updating the text value with an input form, but I'm not too sure how I'd implement that in my editTodo function.
Similar to your removeTodo handler, you want to pass the todo.id to completeTodo.
<div className={todo.isComplete ? "complete" : ""} key={todo.id} onClick={() => completeTodo(todo.id)}>
Then you would update a bool value in the todo object.
const completeTodo = (id) => {
let updatedTodos = todos.map(todo => {
if(todo.id === id){
todo.isComplete = true
}
return todo
})
setTodos(updatedTodos)
};
Edit: add styling strikethrough
You'll then conditionally add a css style based on isComplete boolean
CSS
.complete {
text-decoration: line-through;
}
To be able to click on the Remove button, place it outside the todo div in your map function.
{todos.map((todo, isComplete) => (
<>
<div
key={todo.id}
onClick={completeTodo}
className={isComplete ? 'complete' : ''}
>
{todo.text}
</div>
<FaWindowClose onClick={() => removeTodo(todo.id)} />
</>
))}
As discussion with you in another question here it is:
TodoList.js
import React, { useState } from "react";
import TodoForm from "./TodoForm";
import Todo from "./Todo";
function TodoList({ onClick }) {
const [todos, setTodos] = useState([]);
//Track is edit clicked or not
const [editId, setEdit] = useState(false);
//Save input value in input box
const [inputValue, setInputValue] = useState("");
const handleEditChange = (id, text) => {
setEdit(id);
setInputValue(text);
};
const addTodo = (todo) => {
if (!todo.text || /^\s*$/.test(todo.text)) {
return;
}
const newTodos = [todo, ...todos];
setTodos(newTodos);
console.log(newTodos);
};
const removeTodo = (id) => {
const removedArr = [...todos].filter((todoId) => todoId.id !== id);
setTodos(removedArr);
};
const completeTodo = (id) => {
let updatedTodos = todos.map((todo) => {
if (todo.id === id) {
todo.isComplete = !todo.isComplete;
}
return todo;
});
setTodos(updatedTodos);
};
const editTodo = (id, text) => {
let editTodos = todos.map((todo) => {
if (todo.id === id) {
todo.text = text;
}
return todo;
});
setTodos(editTodos);
setEdit(false);
};
return (
<>
<TodoForm onSubmit={addTodo} />
{/* I want to move this code below into a new component called Todo.js */}
<Todo
todos={todos}
completeTodo={completeTodo}
removeTodo={removeTodo}
editTodo={editTodo}
handleEditChange={handleEditChange}
editId={editId}
inputValue={inputValue}
setInputValue={setInputValue}
/>
</>
);
}
export default TodoList;
Todo.js
// I want to move this code into this component
import React, { useState } from "react";
import { FaWindowClose, FaRegEdit } from "react-icons/fa";
const Todo = ({
todos,
completeTodo,
removeTodo,
editTodo,
editId,
handleEditChange,
inputValue,
setInputValue
}) => {
return todos.map((todo) => (
<div className="todo-row">
{editId === todo.id ? (
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
) : (
<div
key={todo.id}
className={todo.isComplete ? "complete" : ""}
onClick={() => completeTodo(todo.id)}
>
{todo.text}
</div>
)}
{editId === todo.id ? (
<button onClick={() => editTodo(todo.id, inputValue)}>Edit todo</button>
) : (
<>
<FaWindowClose onClick={() => removeTodo(todo.id)} />
<FaRegEdit onClick={() => handleEditChange(todo.id, todo.text)} />
</>
)}
</div>
));
};
export default Todo;
Make sure you read and understand code first. Logic is pretty simple what you do in completeTodo. You just need to update text part. Tricky part is to open in input. So logic is like track if user click on id set that id. And check if id is there open input with that id value other wise normal one.
Here is demo of this POC: https://codesandbox.io/s/nostalgic-silence-idm21?file=/src/Todo.js:0-1059