Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the ga-google-analytics domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /home/posttrau/public_html/mdtWordpress/wp-includes/functions.php on line 6121

Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the themeisle-companion domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /home/posttrau/public_html/mdtWordpress/wp-includes/functions.php on line 6121

Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the foxiz-core domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /home/posttrau/public_html/mdtWordpress/wp-includes/functions.php on line 6121

Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the hestia domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /home/posttrau/public_html/mdtWordpress/wp-includes/functions.php on line 6121
Hack to refresh component in reactjs app with Typescript Javascript

react_typescript_todo_list is one of my Github repositories that I have been maintaining for the last few months now. It is a todo list app that is built in the microservices architecture style and it communicates with a NodeJS/Express backend i.e. node_typescript_crud_notes written in Typescript. Given this full stack app follows the microservices architecture pattern, both the backend and the front-end were being developed independently. The backend is hosted on AWS Elastic Beanstalk whereas the front-end is hosted as a static website on AWS S3. Last month, I finally started integrating both the backend and the front-end and that is when I started noticing a problem. Once I integrated the front-end to call the NodeJS backend on Elasticbeanstalk to delete a todo or to mark it done, the change would be successfully done on the server-side but it wouldn’t be reflected in the UI. Both update and delete operations were working as expected before integrating it with the backend on AWS. Hence this blogpost will show the solution with code samples of a todo list component UI as well hack to refresh component in reactjs app with Typescript.

Reactjs code for todolist UI component

This solution uses the MUI library to get some default look and feel for it. All this code is from the Github repository react_typescript_todo_list. Show your support by giving that repository a like.

import { DeleteRounded } from "@mui/icons-material";
import {
  Button,
  Card,
  CardContent,
  Checkbox,
  IconButton,
  List,
  ListItem,
  ListItemIcon,
  ListItemText,
  TextField,
} from "@mui/material";
import React, {
  ChangeEvent,
  ChangeEventHandler,
  MouseEventHandler,
  useEffect,
  useState,
} from "react";
import EditRoundedIcon from "@mui/icons-material/EditRounded";
import { deleteTodoFromDB, getExistingList, saveTodoList, saveTodoToDB, updateTodo } from "../backend/db";
import { Todo } from "../customTypes";

import { SimpleDialog } from "./dialogs/simpleDialog";

export const TodoList = () => {
  const [todo, setTodo] = useState("");
  const [todoList, setTodoList] = useState<Todo[]>([]);
  const [editTodoIdx, setEditTodoIdx] = useState<number | null>();
  const [openDialog, setOpenDialog] = useState<boolean>(false);
  
 
  useEffect(() => {
    getExistingList().then(response => {
      if(typeof response === "string") {
        console.log("Error fetching" + response);
      } else {
        setTodoList(response);
      }
    }).catch(e => {
      console.error(e);
    })
  }, []);

  const editTodo: MouseEventHandler<Element> = (
    btn: React.MouseEvent<HTMLElement>
  ) => {
    const todoIdxNo = (btn.currentTarget as any).value;
    const nBeingEdited = Number(todoIdxNo);
    const todo = todoList[todoIdxNo];
    setTodo(todo.text);
    setEditTodoIdx(nBeingEdited);
  };

  const deleteTodo: MouseEventHandler<Element> = (
    btn: React.MouseEvent<HTMLElement>
  ) => {
    const idxNoStr = (btn.currentTarget as any).value;
    const idx = Number(idxNoStr);
    const todo = todoList[idx];
    delRemove(todo.id, idx);
    if(idx === editTodoIdx) {
      setEditTodoIdx(null);
    }
  };

  const delRemove = async (todoId: string, idx: number) => {
    const delResp = await deleteTodoFromDB(todoId);
    if(typeof delResp != "number") {
      console.log(`Error deleting todo, because ${delResp}`);
      return;
    }
    delete todoList[idx];
    setTodoList(todoList); 
  }

  const markDoneChange: ChangeEventHandler<Element> = async (event: ChangeEvent) => {
    var elem = event.target as HTMLTextAreaElement;
    const idx = Number(elem.value);
    const tEdit = todoList[idx];
    tEdit.done = !tEdit.done;
    await updateTodo(todoList[idx]);
    setTodoList(todoList); 
  };

  const saveTodo = async () => {
    if (editTodoIdx != null) {
      const todoBeingEdited = todoList[editTodoIdx];
      todoBeingEdited.text = todo;
      todoList[editTodoIdx].text = todo;
      const updateResult = await updateTodo(todoBeingEdited);
      if(updateResult >= 300) {
        setOpenDialog(true);
      }
    } else {
      const tObj = new Todo(todo, false);
      tObj.user_id = session.getUserIdInSession() ?? "";
      tObj.date = new Date();
      const todoSaved = await saveTodoToDB(tObj);
      todoList.push(todoSaved);
      setTodoList(todoList);
    }
    setEditTodoIdx(null);
    setTodo("");
  };

  const enterTodo: MouseEventHandler<Element> = () => {
    saveTodo();
  };
  
  const handleChange: ChangeEventHandler<Element> = (event: ChangeEvent) => {
    const elem = event.target as HTMLTextAreaElement;
    setTodo(elem.value);
  };
  const handleKeyDown = (event: any) => {
    if (event.key === "Enter") {
      saveTodo();
    }
  };

  const handleClose = () => {
    setOpenDialog(false);
  };

  return (
    <Card>
      <CardContent>
        <div>
          <TextField
            id="standard-basic"
            label="Enter todo"
            variant="standard"
            onChange={handleChange}
            value={todo}
            onKeyDown={handleKeyDown}
            multiline={true}
            fullWidth={true}
          />
        </div>
        <div style={{ marginTop: 20 }}>
          <Button variant="contained" onClick={enterTodo}>
            Add todo{" "}
          </Button>
        </div>
        <List>
          {todoList.map((t: Todo, idx: number) => (
            <ListItem key={idx}>
              <ListItemIcon>
                <Checkbox
                  value={"" + idx}
                  edge="start"
                  checked={t.done ? true : false}
                  disableRipple
                  onChange={markDoneChange}
                />
              </ListItemIcon>
              <ListItemText primary={t.text} />
              <IconButton value={"" + idx} onClick={editTodo}>
                <EditRoundedIcon />
              </IconButton>
              <IconButton
                style={{ marginLeft: 10 }}
                value={"" + idx}
                onClick={deleteTodo}
              >
                <DeleteRounded key={"" + idx} />
              </IconButton>
            </ListItem>
          ))}
        </List>
      </CardContent>

      <SimpleDialog
        openDialog={openDialog}
        handleClose={handleClose}
        dialogHeader="Error"
        dialogMessage="Error updating your todo, try again later"
      />
    </Card>
  );
};

TodoList.propTypes = {};

And this is the code in the db.ts class.

import { gapi } from "gapi-script";
import { Todo, User } from "../customTypes";

const TODO_SAVE_KEY = "todoList";
let todoList: Todo[] = [];
const BASE_URL=process.env.REACT_APP_BASE_URL;

export const getExistingList = (): Promise<Todo[] | string> => {
  const list: Todo[] = [];
  //const baseUrl = "https://api.mydaytodos.com"
  return new Promise((resolve, reject) => {
    fetch(`${BASE_URL}/todos`, {
      method: "GET", // *GET, POST, PUT, DELETE, etc.
      headers: {
        "x-api-key": process.env.REACT_APP_API_KEY ?? '',
      }
    }).then((resp) => resp.json())
      .then((data) => {
        data.forEach((element: any) => {
          try {
            const d = new Date(Date.parse(element["date"]));
            const t = new Todo(
              element["text"],
              JSON.parse(element["done"]),
              element["id"],
              element["user_id"],
              d
            );
            list.push(t);
          } catch (e) {
            console.log(e);
          }
        });
        resolve(list);
      })
      .catch((err) => {
        const msg = `Err fetching data ${JSON.stringify(err)}`;
        console.error(msg);
        reject(msg);
      });
  });
};

export const saveTodoToDB = (todo: Todo): Promise<Todo> => {
  console.log(`db.ts -> About to save todo ${JSON.stringify(todo)}`);
  return new Promise((resolve, reject) => {
    fetch(`${BASE_URL}/todo/`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-api-key": process.env.REACT_APP_API_KEY ?? '',
      },
      body: JSON.stringify(todo),
    }).then(resp => resp.json())
    .then(data => {
      resolve(data);
    })
    .catch(err => {
      reject(err);
    })
  });
};

export const updateTodo = (todo: Todo): Promise<any> => {

  return new Promise((resolve, reject) => {
    fetch(`${BASE_URL}/todo?id=${todo.id}`, {
      method: "PATCH",
      headers: {
        "Content-Type": "application/json",
        "x-api-key": process.env.REACT_APP_API_KEY ?? '',
      },
      body: JSON.stringify(todo)
    }).then(resp => {
      resolve(resp.status);
    }).catch(err => {
      reject(err);
    })
  });
};

export const deleteTodoFromDB = (todoId: string): Promise<number> => {
  return new Promise((resolve, reject) => {
    fetch(`${BASE_URL}/todo?id=${todoId}`, {
      method: "DELETE",
      headers: {
        "x-api-key": process.env.REACT_APP_API_KEY ?? '',
      }
    }).then(resp => {
      resolve(resp.status);
    }).catch(err =>{
      reject(err);
    })
  }); 
};

export const getUserByEmail = (email: string): Promise<User> => {
  return new Promise((resolve, reject) => {
    return fetch(`${BASE_URL}/user/by/email/${email}`, {
      method: "GET",
      headers: {
        "x-api-key": process.env.REACT_APP_API_KEY ?? '',
      }
    }).then(res => res.json())
    .then(data => {
      if(data) {
        if(data.length > 1) {
          reject({"msg": "500: server side error - Too many users"});
        } else {
          resolve(data[0]);
        }
      }
    }).catch(err => {
      reject(err);
    })
  })
};should refresh in real-time

export const addNewUser = (user: any): Promise<any> => {
  return new Promise((resolve, reject) => {
    fetch(`${BASE_URL}/user/`, {
      method: "POST",
      body: user,
      headers: {
        "x-api-key": process.env.REACT_APP_API_KEY ?? '',
      }
    }).then(resp => resp.json())
    .then(data => {
      resolve(data);
    })
    .catch(err => reject(err));
  });
};

Now the issue was, the method to mark todo done or the code to delete todo

// update todo
await updateTodo(todoList[idx]);
setTodoList(todoList); 

// delete todo
delete todoList[idx];
setTodoList(todoList); 

Would not update the list in UI. I think it is something about react hooks that I do not fully understand, so I came up with this state change hack that works.

Hack to refresh component in reactjs

The hack is to add another state variable of type boolean which would be modified every time.

// add state variable at the top of the component
const [triggerUpdate, setTriggerUpdate] = useState<boolean>(false);

// modified mark todo done function
  const markDoneChange: ChangeEventHandler<Element> = async (event: ChangeEvent) => {
    var elem = event.target as HTMLTextAreaElement;
    const idx = Number(elem.value);
    const tEdit = todoList[idx];
    tEdit.done = !tEdit.done;
    await updateTodo(todoList[idx]);
    setTriggerUpdate(!triggerUpdate);
  };

// modified delete todo function
  const delRemove = async (todoId: string, idx: number) => {
    const delResp = await deleteTodoFromDB(todoId);
    if(typeof delResp != "number") {
      console.log(`Error deleting todo, because ${delResp}`);
      return;
    }
    delete todoList[idx];
    setTodoList(todoList); 
    setTriggerUpdate(!triggerUpdate);   
  }

Apply the code above and the todo list component should refresh/reload.

Conclusion

In this post you saw code samples for a hack to refresh component in reactjs with Typescript. This is a hack and I am sure there is a better way to do this without having to declare an extra state variable. So if you know how to do that, then please leave a comment on this post and tell me how.

All this code in this post is from my Github repository react_typescript_todo_list, so give it a star and help me continue my work.

If you find any of my posts useful and want to support me, you can buy me a coffee 🙂

https://www.buymeacoffee.com/bhumansoni

While you are here, maybe try one of my apps for the iPhone.

Products – My Day To-Do (mydaytodo.com)

Have a read of some of my other posts on AWS

Deploy NodeJS, Typescript app on AWS Elastic beanstalk – (mydaytodo.com)

How to deploy spring boot app to AWS & serve via https – My Day To-Do (mydaytodo.com)

Some of my other posts on Javascript …

What is Javascript event loop? – My Day To-Do (mydaytodo.com)

How to build a game using Vanilla Javascript – My Day To-Do (mydaytodo.com)

Vanilla Javascript: Create Radio Buttons (How-To) – Bhuman Soni (mydaytodo.com)

Java Spring Boot & Vanilla Javascript solution – My Day To-Do (mydaytodo.com)

Vanilla Javascript: Create Radio Buttons (How-To) – Bhuman Soni (mydaytodo.com)


0 Comments

Leave a Reply

Avatar placeholder
Verified by MonsterInsights