error-handling #1
11
src/App.js
11
src/App.js
|
@ -8,6 +8,7 @@ import Items from './components/Items';
|
|||
import Navbar from './components/Navbar'; // Correct import here
|
||||
import Admin from './components/Admin'; // Correct import here
|
||||
import { createContext } from 'react';
|
||||
import ErrorBoundary from './components/ErrorBoundary';
|
||||
import './styles.css'
|
||||
|
||||
export const AppContext = createContext();
|
||||
|
@ -26,10 +27,12 @@ function App() {
|
|||
|
||||
return (
|
||||
<AppContext.Provider value={{ token, setToken }}>
|
||||
<Router>
|
||||
<Navbar />
|
||||
<AppRoutes token={token} setToken={setToken} />
|
||||
</Router>
|
||||
<ErrorBoundary>
|
||||
<Router>
|
||||
<Navbar />
|
||||
<AppRoutes token={token} setToken={setToken} />
|
||||
</Router>
|
||||
</ErrorBoundary>
|
||||
</AppContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import React from 'react';
|
||||
import { Alert, Button, Container } from '@mui/material';
|
||||
|
||||
class ErrorBoundary extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hasError: false, error: null };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error) {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<Container>
|
||||
<Alert severity="error" sx={{ mt: 2 }}>
|
||||
Something went wrong
|
||||
<Button
|
||||
onClick={() => window.location.reload()}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
sx={{ ml: 2 }}
|
||||
>
|
||||
Reload Page
|
||||
</Button>
|
||||
</Alert>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react';
|
||||
import {
|
||||
Container,
|
||||
Button,
|
||||
|
@ -27,6 +27,30 @@ import { useApiCall } from './hooks/useApiCall';
|
|||
import { api } from '../services/api';
|
||||
import ItemDetails from './ItemDetails';
|
||||
|
||||
const Item = React.memo(({ item, onDelete, onEdit, itemImages }) => (
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Avatar src={itemImages[item.ID] || '/images/default.jpg'} />
|
||||
</TableCell>
|
||||
<TableCell>{item.name}</TableCell>
|
||||
<TableCell>{item.description}</TableCell>
|
||||
<TableCell>
|
||||
<Box display="flex" justifyContent="space-between" width="100%">
|
||||
<Tooltip title="Edit Item">
|
||||
<IconButton onClick={() => onEdit(item)} size="large" sx={{ mr: 1 }}>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Delete Item">
|
||||
<IconButton onClick={() => onDelete(item.ID)} size="large" color="error">
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
));
|
||||
|
||||
export default function Items({ token }) {
|
||||
const { id: boxId } = useParams();
|
||||
const [items, setItems] = useState([]);
|
||||
|
@ -53,7 +77,7 @@ export default function Items({ token }) {
|
|||
const getItems = async () => {
|
||||
try {
|
||||
const response = await fetchItems(() =>
|
||||
api.items.getAll(token, boxId)
|
||||
boxId ? api.items.getByBox(token, boxId) : api.items.getAll(token)
|
||||
);
|
||||
setItems(response.data);
|
||||
|
||||
|
@ -103,7 +127,7 @@ export default function Items({ token }) {
|
|||
return imageName;
|
||||
};
|
||||
|
||||
const handleSaveNewItem = async () => {
|
||||
const handleSaveNewItem = useCallback(async () => {
|
||||
try {
|
||||
const newItemResponse = await createItem(() =>
|
||||
api.items.create(token, {
|
||||
|
@ -124,30 +148,45 @@ export default function Items({ token }) {
|
|||
await uploadImage(() =>
|
||||
api.items.uploadImage(token, newItemResponse.data.id, formData)
|
||||
);
|
||||
if (newItemResponse.data.id) {
|
||||
try {
|
||||
const imageResponse = await api.items.getImage(token, newItemResponse.data.id);
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
setItemImages(prev => ({
|
||||
...prev,
|
||||
[newItemResponse.data.id]: reader.result
|
||||
}));
|
||||
};
|
||||
reader.readAsDataURL(imageResponse.data);
|
||||
} catch (err) {
|
||||
setItemImages(prev => ({
|
||||
...prev,
|
||||
[newItemResponse.data.id]: '/default.jpg'
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleCloseAddItemDialog();
|
||||
|
||||
// Refresh items list
|
||||
const response = await fetchItems(() =>
|
||||
api.items.getAll(token, boxId)
|
||||
boxId ? api.items.getByBox(token, boxId) : api.items.getAll(token)
|
||||
);
|
||||
setItems(response.data);
|
||||
} catch (err) {}
|
||||
};
|
||||
}, [token, boxId, newItemName, newItemDescription, createItem, uploadImage, fetchItems]);
|
||||
|
||||
const handleDeleteItem = async (itemId) => {
|
||||
const handleDeleteItem = useCallback(async (itemId) => {
|
||||
try {
|
||||
await deleteItem(() =>
|
||||
api.items.delete(token, itemId)
|
||||
);
|
||||
setItems(items.filter(item => item.ID !== itemId));
|
||||
await deleteItem(() => api.items.delete(token, itemId));
|
||||
setItems(prev => prev.filter(item => item.ID !== itemId));
|
||||
} catch (err) {}
|
||||
};
|
||||
}, [token, deleteItem]);
|
||||
|
||||
const handleEditItem = (item) => {
|
||||
const handleEditItem = useCallback((item) => {
|
||||
setEditingItem(item);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleSaveEdit = async () => {
|
||||
setEditingItem(null);
|
||||
|
@ -157,9 +196,12 @@ export default function Items({ token }) {
|
|||
setItems(response.data);
|
||||
};
|
||||
|
||||
const filteredItems = items.filter(item =>
|
||||
item.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
item.description.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
const filteredItems = useMemo(() =>
|
||||
items.filter(item =>
|
||||
item.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
item.description.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
),
|
||||
[items, searchQuery]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -251,40 +293,17 @@ export default function Items({ token }) {
|
|||
<TableCell>Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{filteredItems.map((item) => (
|
||||
<TableRow key={item.ID}>
|
||||
<TableCell>
|
||||
<Avatar src={itemImages[item.ID] || '/images/default.jpg'} />
|
||||
</TableCell>
|
||||
<TableCell>{item.name}</TableCell>
|
||||
<TableCell>{item.description}</TableCell>
|
||||
<TableCell>
|
||||
<Box display="flex" justifyContent="space-between" width="100%">
|
||||
<Tooltip title="Edit Item">
|
||||
<IconButton
|
||||
onClick={() => handleEditItem(item)}
|
||||
size="large"
|
||||
sx={{ mr: 1 }}
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Delete Item">
|
||||
<IconButton
|
||||
onClick={() => handleDeleteItem(item.ID)}
|
||||
size="large"
|
||||
color="error"
|
||||
disabled={deletingItem}
|
||||
>
|
||||
{deletingItem ? <CircularProgress size={20} /> : <DeleteIcon />}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
<TableBody>
|
||||
{filteredItems.map((item) => (
|
||||
<Item
|
||||
key={item.ID}
|
||||
item={item}
|
||||
itemImages={itemImages}
|
||||
onDelete={handleDeleteItem}
|
||||
onEdit={handleEditItem}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
|
|
Loading…
Reference in New Issue