MooseCakeRunner
MooseCakeRunner

Reputation: 25

Update state through React functional component checkbox

I am trying to update the rendered cards everytime the checkbox is checked/unchecked. Currently I am having trouble for quite some time trying to figure out how to update the todos state which is an array containing multiple JSON as such:

[{"_id":"fhrgiu34hnt","todo_description":"Lunch","todo_responsible":"1:30PM","todo_priority":"High","todo_completed":true,"todo_id":"fhrgiu34hnt"},{"_id":"fgvjhbansfgki35sn","todo_description":"Dinner","todo_responsible":"7:45PM","todo_priority":"High","todo_completed":true,"todo_id":"fgvjhbansfgki35sn"},{"_id":"sfgvhio3t58","todo_description":"Supper","todo_responsible":"10:30PM","todo_priority":"Medium","todo_completed":false}]

The values gets updated when changedBox is called via onChange but does not re-render because of (wrongly) trying to compare the todos in the componentDidUpdate? The updated values of the checkbox would be rendered again when I manually refresh the page. Is there a way I can update the checked todos in the component Todo so that it would render correctly?

Here's what I have at the moment:

import React, {Component} from 'react';
import {useState, useEffect} from 'react';
import {Link} from 'react-router-dom';
import axios from 'axios';

const changedBox = (e, props) => {

    const completed = e;
    console.log("Inside changedBox");
    console.log("Trying to update - ID: " + props.todo._id);
    console.log("Checkbox is currently: " + completed);
    const obj = {
        todo_id: props.todo._id,
        todo_description: props.todo.todo_description,
        todo_responsible: props.todo.todo_responsible,
        todo_priority: props.todo.todo_priority,
        todo_completed: completed
    };
    axios.post('example.com/api', obj)
        .then(response => console.log(response.data));
};

function Todo (props) {
    let [t] = useState(props.todo);
    console.log(t)
    console.log(JSON.stringify(t));
    useEffect(() => {
        console.log("Inside useEffect");
        axios.get('example.com/api')
            .then(response => {
                t = props.todo;
            })
            .catch(function (error) {
                console.log(error);
            })

    })

    return (
        <div className="card" key={props.todo._id}>
            <div className="card-body">

                <input type="checkbox" id={props.todo._id} checked={props.todo.todo_completed}
                       onChange={event => changedBox(!props.todo.todo_completed, props)}/>
                <label htmlFor={props.todo._id}/>

                <div className="card-title">
                    <h4> {props.todo.todo_description} </h4>
                    <h6> {props.todo.todo_responsible} </h6>
                    <h6> {props.todo._id ? props.todo._id : "No ID yet"} </h6> {/* Debugging */}
                    <h6> {props.todo.todo_priority} </h6> {/* Debugging */}
                    <span
                        className="badge badge-info"> {props.todo.todo_completed ? "Completed" : "Not Completed"} </span>
                </div>

                <div className="dropdown card-options">
                    <Link to={"/edit/" + props.todo._id}
                          style={{color: 'inherit', textDecoration: 'inherit'}}>
                        <button className="btn-options" type="button">
                            <i className="fas fa-ellipsis-v"/>
                        </button>
                    </Link>
                </div>

            </div>

        </div>

    )
}

class TodosList extends Component {
    _isMounted = false;

    constructor(props) {
        super(props);
        this.state = {
            todos: []
        };
    }

    componentDidMount() {
        this._isMounted = true;
        axios.get('example.com/api')
            .then(response => {
                if (this._isMounted) {
                    this.setState({todos: response.data});
                }
            })
            .catch(function (error) {
                console.log(error);
            })
    }

    componentDidUpdate(prevProps, prevState, ss) {

        console.log("prevState.todos: " + prevState.todos);
        console.log(JSON.stringify(prevState.todos));

        console.log("Comparing JSON.stringify(this.state.todos) !== JSON.stringify(prevState.todos): " + (JSON.stringify(this.state.todos) !== JSON.stringify(prevState.todos)));

        if (JSON.stringify(this.state.todos) !== JSON.stringify(prevState.todos)) {

            console.log("Inside componentDidUpdate - Attempting to update!");
            axios.get('example.com/api')
                .then(response => {
                    this.setState({todos: response.data});
                })
                .catch(function (error) {
                    console.log(error);
                });
        }
    }

    componentWillUnmount() {
        this._isMounted = false;
    }


    todoList() {
        return this.state.todos.map(function (currentTodo, i) {
            console.log("Todo ID: " + currentTodo._id + ", Num: " + i);
            return <Todo todo={currentTodo} key={i} ind={i}/>;
        });
    }

    render() {

        return (
            <div className="card-container">
                <h3>List</h3>
                {this.todoList()}
            </div>
        )
    }
}

export default TodosList;

Upvotes: 0

Views: 1815

Answers (1)

Robert Sidzinka
Robert Sidzinka

Reputation: 828

There's a few things you could do better here.

First, the useState returns array of 2 elements, the first one is the data, second - the updater functions, so whenever you're using useState and you want to update the state, do it like this:

const [data, setData] = useState('some data');

setData('I want to update it');

Then the component will re-render.

This should be fixed

.then(response => {
  // t = props.todo;
  setTodo(props.todo);
})

The changedBox method doesn't cause the rerender of the component because it's not changing the state, use setTodo again.

    const obj = {
        todo_id: props.todo._id,
        todo_description: props.todo.todo_description,
        todo_responsible: props.todo.todo_responsible,
        todo_priority: props.todo.todo_priority,
        todo_completed: completed
    };

setTodo(obj)

I see that you're using mix of hooks and class based components, try to use one of them, it will make your code more consistent.

Upvotes: 1

Related Questions