2

I am new to ReactJS and I am trying to make a todo list. The backend is Java Spring Boot.

I just have a checkbox and a string (string is the task name for the list, like "go shopping")

If I click on the checkbox I get the error :

A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component.

This is my App.js:

import { useEffect, useState } from 'react';
import './App.css';
import TodoItem from './components/todoItem';

function App() {

  const [todoItems, setTodoItems] = useState(null);

  useEffect(() => {
    console.log("Hey loading");

  if (!todoItems){
  fetch('http://localhost:8080/api/todoItems')
  .then((res) => res.json())
  .then((data) => {
    console.log("todo items list: ", data);
    setTodoItems(data);
  });
}
}, [todoItems]); //depends on todoItems
  return (
  <div>
    {todoItems ?  todoItems.map((todoItem) => {
      return (
        <TodoItem 
        key={todoItem.id}
        data = {todoItem} />
      );
    }) 
    : 'loading data..'}
  </div>
  );
}

export default App;

This is my todoItem.jsx

import React, { useEffect, useState } from 'react';

const TodoItem = (props) => {

    const[todoItem, setTodoItem] = useState(props.data);
    //const[getter, setter]
    const [isDirty, setDirty] = useState(false);


    useEffect( () => {
        if(isDirty){
        fetch('http://localhost:8080/api/todoItems/'+todoItem.id, {
            method: 'PUT',
            headers: {
                "content-type": "application/json"
            },
            body: JSON.stringify(todoItem),
        })
        .then(response => response.json())
        .then((data) => {
            setDirty(false);
            setTodoItem(data)
        });
    }
        console.log("hey the todo is changing", todoItem);
        
    }, [todoItem, isDirty]);

    return ( 
    <>
        <input type ="checkbox" checked = {todoItem.isDone}
         onChange={() => {
            setDirty(true);
            setTodoItem({ ...todoItem, isDone: !todoItem.isDone });
        }}
        />
        <span>{todoItem.task}</span>
    </>
    );
};

export default TodoItem;

I kinda know that the error comes because the checkbox is undefined. But I cannot understand why the checkbox is undefined since it comes from App.js.

Here is the backend: ToDoController.java

@RestController
@CrossOrigin(origins = "http://localhost:3000")
public class ToDoController {

    @Autowired
    private TodoService todoService;

    @RequestMapping(value = "/api/todoItems", method = RequestMethod.GET)
    public ResponseEntity<?> fetchAllToDoItems(){
       List<TodoItem> todoItemList =  todoService.fetchAllTodoItems();
       return ResponseEntity.status(HttpStatus.OK).body(todoItemList);

    }

    @RequestMapping(value="/api/todoItems/{id}", method = RequestMethod.PUT)
    public ResponseEntity<?> updateTodoItem(@PathVariable Integer id, @RequestBody TodoItem todoItem){
        TodoItem updateTodoItem =todoService.updateTodoItem(id, todoItem);
        return ResponseEntity.ok(updateTodoItem);

    }
}

ToDoService.java

    @Service
public class TodoService {


    @Autowired
    private TodoRepository todoRepository;

    public List<TodoItem> fetchAllTodoItems (){
        return todoRepository.fetchAllTodoItems();
    }

    public TodoItem updateTodoItem(Integer id, TodoItem todoItem){
        Optional<TodoItem> todoItemOptional = todoRepository.fetchAllTodoItems()
                .stream()
                .filter(item -> todoItem.getId().equals(id) )
                .findAny();

        if (todoItemOptional.isPresent()) {
            TodoItem item = todoItemOptional.get();
            item.setDone(todoItem.getDone());
            item.setTask(todoItem.getTask());
            return item;
        }
        return null;
    }
}

ToDoRepository.java

@Repository
public class TodoRepository {
    private Integer idCounter =0;

    private List<TodoItem> todoItems = new ArrayList<>();

    public List<TodoItem> fetchAllTodoItems() {
        if (todoItems.size() == 0 ) {
            TodoItem item1 = new TodoItem();
            item1.setId(idCounter++);
            item1.setDone(false);
            item1.setTask("Task #1");

            todoItems.add(item1);
        }
        return todoItems;

    }

}

ToDoItem.java

public class TodoItem {


    private Integer id;
    private String task;
    private Boolean isDone;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getTask() {
        return task;
    }

    public void setTask(String task) {
        this.task = task;
    }

    public Boolean getDone() {
        return isDone;
    }

    public void setDone(Boolean done) {
        isDone = done;
    }
}
Blnpwr
  • 1,793
  • 4
  • 22
  • 43
  • It's worth checking this [A component is changing an uncontrolled input of type text to be controlled error in ReactJS](https://stackoverflow.com/questions/47012169/a-component-is-changing-an-uncontrolled-input-of-type-text-to-be-controlled-erro) – Ravikumar Apr 10 '21 at 17:07

1 Answers1

2

Try replacing checked with defaultChecked

and default prop is coming as null so but obvious it would undefined because you are access isDone from null

Add !! before todoItem.isDone

<input type ="checkbox" defaultChecked= {!!todoItem.isDone}
         onChange={() => {
            setDirty(true);
            setTodoItem({ ...todoItem, isDone: !todoItem.isDone });
        }}
        />

!! converts value to Boolean value so undefined is false value so ultimately it will be false

DEEPAK
  • 1,364
  • 10
  • 24
  • Thanks, now I don't get the error. But what is the difference between checked and defaultChecked? And it looks like, if I refresh the page, the checkbox state is not saved. Actually it should be saved, even if I refresh? – Blnpwr Apr 10 '21 at 17:11
  • You are controlling that input checkbox using onChange so it should have defaultChecked which says default value for that attribute – DEEPAK Apr 10 '21 at 17:15
  • ok makes sense, thanks. And do you have an idea, why the state of the checkbox is not saved, when I refresh the page? – Blnpwr Apr 10 '21 at 17:20
  • so does your API return the proper response when you refresh, in the app component? – DEEPAK Apr 10 '21 at 17:28
  • actually yes. I posted the backend code to the question. can you pls have a look? – Blnpwr Apr 10 '21 at 17:37
  • I am not sure about java, Here is my question that may help: when you are changing the checkbox (suppose false to true ), do you see that change in response to your get request on refresh? here in useEffect of APP component `console.log("todo items list: ", data);` – DEEPAK Apr 10 '21 at 17:46
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/231000/discussion-between-deepak-and-blnpwr). – DEEPAK Apr 11 '21 at 11:30