Home > Software engineering >  How to re-render a page by calling a prop from a child component?
How to re-render a page by calling a prop from a child component?

Time:01-23

I think there's something about states I'm not understanding when it comes to causing a re-render. When passing a state as a prop and its setState I was expecting that I would be able to cause a re-render when calling the setState however this does not seem to be the case.

I have created a piece of code that should show what I mean. The code below holds a simple task list. You can see I have split the list into 4 parts, (header, add task, update all task, update task within a day). When you enter a task name, task time and a task day the task will be added to the task state after you press create new task. This will then be passed down to the second layer where the task will be sorted by day then that will passed to the final child that will list this task in a drop down which can be expanded by clicking on the small carrot on the side of the day.

My goal is try and create a system where I can update a individual task, all task associated within a certain day or all task in the list. Currently I have it where I can update all task within a day by using the prop.setTask. If I do something that causes a re-render like adding a new task then the child component will re-render and go where it is supposed to go but if I only change all task within a day no re-render happens despite calling my props.setTask and i'm not sure why since I am updating a state. Can someone explain why this is?

Please note that I have only created it to update all task within a day which you can do by selecting a day in the dropdown next to the name of the day. Each level is color coded for easier identification.

  • light blue = title
  • red - add task
  • coral - update all task
  • light grey - task within a day

Steps to reproduce issue

  • CreateTask
  • Open dropdown on day assigned to task
  • Change task day with dropdown next to name of day
  • Issue no re-render is invoked

Example Code

If you need any clarification I'm more then happy to provide it. Although I think this is just some misunderstanding with the state life cycle.

Code incase sandbox is not loading

App.js

import './styleSheets/mainPage.css';
import {useState,useEffect} from 'react'
import AllTaskList from './components/AllTaskList'

function App() {

  const[task,setTask] = useState([])

  useEffect(()=>{
    console.log('task have updated')
    console.log(task)
  },[task])

  const clearAllTask = () =>{

    document.getElementById('task-name').value = ''
    document.getElementById('task-time').value = ''
    setTask([])

  }

  const createTask = () =>{

    let temp = {}
    let tempArr = []

    if(document.getElementById('task-name').value.length > 0 && document.getElementById('task-time').value.length > 0){

      try{
        temp.taskName = document.getElementById('task-name').value
        temp.taskTime = document.getElementById('task-time').value
        temp.taskDay = document.getElementById('task-date').value

        tempArr.push(temp)
        tempArr = task.concat(tempArr)
    
        setTask(tempArr)
      }catch{
        window.alert('It seems like you may have entered your values incorrectly please try again')
      }
  
    }else{
      window.alert('Please make sure all inputs are filled in.')
    }


  }
 
  return (
    <div className={'todo-list-all-container'}>
      <div className={'todo-list-header-container'}>
        <h className={'todo-list-all-header'}>TODO List</h>
      </div>

      <div className='todo-list-create-container'>
        <div className='todo-list-name-container'>
          <input id='task-name' placeholder='Enter Task Name' autoComplete={'off'}/>
        </div>

        <div className='todo-list-time-container'>
          <input id='task-time' placeholder='Enter Task Due Time' autoComplete={'off'}/>
        </div>

        <div className='todo-list-date-container'>
          <select name="days" id="task-date">
            <option value="Monday">Monday</option>
            <option value="Tuesday">Tuesday</option>
            <option value="Wednsday">Wednsday</option>
            <option value="Thursday">Thursday</option>
            <option value="Friday">Friday</option>
            <option value="Saturday">Saturday</option>
            <option value="Sunday">Sunday</option>
          </select>
        </div>

        <div className='create-task-button-container'>
          <button className='create-task-button' onClick={()=>{createTask()}}>Create New Task</button>
          <button onClick={()=>{clearAllTask()}} >Clear All Task</button>
        </div>

        <div className={'All-Task-List'}>
          <AllTaskList taskList = {task} setTask={setTask}/>
        </div>

      </div>

    </div>
  );
}

export default App;

./components/AllTaskList.js

import { useState,useEffect } from "react"
import WeekdayTable from './WeekdayTable'


export default function AllTaskList(props){

    let sortedTask = {Monday:[],Tuesday:[],Wednsday:[],Thursday:[],Friday:[],Saturday:[],Sunday:[]}
    let weekDays = ['Monday','Tuesday','Wednsday','Thursday','Friday','Saturday','Sunday']
    let task = props.taskList
    let blanketTaskDay = false


    useEffect(()=>{
        sortTask()
        console.log(sortedTask)
    },[props])

    const sortTask = () =>{

        if(Array.isArray(task)){

            for(let day = 0;day < weekDays.length;day  ){
                for(let taskPos = 0;taskPos < task.length;taskPos  ){
                    if(task[taskPos].taskDay == weekDays[day]){
                        sortedTask[weekDays[day]].push(task[taskPos])
                    }
                }
            }

        }else{
            console.log('did not detect array')
        }

    }

    const updateAllTaskDay = (e) =>{

        blanketTaskDay = e.target.value

    }

    const resetAllTaskDay = () =>{

        blanketTaskDay = false

    }

    return(
        <div className="all-week-days-sorted">
            <div className='week-day-table-container'>
                <div className='table-container-header'>
                    <p>This Weeks Task</p>
                </div>

                <div className="update-all-container">
                    <select name="days" id="task-date" onSelect={(e)=>{updateAllTaskDay(e)}}> 
                        <option value="Monday">Monday</option>
                        <option value="Tuesday">Tuesday</option>
                        <option value="Wednsday">Wednsday</option>
                        <option value="Thursday">Thursday</option>
                        <option value="Friday">Friday</option>
                        <option value="Saturday">Saturday</option>
                        <option value="Sunday">Sunday</option>
                    </select>
                </div>

                {weekDays.map((day)=>{
                    return <WeekdayTable 
                        weekList = {sortedTask[day]} 
                        weekDay = {day} updateDate = {blanketTaskDay} 
                        resetAllTaskDay = {resetAllTaskDay} 
                        task={props.taskList} 
                        setTask = {props.setTask}/>
                })}   

            </div>
        </div>
    )

}

./components/WeekdayTable.js

import {useState,useEffect} from 'react'

export default function WeekdayTable(props){

    const[open,setOpen] = useState()
    const toggleOpen = (val) =>{setOpen(val)}

    let todaysTask = props.weekList
    let day = props.weekDay
    let masterTask = props.task

    useEffect(()=>{

        

    },[props])

    const updateAllWeekDay = (e) =>{

        let temp = []

        for(let taskPos = 0;taskPos < todaysTask.length;taskPos  ){
            for(let masterTaskPos = 0;masterTaskPos < masterTask.length;masterTaskPos  ){
                if(todaysTask[taskPos].taskName == masterTask[masterTaskPos].taskName && todaysTask[taskPos].taskDay == masterTask[masterTaskPos].taskDay){
                    masterTask[masterTaskPos].taskDay = e.target.value
                }
            }
        }

        props.setTask(masterTask)

    }

    return(
        <div className={'day-wrapper'}>

                <div className = {'dd-header--bold'} id={day '-dd-header--bold'}>
                    <p>{day}</p>
                </div>
                
                <div className={'update-day'} id={day '-update-day'} onChange={(e)=>{updateAllWeekDay(e)}}>
                    <select name="days" id="task-date">
                            <option value="Monday">Monday</option>
                            <option value="Tuesday">Tuesday</option>
                            <option value="Wednsday">Wednsday</option>
                            <option value="Thursday">Thursday</option>
                            <option value="Friday">Friday</option>
                            <option value="Saturday">Saturday</option>
                            <option value="Sunday">Sunday</option>
                        </select>
                </div>

                <div className='embedded-dd-header__action'>
                    <p onClick={()=>{toggleOpen(!open)}}>{open ? '^' : '⌄'}</p>
                </div>

                {open && (
                    todaysTask.map(task=>{
                        return(
                            <table>
                                <tr className={'task-val-row'}>
                                    <td className={'task-val'}>
                                        <input defaultValue = {task.taskName}></input>
                                    </td>
                                    <td className={'task-val'}>
                                        <input defaultValue = {task.taskTime}></input>
                                    </td>
                                    <td className={'task-val'}>
                                        <input id={task.taskName task.taskDay '-task-day'} defaultValue = {task.taskDay}/>
                                    </td>
                                </tr>
                            </table>
                        )
                    })
                )}

        </div>
    )

}

./styleSheets/mainPage.css

.todo-list-all-container{
    background-color: aqua;
}

.todo-list-header-container{
    text-align: center;

}

.todo-list-create-container{
    text-align: center;
    background-color: red;
}

.All-Task-List{
    background-color: lightcoral;
}

.table-container-header{
    display: inline-block;
}

.update-all-container{
    display:inline-block;
}

.day-wrapper{
    background-color: lightcyan;
}

.dd-header--bold{
    display: inline-block;
    padding-right: 1%;
}

.update-day{
    display: inline-block;
    padding-right: 1%;
}

.embedded-dd-header__action{
    display: inline-block;
}

.table{
    border:1px solid black;
}

.task-val-row{
    text-align: center;
    border:1px solid black;
}

.task-val{
    text-align: center;
    border:1px solid black;
}

CodePudding user response:

It seems that some of the fundamental rules of React are being overlooked.

  • Props are meant to be used as read-only values
  • Always pass a new object to the state setters
  • Never mutate the state directly

Also, the select element has no onSelect event associated with it. The two available events, are input and change.

export default function WeekdayTable(props) {
  // ...
  let masterTask = props.task;

  const updateAllWeekDay = (e) => {

    props.setTask(masterTask); // <= You are using the same object reference 
    // as the new state, thus React will compare the previous reference
    // with the one you pass to setTask, the equality check on the previous 
    // and next state values will return true since they are the
    // same object, hence no re-render will occur

    // To trigger a re-render, you must respect the rules of React: 
    // always return a new object as a value passed to the setState methods:
    props.setTask([...masterTask])

    // Also, inside the updateAllWeekDay, you should NOT be mutating the masterTask 
    // which was passed as a prop
    // Props are meant to be treated as read-only values
    // Work on a copy of the prop, e.g.
    let masterTask = [...props.task];
    masterTask.push( something );
    etc.

  };
  ...
  • Related