0

I am building a todo app using PERN(postgres, Express, React, Node). Iam almost there, however I got stuck in this issue.

I have attached the images at the bottom.

The issue is my application seems to render twice due to my componentDidMount? (Not sure) I have looked at some posts, but still i do not understand how to get around this problem.

What I want is that when I click edit button, it pops up a modal with input box to edit a todo. and the input box needs to have the value before editing. however what i get now is always the first todo, even though you click the second todo in the list.

Side note, i would like to use class component for EditTodo.jsx

I highly appreciate if you could tell me why I get such outputs and provide me the exact solution to this issue. Thank you so much in advance.

ListTodo.jsx

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

const ListTodos = () => {

    const [todos, setTodos] = useState([]);

    const deleteTodo = async id => {
        try {
            const deleteTodo = await fetch(`http://localhost:5000/todos/${id}`, {
                method: "DELETE"
            });
            console.log(deleteTodo);

            setTodos(todos.filter(todo => todo.todo_id !== id))

        } catch(err) {
            console.error(err.message);
        }
    }

    const getTodos = async () => {
        try {
            const response = await fetch("http://localhost:5000/todos")
            const jsonData = await response.json()

            console.log(jsonData);
            
            setTodos(jsonData)
        }catch (err) {
            console.error(err.message);
        }
    }

    useEffect(() => {
        getTodos();
    }, []) 
    return(
        <>
            <table className="centered highlight striped">
                <thead>
                    <tr className="centered">
                        <th>Description</th>
                        <th>Edit</th>
                        <th>Delete</th>
                    </tr>
                </thead>
                
                <tbody>
                {
                    todos.map(todo => (
                        <tr key={todo.todo_id}>
                            <td>{ todo.description }</td>

                            <td>
                                <EditTodo todo={todo} />
                                {/* this todo does not pass iteratively */}
                            </td>

                            <td>
                                <button
                                    className="btn waves-effect waves-red red darken-4"
                                    onClick={() => deleteTodo(todo.todo_id)}
                                >
                                    Delete
                                </button></td>
                        </tr>
                    ))
                }
                </tbody>
            </table>

        </>
    )
}

export default ListTodos;

EditTodo.jsx

import React from 'react';
import M from "materialize-css";

export default class EditTodo extends React.Component {

    constructor(props) {
        super (props);
        this.state = {
            todo_id: this.props.todo.todo_id,
            description: this.props.todo.description
        }
    }

    componentDidMount() {
        const options = {
            onOpenStart: () => {
            console.log("Open Start");
        },
            onOpenEnd: () => {
            console.log("Open End");
        },
            onCloseStart: () => {
            console.log("Close Start");
        },
            onCloseEnd: () => {
            console.log("Close End");
        },
            inDuration: 250,
            outDuration: 250,
            opacity: 0.5,
            dismissible: false,
            startingTop: "4%",
            endingTop: "10%"
        };
        M.Modal.init(this.Modal, options);
    }

    editing = e => {
        console.log(e.target.value);
        this.setState({
            description: e.target.value
        })
    }

    updateSubmit = async e => {
        e.preventDefault();

        try {
            const body = this.state.description;
            const id = this.state.todo_id;

            if(body.description === "") throw new Error("Input cannot be empty");
            // const response = await fetch(`http://localhost:5000/todos/${id}`, {
            //     method: "PUT",
            //     body: JSON.stringify(body)
            // });

            // console.log(response)
            // window.location = "/";
        } catch (err) {
            console.log(err.message)
        }
    }

    render() {
        // console.log(this.props)
        console.log(this.props.todo)
        // console.log(this.props.children)
        return (
            <>
                <a
                    className="waves-effect waves-light btn modal-trigger yellow blue-text"
                    data-target="modal1"
                >
                    Edit
                </a>

                <div
                    ref={Modal => {
                        this.Modal = Modal;
                    }}
                    id="modal1"
                    className="modal"
                >


                    <form onSubmit={this.updateSubmit}>
                        <div className="modal-content">
                            <h4>Edit a todo</h4>
                            <input
                                type="text"
                                onChange={this.editing}
                                value={this.props.todo.description}
                            />
                        </div>

                        <div className="modal-footer">
                            <button type="submit" className="modal-close waves-effect orange waves-yellow btn-flat">Edit</button>
                            <a className="modal-close waves-effect green waves-red btn-flat">Close</a>
                        </div>
                    </form>
                </div>
            </>
        )
    }
}

what it looks like on browser what i get on console

keppodon
  • 75
  • 1
  • 11
  • Can you try setting dynamic model id? like `model1` `model2` – NishanthSpShetty Jul 22 '20 at 04:21
  • Does this answer your question? [Why does useState cause the component to render twice on each update?](https://stackoverflow.com/questions/61578158/why-does-usestate-cause-the-component-to-render-twice-on-each-update) – Drew Reese Jul 22 '20 at 04:36

1 Answers1

0

The problem is that you are always refering to modal1 for all objects inside the loop. You need to change that

                todos.map((todo, index) => (
                    <tr key={todo.todo_id}>
                        <td>{ todo.description }</td>

                        <td>
                            <EditTodo todo={todo} index={index} />

and then

       <a
            className="waves-effect waves-light btn modal-trigger yellow blue-text"
            data-target={"modal" + this.props.index}
        >
                Edit
            </a>

            <div
                ref={Modal => {
                    this.Modal = Modal;
                }}
                id={"modal" + this.props.index}
                className="modal"
            >
Apostolos
  • 10,033
  • 5
  • 24
  • 39
  • Thank you very much for your help. Your answer helped me a lot. Now I know that I can pass description and id through props -> state -> and setState to change the value and sent to database. Thanks a lot again – keppodon Jul 22 '20 at 23:15