Do todos
This commit is contained in:
1 parent
6bf7f18bb5
commit
ea59db58d3
6 files changed
+302
-165
No files matched your search
Generated
+52
@@ -11,6 +11,7 @@
|
||||
"@date-io/dayjs": "^2.13.1",
|
||||
"@emotion/react": "^11.9.0",
|
||||
"@emotion/styled": "^11.8.1",
|
||||
"@mui/icons-material": "^5.6.2",
|
||||
"@mui/material": "^5.6.2",
|
||||
"@mui/x-date-pickers": "^5.0.0-alpha.1",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
@@ -20,6 +21,7 @@
|
||||
"@types/node": "^16.11.27",
|
||||
"@types/react": "^18.0.6",
|
||||
"@types/react-dom": "^18.0.2",
|
||||
"date-fns": "^2.28.0",
|
||||
"dayjs": "^1.11.1",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
@@ -3049,6 +3051,31 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
},
|
||||
"node_modules/@mui/icons-material": {
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.6.2.tgz",
|
||||
"integrity": "sha512-9QdI7axKuBAyaGz4mtdi7Uy1j73/thqFmEuxpJHxNC7O8ADEK1Da3t2veK2tgmsXsUlAHcAG63gg+GvWWeQNqQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.17.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mui/material": "^5.0.0",
|
||||
"@types/react": "^17.0.0 || ^18.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material": {
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.6.2.tgz",
|
||||
@@ -6410,6 +6437,18 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "2.28.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz",
|
||||
"integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==",
|
||||
"engines": {
|
||||
"node": ">=0.11"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/date-fns"
|
||||
}
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.1.tgz",
|
||||
@@ -18733,6 +18772,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@mui/icons-material": {
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.6.2.tgz",
|
||||
"integrity": "sha512-9QdI7axKuBAyaGz4mtdi7Uy1j73/thqFmEuxpJHxNC7O8ADEK1Da3t2veK2tgmsXsUlAHcAG63gg+GvWWeQNqQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.17.2"
|
||||
}
|
||||
},
|
||||
"@mui/material": {
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.6.2.tgz",
|
||||
@@ -21132,6 +21179,11 @@
|
||||
"whatwg-url": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"date-fns": {
|
||||
"version": "2.28.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz",
|
||||
"integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw=="
|
||||
},
|
||||
"dayjs": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.1.tgz",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"@date-io/dayjs": "^2.13.1",
|
||||
"@emotion/react": "^11.9.0",
|
||||
"@emotion/styled": "^11.8.1",
|
||||
"@mui/icons-material": "^5.6.2",
|
||||
"@mui/material": "^5.6.2",
|
||||
"@mui/x-date-pickers": "^5.0.0-alpha.1",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
@@ -15,6 +16,7 @@
|
||||
"@types/node": "^16.11.27",
|
||||
"@types/react": "^18.0.6",
|
||||
"@types/react-dom": "^18.0.2",
|
||||
"date-fns": "^2.28.0",
|
||||
"dayjs": "^1.11.1",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" style="height: 100%">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
@@ -26,9 +26,9 @@
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<body style="height: 100%">
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<div id="root" style="height: 100%"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
+47
-162
@@ -1,158 +1,36 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import './App.css';
|
||||
import { AppBar, Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, Grid, TextField, Toolbar, Typography } from '@mui/material';
|
||||
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
||||
import deLocale from 'date-fns/locale/de';
|
||||
import { AppBar, Box, Button, Paper, Toolbar, Typography } from '@mui/material';
|
||||
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
|
||||
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
||||
import dayjs from 'dayjs';
|
||||
import { DatePicker } from '@mui/x-date-pickers';
|
||||
|
||||
type Todo = {
|
||||
name: string,
|
||||
id: string,
|
||||
done: boolean
|
||||
}
|
||||
|
||||
function TodoItem({todo,setDone}: {todo: Todo, setDone: (done: boolean) => void}){
|
||||
return (
|
||||
<li>
|
||||
{todo.name}
|
||||
<input type="checkbox" checked={todo.done} onChange={(e) => setDone(e.target.checked)}></input>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
function ChangeTodo({todo, setTodo, handleCancel}: {todo: Todo, setTodo: (todo: Todo) => void, handleCancel: () => void}){
|
||||
|
||||
const [name, setName] = React.useState<string>(todo.name)
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setName(event.target.value);
|
||||
};
|
||||
|
||||
const [id, setId] = React.useState<string>(todo.id)
|
||||
|
||||
const [done, setDone] = React.useState<boolean>(todo.done)
|
||||
|
||||
const [from, setFrom] = React.useState<Date | null>(
|
||||
new Date('2022-04-18T21:11:54'),
|
||||
);
|
||||
const handleFrom = (newValue: Date | null) => {
|
||||
setFrom(newValue);
|
||||
};
|
||||
|
||||
const [to, setTo] = React.useState<Date | null>(
|
||||
new Date('2022-05-18T21:11:54'),
|
||||
);
|
||||
const handleTo = (newValue: Date | null) => {
|
||||
setTo(newValue);
|
||||
};
|
||||
|
||||
const saveTodo=() => {
|
||||
const newtodo = {
|
||||
name: name,
|
||||
id: id === "" ? Math.random().toString() : id,
|
||||
done: done
|
||||
};
|
||||
setTodo(newtodo);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
id="name"
|
||||
value={name}
|
||||
label="Name"
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
id="description"
|
||||
label="Description"
|
||||
/>
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs} locale={dayjs().locale('de')}>
|
||||
<DatePicker
|
||||
mask={'__.__.____'}
|
||||
label="From:"
|
||||
value={from}
|
||||
onChange={handleFrom}
|
||||
renderInput={(params: any) => <TextField {...params} />}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs} locale={dayjs().locale('de')}>
|
||||
<DatePicker
|
||||
mask={'__.__.____'}
|
||||
label="To:"
|
||||
value={to}
|
||||
onChange={handleTo}
|
||||
renderInput={(params: any) => <TextField {...params} />}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleCancel}>Cancel</Button>
|
||||
<Button onClick={saveTodo}>Save</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function Todos() {
|
||||
const [todos, setTodos] = useState<Todo[]>([]);
|
||||
const setDone=(i: number,todo: Todo) => (done: boolean) => setTodos([...todos.slice(0,i),{name: todo.name, id: todo.id, done: done},...todos.slice(i+1)]);
|
||||
|
||||
const setTodo= (todo: Todo) => {
|
||||
handleClose();
|
||||
setTodos([...todos, todo]);
|
||||
}
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Typography variant="h2">ToDo's</Typography>
|
||||
<ul>
|
||||
{todos.map((todo, i) =>
|
||||
<TodoItem key={todo.id} todo={todo} setDone={setDone(i, todo)}></TodoItem>
|
||||
)}
|
||||
</ul>
|
||||
<Button onClick={handleClickOpen}>Add Todo</Button>
|
||||
<Dialog open={open} onClose={handleClose}>
|
||||
<DialogTitle>New Todo</DialogTitle>
|
||||
<ChangeTodo todo={{id: "", name: "", done: false}} setTodo={setTodo} handleCancel={handleClose}></ChangeTodo>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
import { Todos } from './components/Todos';
|
||||
|
||||
function Calendar(){
|
||||
return(
|
||||
<Typography variant="h2">Calendar</Typography>
|
||||
<Typography variant="h3">Calendar</Typography>
|
||||
)
|
||||
}
|
||||
|
||||
function Dinos(){
|
||||
return(
|
||||
<Typography variant="h2">Dinos</Typography>
|
||||
<div>
|
||||
<Typography variant="h3">Dinos</Typography>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Overview(){
|
||||
return (
|
||||
<div>
|
||||
<Typography variant="h2">Overview</Typography>
|
||||
<Grid
|
||||
container
|
||||
justifyContent="space-evenly"
|
||||
alignItems="flex-start"
|
||||
spacing={2}>
|
||||
<Todos/>
|
||||
<Todos/>
|
||||
</Grid>
|
||||
<div style={{display: "flex", flexGrow: 1, flexDirection: "column", height: "100%", maxHeight: "100%"}}>
|
||||
<div><Typography variant="h2">Overview</Typography></div>
|
||||
<Box sx={{display: "flex", flexGrow: 1, justifyContent: "space-evenly", gap: 4, height: "100%", minHeight: 0}}>
|
||||
<Paper sx={{overflowY: "auto", width: "100%", padding: 2, height: "calc(100% - 32px)"}}><Todos/></Paper>
|
||||
<Box sx={{width: "100%", display: "flex", flexDirection: "column", justifyContent: "space-evenly"}}>
|
||||
<Calendar/>
|
||||
<Calendar/>
|
||||
</Box>
|
||||
</Box>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -163,28 +41,35 @@ function App() {
|
||||
const [page,setPage] = useState<string>("Overview");
|
||||
return (
|
||||
<>
|
||||
<AppBar>
|
||||
<Toolbar>
|
||||
<Typography variant="h5"
|
||||
noWrap
|
||||
component="div"
|
||||
//sx={{ flexGrow: 0, display: { xs: 'flex', md: 'none' } }}
|
||||
>
|
||||
ToDinos
|
||||
</Typography>
|
||||
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
|
||||
{pages.map((page) =>
|
||||
<Button key={page} onClick={() => setPage(page)} sx={{ my: 2, color: 'white', display: 'block' }}>
|
||||
{page}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
{page === "Overview" && <Overview/>}
|
||||
{page === "Todos" && <Todos/>}
|
||||
{page === "Calendar" && <Calendar/>}
|
||||
{page === "Dinos" && <Dinos/>}
|
||||
<LocalizationProvider dateAdapter={AdapterDateFns} locale={deLocale}>
|
||||
<AppBar>
|
||||
<Toolbar>
|
||||
<Typography variant="h5"
|
||||
noWrap
|
||||
component="div"
|
||||
//sx={{ flexGrow: 0, display: { xs: 'flex', md: 'none' } }}
|
||||
>
|
||||
ToDinos
|
||||
</Typography>
|
||||
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
|
||||
{pages.map((page) =>
|
||||
<Button key={page} onClick={() => setPage(page)} sx={{ my: 2, color: 'white', display: 'block' }}>
|
||||
{page}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<div style={{display: "flex", flexDirection: "column", gap: 2, height: "100%", maxHeight: "100%"}}>
|
||||
<Toolbar sx={{flexGrow: 1}}/>
|
||||
<div style={{marginLeft: "1em", marginRight: "1em", flexGrow: 1, height: "100%", maxHeight: "100%", minHeight: 0}}>
|
||||
{page === "Overview" && <Overview/>}
|
||||
{page === "Todos" && <Todos/>}
|
||||
{page === "Calendar" && <Calendar/>}
|
||||
{page === "Dinos" && <Dinos/>}
|
||||
</div>
|
||||
</div>
|
||||
</LocalizationProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
import React, {useState } from 'react';
|
||||
import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, Divider, List, ListItem, TextField, Typography } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers';
|
||||
import DeleteOutlineRoundedIcon from '@mui/icons-material/DeleteOutlineRounded';
|
||||
import EditRoundedIcon from '@mui/icons-material/EditRounded';
|
||||
import { useLocalStorage } from '../hooks/useLocalStorage';
|
||||
|
||||
type Todo = {
|
||||
id: string,
|
||||
name: string,
|
||||
done: boolean,
|
||||
description: string,
|
||||
from?: string,
|
||||
to?: string,
|
||||
}
|
||||
|
||||
|
||||
function TodoItem({todo}: {todo: Todo}){
|
||||
return (
|
||||
<ListItem>
|
||||
<Box sx={{display: "flex", flexDirection: "column"}}>
|
||||
<Typography variant="h5">{todo.name}</Typography>
|
||||
{todo.description && <span>Description: {todo.description}</span>}
|
||||
{todo.from && <span>From: {(new Date(todo.from)).toLocaleDateString()}</span>}
|
||||
{todo.to && <span>To: {(new Date(todo.to)).toLocaleDateString()}</span>}
|
||||
</Box>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
function ChangeTodo({todo, setTodo, handleCancel}: {todo: Todo, setTodo: (todo: Todo) => void, handleCancel: () => void}){
|
||||
|
||||
const [name, setName] = React.useState<string>(todo.name)
|
||||
const handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setName(event.target.value);
|
||||
};
|
||||
|
||||
const [id, setId] = React.useState<string>(todo.id)
|
||||
|
||||
const [description, setDescription] = React.useState<string>(todo.description)
|
||||
const handleDescChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setDescription(event.target.value);
|
||||
};
|
||||
|
||||
const [done, setDone] = React.useState<boolean>(todo.done)
|
||||
|
||||
const [from, setFrom] = React.useState<Date | null>(todo.from ? new Date(todo.from) : null);
|
||||
const handleFrom = (newValue: Date | null) => {
|
||||
setFrom(newValue);
|
||||
};
|
||||
|
||||
const [to, setTo] = React.useState<Date | null>(todo.to ? new Date(todo.to) : null);
|
||||
|
||||
const handleTo = (newValue: Date | null) => {
|
||||
setTo(newValue);
|
||||
};
|
||||
|
||||
const saveTodo=() => {
|
||||
const newtodo = {
|
||||
name: name,
|
||||
id: id === "" ? Math.random().toString() : id,
|
||||
done: done,
|
||||
description: description,
|
||||
to: to?.toISOString(),
|
||||
from: from?.toISOString()
|
||||
};
|
||||
setTodo(newtodo);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogContent>
|
||||
<Box component="form"
|
||||
sx={{
|
||||
'& > :not(style)': { m: 2, width: '50ch' },
|
||||
}}>
|
||||
<TextField
|
||||
id="name"
|
||||
value={name}
|
||||
label="Name"
|
||||
onChange={handleNameChange}
|
||||
/>
|
||||
<TextField
|
||||
id="description"
|
||||
value={description}
|
||||
label="Description"
|
||||
onChange={handleDescChange}
|
||||
/>
|
||||
<DatePicker
|
||||
mask={'__.__.____'}
|
||||
label="From:"
|
||||
value={from}
|
||||
onChange={handleFrom}
|
||||
renderInput={(params: any) => <TextField {...params} />}
|
||||
/>
|
||||
<DatePicker
|
||||
mask={'__.__.____'}
|
||||
label="To:"
|
||||
value={to}
|
||||
onChange={handleTo}
|
||||
renderInput={(params: any) => <TextField {...params} />}
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleCancel}>Cancel</Button>
|
||||
<Button onClick={saveTodo}>Save</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function Todos({style}: {style?: any}) {
|
||||
const [todos, setTodos] = useLocalStorage<Todo[]>("todos",[]);
|
||||
const setDone=(i: number,todo: Todo, done: boolean) =>
|
||||
setTodos([...todos.slice(0,i),
|
||||
{name: todo.name, id: todo.id, done: done, description: todo.description, to: todo.to, from: todo.from},
|
||||
...todos.slice(i+1)]);
|
||||
|
||||
const deleteTodo=(i: number,todo: Todo) =>
|
||||
setTodos([...todos.slice(0,i),
|
||||
...todos.slice(i+1)]);
|
||||
|
||||
const setTodo= (todo: Todo) => {
|
||||
handleClose();
|
||||
setTodos([...todos, todo]);
|
||||
}
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const [open2, setOpen2] = useState(false);
|
||||
const [edit, setEdit] = useState<Todo>({name: "", description: "", done: false, id: ""});
|
||||
const [editI, setEditI] = useState<number>(0);
|
||||
const handleClickOpen2 = (todo: Todo, i: number) => {
|
||||
setOpen2(true);
|
||||
setEdit(todo);
|
||||
setEditI(i);
|
||||
};
|
||||
const handleClose2 = () => {
|
||||
setOpen2(false);
|
||||
};
|
||||
|
||||
const editTodo= (todo: Todo) => {
|
||||
handleClose2();
|
||||
setTodos([...todos.slice(0,editI),
|
||||
todo,
|
||||
...todos.slice(editI+1)]);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{...style, textAlign: "left"}}>
|
||||
<Typography variant="h3">ToDo's</Typography>
|
||||
<Button onClick={handleClickOpen}>Add Todo</Button>
|
||||
<List>
|
||||
{todos.map((todo, i) =>
|
||||
<>
|
||||
<Box sx={{display: "flex", flexDirection: "columns"}}>
|
||||
<TodoItem key={todo.id} todo={todo}/>
|
||||
<input type="checkbox" checked={todo.done} onChange={(e) => setDone(i, todo,e.target.checked)}></input>
|
||||
<Button onClick={() => handleClickOpen2(todo, i)}><EditRoundedIcon/></Button>
|
||||
<Button onClick={() => deleteTodo}><DeleteOutlineRoundedIcon/></Button>
|
||||
</Box>
|
||||
{(i + 1) < todos.length && <Divider/>}
|
||||
</>
|
||||
)}
|
||||
</List>
|
||||
<Dialog open={open} onClose={handleClose}>
|
||||
<DialogTitle>New Todo</DialogTitle>
|
||||
<ChangeTodo todo={{id: "", name: "", done: false, description: ""}} setTodo={setTodo} handleCancel={handleClose}></ChangeTodo>
|
||||
</Dialog>
|
||||
<Dialog open={open2} onClose={handleClose2}>
|
||||
<DialogTitle>Edit Todo</DialogTitle>
|
||||
<ChangeTodo todo={edit} setTodo={editTodo} handleCancel={handleClose2}></ChangeTodo>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
function useLocalStorage<T>(storageKey: string, fallbackState: T): [T, (val: T) => void] {
|
||||
const [value, setValue] = useState(
|
||||
JSON.parse(localStorage.getItem(storageKey)!) ?? fallbackState
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(storageKey, JSON.stringify(value))
|
||||
}, [value, storageKey]);
|
||||
|
||||
return [value, setValue];
|
||||
}
|
||||
|
||||
export {useLocalStorage}
|
||||
Reference in new issue
Block a user