Search code examples
javascriptreactjsmaterial-uiformikformik-material-ui

React Map function is not re-rendering the component every time the state array gets updated


I am trying to create a todo app using the material UI select field and also Formik's withFormik for learning. The add functionality is working but every time I try to delete an item, either the whole list gets deleted or the last element is deleted, irrespective of which Item I clicked to delete.

Form component

`

import React from 'react'
import { withFormik } from 'formik';
import { FormControl , Select, InputLabel, MenuItem, Fab, 
  // Button
} from '@mui/material'
import AddIcon from '@mui/icons-material/Add'
import { useState } from 'react';
import List from './List'



const Form = (props) => {
    

const[items, setItems]= useState([]);

// const handleChange = (e)=>{
//   setCountry(e.target.value);
// }
const{
  values,
  handleChange,
  handleSubmit
}= props;


const handleClick = ()=>{
    // setItems((allitem)=>{   
    //   return[...allitem, values.name];
    //   // items.push(values.name)       
    // }
      
    // );
    items.push(values.name)
    setItems(items => [...items])
    

      console.log(items);
  }

const deleteItem = (id)=>{
  console.log(values.name, id);
  // setItems((allItems)=>{
  //   return allItems.splice(id, 1)
  //   })
  
  setItems(()=>{
   return items.splice(id, 1)
  })


// setItems(()=>{
//   return items.filter((elem, index)=>{
//     return index!==id;
//   })
// })

    console.log(items)

}

  return (
    <div className='main'>
    <FormControl sx={{ m: 1, minWidth: 250 }} size="small" onSubmit={handleSubmit}>
  <InputLabel id="country-label">Country</InputLabel>
  <Select
    labelId="Country-label-id"
    id="countries"
    value={values.name}
    label="Country"
    name= "name"
    onChange={handleChange}
  >
    <MenuItem value={9}></MenuItem>
    <MenuItem value={10}>India</MenuItem>
    <MenuItem value={20}>Canada</MenuItem>
    <MenuItem value={30}>Albania</MenuItem>
    <MenuItem value={40}>Zambia</MenuItem>
    <MenuItem value={50}>USA</MenuItem>
    <MenuItem value={60}>Poland</MenuItem>
  </Select>
 
</FormControl>
       <Fab size='small' color="primary" aria-label="add" id='button'>
  <AddIcon onClick={handleClick}/>
</Fab>






<ul>
{/* {console.log(items)} */}
      {items.map((itemval, index)=>{
        
        return (
          <List 
          key= {index}
          id={index}
          valCountry={itemval}
          onDel= {deleteItem}
            
          /> 
        );

      })}
      {/* <Button variant='contained' size='medium' id='clear-btn' onClick={()=>{setItems([])}}>Clear All</Button> */}
    </ul>

    

    </div>
   
  );
  }

  const MySelectField = withFormik({
    mapPropsToValues: ()=>({name: ''}),
    enableReinitialize: true,

    handleSubmit: (values, { setSubmitting }) => {
      setTimeout(() => {
        console.log(JSON.stringify(values, null, 2));
        setSubmitting(false);
      }, 1000);
    },

  })(Form)

export default MySelectField;

`

In my form component I have used withFormik and Material UI. I have commented some of my logics. None of them was working.

List Component

`

import React, { useState } from 'react'
// import { withFormik } from 'formik';
import {Select,MenuItem,} from '@mui/material'
import DeleteIcon from '@mui/icons-material/Delete';

const List = (props) => {
const[listVal, setListVal]= useState(props.valCountry);
//  const{
//   values,
//   handleChange
//  }=props

  return (
    <>
   
        <li> {
            <Select
    labelId="Country-label-id"
    id="countries"
    value={listVal}
    label="Country"
    name='listvalue'
    onChange={(e)=> setListVal(e.target.value)}
  >
    <MenuItem value={9}></MenuItem>
    <MenuItem value={10}>India</MenuItem>
    <MenuItem value={20}>Canada</MenuItem>
    <MenuItem value={30}>Albania</MenuItem>
    <MenuItem value={40}>Zambia</MenuItem>
    <MenuItem value={50}>USA</MenuItem>
    <MenuItem value={60}>Poland</MenuItem>
  </Select>
        } 
        <DeleteIcon margin-top='5px' fontSize='large' onClick={()=>{
              props.onDel(props.id);
        }}

        />
         </li>
       
       
    </>
  )
}



export default List;

` here is the List component which I am passing in my form component using the Map function but somehow it is not working.

previously I have used simple react hooks and it was working perfectly but when I integrated withFormik() in the code the delete functionality is not working.

enter image description here


Solution

  • First of we will need to fix your handleClick and deleteItem. Never mutate the state, you can only change the state with the setItems function. As @terpinmd point out does splice mutate the state which is not good.

    This is how I would write the functions

    const handleClick = () => {
      // You should only mutate the state with setItems
      // If you need the previous state, use the function form of setItems
      // Dont use the same name for the state and the function, this might cause problems
      setItems((currentItems) => [values.name, ...currentItems]);
    };
    
    const deleteItem = (index) => {
      // If you only use the index, dont lie and say you use an id, this just becomes confusing
      setItems((currentItems) => currentItems.filter((_, i) => i !== index));
    };
    

    Star with adjusting your code, and then update your question if you have further issues.

    Edit: The second issue that your are facing is probably caused since you use index as key. https://reactjs.org/docs/lists-and-keys.html

    Since the items have their own state, they will keep the state. You could resolve this issue by moving up the state from your List component and preferably store the value in items since you now only have the initial value there. Since then you will always feed down the value, so the items can adjust to the correct value.

    Or make sure that the key of an item does not change.