Franco
Franco

Reputation: 219

How to use each table row with different background color

How I use each table row with different background color like nth-child (even) and nth-child (odd), at first it works as I want, but then when I show only uncompleted tasks it doesn't work

Image from Gyazo

also when I complete a task where only incomplete tasks are shown this is not updated

I am also trying to avoid rendering all tasks, I tried many things but nothing works and I already run out of ideas

Tasks Component:

import React, { useEffect, useReducer } from 'react'
import TaskItem from "./TaskItem";

function saveLocalStorage(tasks) {
    localStorage.setItem('tasks', JSON.stringify(tasks))
}

function TasksReducer(taskItems, { type, task }) {
    switch (type) {
        case 'UPDATE_TAKS': {
            let taskItemsCopy = [...taskItems].map((task) => ({ ...task }))
            let newItems = taskItemsCopy.map((t) => {
                if (t.id === task.id) {
                    t.done = !t.done
                };
                return t;
            })
            saveLocalStorage(newItems)
            return newItems
        }

        case 'ADD_TASK': {
            const newItems = [...taskItems, task]
            saveLocalStorage(newItems)
            return newItems
        }

        default:
            window.alert('INVALID ACTION')
            break;
    }
}

const initialState = JSON.parse(localStorage.getItem('tasks')) || []

//STYLES

const styleTable = {
    'borderCollapse': 'collapse',
    'margin': '25px 0',
    'fontSize': '0.9em',
    'fontFamily': 'sans-serif',
    'minWidth': '400px',
    'boxShadow': '0 0 20px rgba(0, 0, 0, 0.15)'
}

const styleTr = {
    'backgroundColor': '#009879',
    'color': '#ffffff',
    'textAlign': 'left'
}

const styleTh = {
    padding: '12px 15px'
}

function Tasks({ newTask, show }) {
    const [taskItems, dispatch] = useReducer(TasksReducer, initialState);

    useEffect(() => {
        if (!newTask) return
        newTaskHandler({ id: taskItems.length + 1, ...newTask })
    }, [newTask])

    const newTaskHandler = (task) => {
        dispatch({ type: 'ADD_TASK', task })
    }

    const toggleDoneTask = (task) => {
        dispatch({ type: 'UPDATE_TAKS', task })
    }

    return (
        <React.Fragment>
            <h1>learning react </h1>
            <table style={styleTable}>
                <thead>
                    <tr style={styleTr}>
                        <th style={styleTh}>Title</th>
                        <th style={styleTh}>Description</th>
                        <th style={styleTh}>Done</th>
                    </tr>
                </thead>
                <tbody>
                    {
                    taskItems.map((task, i) => {
                            return <TaskItem
                                        tasks={taskItems}
                                        index={i}
                                        task={task}
                                        key={task.id}
                                        show={show}
                                        toggleDoneTask={() => toggleDoneTask(task)}>
                                    </TaskItem>
                                
                        })
                    }
                </tbody>
            </table>
        </React.Fragment>
    )
}


export default Tasks

Task Item component:

import React, { createRef, memo } from 'react'
import styled from "styled-components"
import { useSelector } from "react-redux";
import store from "../../redux/store";


//STYLES

const DIV = styled.div`

    max-height: ${
        props => !useSelector((state)=> state.toggleDoneTasks.show) && props.done ? "0px" : "50px"
    };

    opacity: ${
        props => !useSelector((state)=> state.toggleDoneTasks.show) && props.done ? "0": "1"
    };

    padding: ${
        props => !useSelector((state)=> state.toggleDoneTasks.show) && props.done ? "0px":"12px 15px"
    };

    overflow: hidden;
    transition: opacity 0.5s, max-height 0.5s, padding 0.5s;
`;

/* 
    avoid re-rendering all tasks when changing show state
    that's why it's made this way
    ============================================
*/
const TR = styled.tr`
    background-color: ${
        (props) => {
            //show completed and not completed tasks
            if(useSelector((state)=> state.toggleDoneTasks.show)){
                return props.index % 2 === 0 ? '#f3f3f3': 'none'
            }

            //show only not completed tasks ( FIX IT )

            const tasksNotDone = props.tasks.filter((task) => !task.done)
            const index = tasksNotDone.findIndex(t => t.id === props.task.id)
            console.log(tasksNotDone)
            console.log(index)

            return index % 2 === 0 ? '#f3f3f3': 'none'


        }
    };

    /* &:nth-child(even) {background: #CCC}
    &:nth-child(odd) {background: #FFF} */
/* 
    avoid re-rendering all tasks when changing show state
    that's why it's made this way
    ============================================
*/
    border-bottom: ${
        props => !useSelector((state)=> state.toggleDoneTasks.show) && props.task.done ? "none": "1px solid #dddddd"
    };;

    transition: visibility 0.5s;

    cursor: pointer;
    &:hover{
        font-weight: bold;
        color: #009879;
    }
`;


function TaskRow({ task, toggleDoneTask, index, tasks }) {

    return (
        
        <TR task={task} tasks={tasks} index={index}>
            <td>
                <DIV done={task.done}>
                    {console.log('render', task)}
                    {task.title}
                </DIV>
            </td>
            <td>
                <DIV done={task.done}>
                    {task.description}
                </DIV>
            </td>
            <td>
                <DIV  done={task.done}>
                    <input type="checkbox"
                        checked={task.done}
                        onChange={toggleDoneTask}
                        style={{cursor: 'pointer'}}
                    />
                </DIV>
            </td>
        </TR>

    )

}


export default memo(TaskRow, (prev, next) => {

    //COMPARE TASK OBJECT
    const prevTaskKeys = Object.keys(prev.task);
    const nextTaskKeys = Object.keys(next.task);

    const sameLength = prevTaskKeys.length === nextTaskKeys.length;
    const sameEntries = prevTaskKeys.every(key => {
        return nextTaskKeys.includes(key) && prev.task[key] === next.task[key];
    });
    // store.getState().toggleDoneTasks.show
    return sameLength && sameEntries;
})

If you need the complete code

Edit quizzical-franklin-3r93h

Upvotes: 0

Views: 357

Answers (1)

segFault
segFault

Reputation: 4054

As suggested, you could try not rendering the tasks that are already done when they should be hidden. For example in your map callback you could try doing something like this:

{taskItems.map((task, i) => ((show || !task.done) &&
    <TaskItem
        tasks={taskItems}
        index={i}
        task={task}
        key={task.id}
        show={show}
        toggleDoneTask={() => toggleDoneTask(task)} 
    />
)}

The logic here: (show || !task.done) && <TaskItem /> is basically saying, "If we want to show all tasks OR the current task is not done, show the TaskItem".

This also requires cleaning up your complex styled components to use CSS instead of calculating the nth-child based on index via JS.

Can be done using &:nth-child(odd) (or similar).

In addition, you are using a combination of native react's useState and react-redux useSelector for your show prop/state value. I would recommend using react-redux as that seems to be what you are using in most of your code. To do that change your Tasks component to use useSelector, e.g.

function Tasks({ newTask }) {
  const show = useSelector((state) => state.toggleDoneTasks.show);
  ...

I forked your code with a minimal working example here: https://codesandbox.io/s/recursing-pascal-vjst8?file=/src/components/Tasks/TaskItem.jsx may need some more tweaks. Hope it helps.

Upvotes: 1

Related Questions