Search code examples
typescriptscopecomponentsincrementreact-tsx

Incrementing Key in Simple React To Do App


I have an incrementing key in my React To Do App so I can filter out certain tasks with an id identifier. For some reason, I have to declare and define my id outside of the main function. Why can't I declare and define it inside the main function? Will this affect my website during production since it's a component that gets exported to the main app?

import { useState } from "react";

//incrementing variable outside of main function
let nextId = 1;

//main function
const ToDoList = () => {

    //I make an initial list with only one task so the list renders something at the start, obj in arr brackets
    const initialList = [{ id: 0, taskName: "Example Task" }];

    //set up useState for list and setlist as well as inputvalue and setinputvalue
    const [list, setList] = useState(initialList);
    const [inputValue, setInputValue] = useState<string>("");

    //id increments with each add goal click, but incrementing variable has to be declared and defined outside main function
    function handleAddClick() {
        if (inputValue.length > 0) {
            setList([...list, { id: nextId++, taskName: inputValue }]);
        }
        setInputValue("");
    }

    //delete tasks by rendering with filter method on ids
    function handleDeleteClick(id) {
        console.log(id);
        setList([...list.filter((item) => item.id !== id)]);
    }

    return (
        <>
          <ul id="list">
            {list.map((item) => {
            return (
                <>
                  <li key={item.id}>
                    <input type="checkbox" />
                    {item.taskName}
                    <span onClick={() =>
                                      handleDeleteClick(item.id)}>&#10060;
                                    </span>
                  </li>
                </>
                    );
                })}
        </ul>
        <section>
          <input
            onKeyUp={(e) => {
            if (e.key === "Enter") {
            handleAddClick();
                       }
                    }}
            value={inputValue}
            onChange={(e) => setInputValue(e.target.value)}
            id="addGoal"
            placeholder="Add a Goal here!">
                  </input>
          <button onClick={handleAddClick}>Add Goal!</button>
          <textarea placeholder="Here's some space to use as a place to type your 
                    thoughts!">
                  </textarea>
        </section>
          </>
    );
};

export default ToDoList;

This isn't as important, but I also don't understand why I get a lint error on line 23, column 29 from TypeScript saying "Parameter 'id' implicitly has an 'any' type. It's the function handleAddClick that has the id argument that gets the error. I try to say it's a number, but it doesn't work. I know it's getting a property from an object which is a number, but I don't know how to write the typing.


Solution

  • Here is what the code should look like with a ListType interface

    import { useState } from "react";
    
    //incrementing variable outside of main function
    let nextId = 1;
    
    interface ListType {
      id: number;
      taskName: string;
    }
    
    //main function
    const ToDoList = () => {
    
        //I make an initial list with only one task so the list renders something at the start, obj in arr brackets
        const initialList: ListType[] = [{ id: 0, taskName: "Example Task" }];
    
        //set up useState for list and setlist as well as inputvalue and setinputvalue
        const [list, setList] = useState<ListType[]>(initialList);
        const [inputValue, setInputValue] = useState<string>("");
    
        //id increments with each add goal click, but incrementing variable has to be declared and defined outside main function
        function handleAddClick() {
            if (inputValue.length > 0) {
                setList([...list, { id: nextId++, taskName: inputValue }]);
            }
            setInputValue("");
        }
    
        //delete tasks by rendering with filter method on ids
        function handleDeleteClick(id: number) {
            console.log(id);
            setList([...list.filter((item) => item.id !== id)]);
        }
    
        return (
            <>
              <ul id="list">
                {list.map((item: ListType) => {
                return (
                    <>
                      <li key={item.id}>
                        <input type="checkbox" />
                        {item.taskName}
                        <span onClick={() =>
                                          handleDeleteClick(item.id)}>&#10060;
                                        </span>
                      </li>
                    </>
                        );
                    })}
            </ul>
            <section>
              <input
                onKeyUp={(e) => {
                if (e.key === "Enter") {
                handleAddClick();
                           }
                        }}
                value={inputValue}
                onChange={(e) => setInputValue(e.target.value)}
                id="addGoal"
                placeholder="Add a Goal here!">
                      </input>
              <button onClick={handleAddClick}>Add Goal!</button>
              <textarea placeholder="Here's some space to use as a place to type your 
                        thoughts!">
                      </textarea>
            </section>
              </>
        );
    };
    
    export default ToDoList;
    

    Now I don't know how you handle your nextId but that would be another issue if that doesn't work.