Reputation: 219
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
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
Upvotes: 0
Views: 357
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