Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions graphite-demo/frontend.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React, { useEffect, useState } from 'react';

const TaskSearch = () => {
const [tasks, setTasks] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [searchQuery, setSearchQuery] = useState('');

useEffect(() => {
setLoading(true);
fetch(`/search?query=${encodeURIComponent(searchQuery)}`)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
setTasks(data);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The code assumes that the data received from the API is always an array. If the API returns something else (e.g., null or an error object with a 200 status code), the call to tasks.map on line 46 will throw an error and crash the component. It's safer to validate the data before setting the state to ensure it's an array.

        setTasks(Array.isArray(data) ? data : []);

setLoading(false);
})
.catch(error => {
setError(error.message);
setLoading(false);
});
}, [searchQuery]); // Depend on searchQuery
Comment on lines +9 to +26
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current data fetching logic is susceptible to race conditions. If a user types quickly, multiple requests are sent, but there's no guarantee responses will arrive in the same order. This could result in older data overwriting newer data in the UI. To prevent this, you should cancel the previous request when a new one is initiated. The standard approach in React is to use an AbortController within the useEffect hook and call its abort() method in the cleanup function.

  useEffect(() => {
    const controller = new AbortController();
    setLoading(true);
    fetch(`/search?query=${encodeURIComponent(searchQuery)}`, { signal: controller.signal })
      .then(response => {
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        return response.json();
      })
      .then(data => {
        setTasks(data);
        setLoading(false);
      })
      .catch(error => {
        if (error.name !== 'AbortError') {
          setError(error.message);
          setLoading(false);
        }
      });

    return () => {
      controller.abort();
    };
  }, [searchQuery]); // Depend on searchQuery


if (loading) {
return <div>Loading...</div>;
}

if (error) {
return <div>Error: {error}</div>;
}

return (
<div>
<h2>Task Search</h2>
<input
type="text"
placeholder="Search tasks..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation triggers a search API call on every keystroke because searchQuery is updated directly and is a dependency of the data-fetching useEffect. This can lead to excessive network requests and a poor user experience, especially on slower connections. It's a best practice to debounce the input. This means waiting for the user to stop typing for a short period (e.g., 300-500ms) before triggering the search. You can achieve this by introducing an intermediate state for the input value and using a useEffect with setTimeout to update the searchQuery state that triggers the fetch.

/>
<ul>
{tasks.map(task => (
<li key={task.id}>
<p>{task.description}</p>
</li>
))}
</ul>
</div>
);
};

export default TaskSearch;
Loading