У меня есть простое приложение списка дел, написанное на React, и я пытаюсь добавить к нему вход в систему. Я хочу, чтобы страница входа была целевой и перенаправлялась на панель управления после входа в систему, но я все еще новичок в React. Код компилируется без ошибок, и после отправки имени пользователя и пароля в браузере появляется следующая ошибка:
tasks is undefined
Dashboard@http://localhost:3000/static/js/bundle.js:215:20
renderWithHooks@http://localhost:3000/static/js/bundle.js:26189:31
mountIndeterminateComponent@http://localhost:3000/static/js/bundle.js:29473:17
beginWork@http://localhost:3000/static/js/bundle.js:30769:20
callCallback@http://localhost:3000/static/js/bundle.js:15785:18
invokeGuardedCallbackDev@http://localhost:3000/static/js/bundle.js:15829:20
invokeGuardedCallback@http://localhost:3000/static/js/bundle.js:15886:35
beginWork$1@http://localhost:3000/static/js/bundle.js:35750:32
performUnitOfWork@http://localhost:3000/static/js/bundle.js:34998:16
workLoopSync@http://localhost:3000/static/js/bundle.js:34921:26
renderRootSync@http://localhost:3000/static/js/bundle.js:34894:11
performSyncWorkOnRoot@http://localhost:3000/static/js/bundle.js:34586:38
flushSyncCallbacks@http://localhost:3000/static/js/bundle.js:22622:26
flushSyncCallbacksOnlyInLegacyMode@http://localhost:3000/static/js/bundle.js:22604:9
scheduleUpdateOnFiber@http://localhost:3000/static/js/bundle.js:34116:11
dispatchSetState@http://localhost:3000/static/js/bundle.js:27217:32
./node_modules/react-router-dom/dist/index.js/BrowserRouter/setState<@http://localhost:3000/static/js/bundle.js:39391:101
push@http://localhost:3000/static/js/bundle.js:1815:15
./node_modules/react-router/dist/index.js/useNavigateUnstable/navigate<@http://localhost:3000/static/js/bundle.js:40519:61
handleSubmit@http://localhost:3000/static/js/bundle.js:841:15
Я не знаю, как устранить эту ошибку. Может ли это быть проблема с маршрутизацией? Обратите внимание, что никакой регистрации или реальной проверки не происходит. Мне просто интересно, чтобы логин перенаправлялся на данный момент.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
import App from './App';
const DATA = [
{id: "todo-0", name: "Make bed", completed: true},
{id: "todo-1", name: "Fold laundry", completed: false},
{id: "todo-2", name: "Brush teeth", completed: false}
];
ReactDOM.render(
<BrowserRouter>
<App tasks = {DATA} />
</BrowserRouter>,
document.getElementById('root')
);
Логин.js
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import './Login.css';
import { useNavigate } from 'react-router-dom';
async function loginUser(credentials) {
return fetch('http://localhost:8080/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(credentials)
})
.then(data => data.json())
}
export default function Login({ setToken }) {
const navigate = useNavigate();
const [username, setUserName] = useState();
const [password, setPassword] = useState();
const handleSubmit = async e => {
e.preventDefault();
const token = await loginUser({
username,
password
});
if (token) {
setToken(token);
navigate("/dashboard");
}
}
return(
<div className = "login-wrapper">
<h1>Please Log In</h1>
<form onSubmit = {handleSubmit}>
<label>
<p>Username</p>
<input type = "text" onChange = {e => setUserName(e.target.value)} />
</label>
<label>
<p>Password</p>
<input type = "password" onChange = {e => setPassword(e.target.value)} />
</label>
<div>
<button type = "login">Login</button>
</div>
</form>
</div>
)
}
Login.propTypes = {
setToken: PropTypes.func.isRequired
};
Приложение.js
import Dashboard from './components/Dashboard/Dashboard';
import Login from './components/Login/Login';
import { Route, Routes } from "react-router-dom";
import './App.css';
import useToken from './useToken';
function App() {
const {token, setToken} = useToken();
if (!token) {
return <Login setToken = {setToken} />
}
return (
<div>
<Routes>
<Route path = "/login" element = {<Login />} />
<Route path = "/dashboard" element = {<Dashboard />} />
</Routes>
</div>
);
}
export default App;
Dashboard.js
import React, { useState, useRef, useEffect } from "react";
import Form from "./Form";
import FilterButton from "./FilterButton";
import Todo from "./Todo";
import { nanoid } from "nanoid";
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const FILTER_MAP = {
All: () => true,
Active: (task) => !task.completed,
Completed: (task) => task.completed
};
const FILTER_NAMES = Object.keys(FILTER_MAP);
export default function Dashboard(props) {
const [tasks, setTasks] = useState(props.tasks);
const [filter, setFilter] = useState('All');
function addTask(name) {
const newTask = { id: `todo-${nanoid()}`, name, completed: false };
setTasks([...tasks, newTask]);
}
function toggleTaskCompleted(id) {
const updatedTasks = tasks.map((task) => {
// if this task has the same ID as the edited task
if (id === task.id) {
// use object spread to make a new object
// whose `completed` prop has been inverted
return {...task, completed: !task.completed}
}
return task;
});
setTasks(updatedTasks);
}
function deleteTask(id) {
const remainingTasks = tasks.filter((task) => id !== task.id);
setTasks(remainingTasks);
}
function editTask(id, newName) {
const editedTaskList = tasks.map((task) => {
// if this task has the same ID as the edited task
if (id === task.id) {
//
return {...task, name: newName}
}
return task;
});
setTasks(editedTaskList);
}
const taskList = tasks.filter(FILTER_MAP[filter]).map((task) => (
<Todo
id = {task.id}
name = {task.name}
completed = {task.completed}
key = {task.id}
toggleTaskCompleted = {toggleTaskCompleted}
deleteTask = {deleteTask}
editTask = {editTask}/>
));
const filterList = FILTER_NAMES.map((name) => (
<FilterButton
key = {name}
name = {name}
isPressed = {name === filter}
setFilter = {setFilter}
/>
));
const tasksNoun = taskList.length !== 1 ? 'tasks' : 'task';
const headingText = `${taskList.length} ${tasksNoun}`;
const listHeadingRef = useRef(null);
const prevTaskLength = usePrevious(tasks.length);
useEffect(() => {
if (tasks.length - prevTaskLength === -1) {
listHeadingRef.current.focus();
}
}, [tasks.length, prevTaskLength]);
return(
<div className = "todoapp stack-large">
<h1>Trevor's ToDo List</h1>
<Form addTask = {addTask} />
<div className = "filters btn-group stack-exception">
{filterList}
</div>
<h2 id = "list-heading" tabIndex = "-1" ref = {listHeadingRef}>
{headingText}
</h2>
<ul className = "todo-list stack-large stack-exception" aria-labelledby = "list-heading" >
{taskList}
</ul>
</div>
);
}
Ошибка: «Задачи не определены» где-то в «Dashboard@http://localhost:3000/static/js/bundle.js:215:20». Судя по коду, состояние tasks
в Dashboard
не определено, потому что props.tasks
не определено, потому что tasks
не было передано свойство Dashboard
.
<Routes>
<Route path = "/login" element = {<Login />} />
<Route
path = "/dashboard"
element = {<Dashboard />} // <-- no tasks prop passed!
/>
</Routes>
export default function Dashboard(props) {
const [tasks, setTasks] = useState(props.tasks); // <-- undefined
...
В любом месте Dashboard
, где есть ссылка на tasks
, например. например tasks.map()
или tasks.filter()
, выдаст ошибку, поскольку tasks
не определено.
Передайте свойство tasks
, переданное в App
, дочерним компонентам.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
import App from './App';
const DATA = [
{id: "todo-0", name: "Make bed", completed: true},
{id: "todo-1", name: "Fold laundry", completed: false},
{id: "todo-2", name: "Brush teeth", completed: false}
];
ReactDOM.render(
<BrowserRouter>
<App tasks = {DATA} /> // <-- tasks passed here
</BrowserRouter>,
document.getElementById('root')
);
Приложение
function App({ tasks }) { // <-- access tasks prop
const { token, setToken } = useToken();
if (!token) {
return <Login setToken = {setToken} />
}
return (
<div>
<Routes>
<Route path = "/login" element = {<Login setToken = {setToken} />} />
<Route
path = "/dashboard"
element = {<Dashboard tasks = {tasks} />} // <-- pass tasks prop
/>
</Routes>
</div>
);
}
Панель приборов
export default function Dashboard(props) {
const [tasks, setTasks] = useState(props.tasks); // <-- defined 🙂
...
@AlexanderWiklund Спасибо. Всегда полезно ограничить объем объявленных переменных, но в этом случае, если вы просите об улучшениях/оптимизации, я бы предложил реализовать/интегрировать глобальное состояние через контекст React (чтобы избежать анти-шаблона «детализация реквизита») или Redux или аналогичное решение для управления состоянием.
Хотя это решает проблему, я бы посоветовал переместить константу задач ближе к тому месту, где она используется. На ранних стадиях разработки можно заменить выборку данных константой, но всегда полезно подумать, какой компонент будет отвечать за выборку данных в будущем.