-1

I'm new in to build a web with React Libray, and use next.js with typscript to create the web app. And I'm trying to build a todo list web app.

I create an interface for collet the list data like below:

interface listData {
  text: string,
  isDone: boolean,
  viewMode?: { display: '' },
  editMode?: { display: 'none' },
}

const [list, setList] = useState<listData[]>([]);

const addList = (item: any) => {
  if (item !== "") {
    const newList = [...list, { text: item, isDone: false, viewMode: { display: '' }, editMode: { display: 'none' } }];
    localStorage.setItem('listData', JSON.stringify([...list, { text: item, isDone: false, viewMode: { display: '' }, editMode: { display: 'none' } }]));
    setList(newList);
  };
}

I got the error on vscode down below when I setList(newList):

Argument of type '(listData | { text: string; isDone: boolean; viewMode: { display: string; }; editMode: { display: string; }; })[]' is not assignable to parameter of type 'SetStateAction<listData[]>'.
  Type '(listData | { text: string; isDone: boolean; viewMode: { display: string; }; editMode: { display: string; }; })[]' is not assignable to type 'listData[]'.
    Type 'listData | { text: string; isDone: boolean; viewMode: { display: string; }; editMode: { display: string; }; }' is not assignable to type 'listData'.
      Type '{ text: string; isDone: boolean; viewMode: { display: string; }; editMode: { display: string; }; }' is not assignable to type 'listData'.
        The types of 'viewMode.display' are incompatible between these types.
          Type 'string' is not assignable to type '""'.ts(2345)

The viewMode and editMode is build for display css to show which tag will be show, like when user press the edit button, then the viewmode.display: 'none', otherwise editMode.display: 'none'.

And now I got a problem about addList assignment problem, please look at the image. I not sure what the prolem is now in addList function when I setList(newList). Hope someone can tell me where am I worng, and really thanks for it.

return (
  <>
    <div className={styles.bodyOuterDiv} >
      ...
        <ul >
          {list.map((item, index) => {
            return (
              <li key={index} className={styles.todoListOuterLi} >
                <div className={item.isDone ? styles.todoListItemsWithIsDone : styles.todoListItems} style={item.viewMode}>
                        {item.text}
                </div>
                <textarea className={styles.todoListTextArea} value={item.text} onChange={(e) => { setItem(e.target.value) }} style={item.editMode} />
                      ...
               </li>
             );
          })}
        </ul>
       ...
     </div>
  </>
);
Austin7L
  • 40
  • 1
  • 6

2 Answers2

1

The problem is that because you don't have a type annotation on newList or the new object in it, TypeScript infers the types of those properties from the types of the property values. When it does that, TypeScript infers "" and "none" to be of type string (rather than the string literal types "" and "none"), but your interface requires the string literal types "" and "none".

There are a couple of ways to fix it. Probably the most direct is to tell TypeScript that newList is a listData[] (but keep reading, there are a couple of other issues):

const addList = (item: any) => {
    if (item !== "") {
        const newList: listData[] = [ // <===========
            ...list,
            { text: item, isDone: false, viewMode: { display: "" }, editMode: { display: "none" } },
        ];
        localStorage.setItem(
            "listData",
            JSON.stringify([
                ...list,
                {
                    text: item,
                    isDone: false,
                    viewMode: { display: "" },
                    editMode: { display: "none" },
                },
            ])
        );
        setList(newList);
    }
};

Playground link


Side note: The item parameter's type should be string, not any:

const addList = (item: string) => {
    // −−−−−−−−−−−−−−−−^^^^^^

By making it any, you leave yourself open to doing addList(42), which will put an object in your list with text: 42, which isn't what the type says it will be.


That said, a couple of other things:

  1. Whenever you're setting state based on existing state, ti's best to use the callback form of the state setter rather than relying on the copy of list that addList closes over, because that can be stale.
  2. There's no reason to repeat the code adding the new item, just use newList when storing in local storage.

So:

const addList = (item: string) => {
    if (item !== "") {
        setList((list) => {
            const newList: listData[] = [
                ...list,
                {
                    text: item,
                    isDone: false,
                    viewMode: { display: "" },
                    editMode: { display: "none" },
                },
            ];
            localStorage.setItem("listData", JSON.stringify(newList));
            return newList;
        });
    }
};

Playground link

Using the callback form also lets you memoize addList, which could be useful if the component you're passing it to is memoized. Giving that component a new addList every time forces it to re-render, whereas if addList is stable (because it's memoized), the component may be able to avoid re-rendering. But whether you want to memoize addList depends on the component you're giving it to. (More about that in my answer about useCallback here.)

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
0

A fix for the first error with the type:

  const newItem: listData = { text: item, isDone: false, viewMode: { display: '' }, editMode: { display: 'none' } }
  const newList = [...list, newItem];
Andrew
  • 630
  • 1
  • 5
  • 14