The program is working fine but when I try to test it does not work properly. I want to rerender the todos after they are searched but this part in the test seems to not work properly: I fetch the data and set it to the todos, also the search state is working because when I log it I can see the value, however, the rerender is not triggered and the searched items are not displayed, I can see still all the todos. I don't know how to do it. What should I do? here is the code:
useEffect(() => {
fetch("some url todos")
.then((response) => {
return response.json();
})
.then((response) => {
setTodos((todos) => ({ ...todos, all: response }));
})
.catch((e) => console.error(e));
}, []);
useEffect(() => {
setTodos((todos) => ({
...todos,
searched: search
? todos.all.filter((item) => { return item.title.toLowerCase().includes(search.toLowerCase());
})
: null,
}));
}, [search]);
const handleOnChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value);
};
<div className="search-container">
<input
className="search"
value={search}
onChange={handleOnChangeInput}
placeholder="Search todo..."
data-testid="search"
type="text"
/>
</div>
<div className="todos" data-testid="todos">
{(todos.searched && todos.searched.length > 0
? todos.searched
: todos.all
).map(({title}) => (
<p
data-testid="todo"
>
{title}
</p>
))}
</div>
Here are codes for the test:
const mockResponse = [
{
userId: 1,
id: 1,
title: "Todo S",
completed: false,
},
{
userId: 1,
id: 2,
title: "Todo A",
completed: true,
},
];
beforeEach(() => {
jest.spyOn(global, "fetch" as any).mockResolvedValue({
json: () => mockResponse,
});
});
afterEach(() => {
jest.restoreAllMocks();
});
it("should filter todos based on search input", async () => {
render(
<MemoryRouter>
<Home />
</MemoryRouter>
);
const searchInput = screen.getByTestId("search");
fireEvent.change(searchInput, {
target: { value: "A" },
});
const todos = await screen.findAllByTestId("todo");
expect(todos).toHaveLength(1);
});
because you are rendering component and setting the state during rendering, you need to wait for the ui to be fully rendered using the waitFor
async utitliy function.
a good technique that works for me.
adding waitFor
fun after the render.
render(
<MemoryRouter>
<Home />
</MemoryRouter>
);
await waitFor(async () => Promise<void>);
and the rest should works fine.