0

I am new to react. I was working on a small project. I am having to display a table(listFeatures.js) and rows can be added to the table via a form submission(addTech.js). But the state change is not being reflected. I probably feel useEffect() can help me in this case but I don't know how to use it properly. Can someone please help...

Code given below

app.js

import React, { useState, useEffect } from 'react'
import ListFeatures from './components/ListFeatures/listFeatures'
import AddTech from './components/AddTech/addTech'

const App = () => {

  const [resourceType, setResourceType] = useState('posts')

  const [tech, setTech] = useState({
    t_names: ["Java", "Python", "C++"],
    feature: [
      {
        name: "Speed",
        feat_list: [
          {desc: "fast", score: 7},
          {desc: "slow", score: 5},
          {desc: "very fast", score: 9}
        ]
       },
       {
        name: "Complexity",
        feat_list: [
          {desc: "High", score: 3},
          {desc: "Low", score: 8},
          {desc: "Medium", score: 6}
        ]
       },
       {
        name: "Modulartiy",
        feat_list: [
          {desc: "medium", score: 7},
          {desc: "Low", score: 6},
          {desc: "High", score: 9}
        ]
       },
    ],
  }) 
  
  const AddNewTechHandler = (newTech) => {

    const newTable = tech

    newTable.t_names.push(newTech.t_names)
    newTable.feature.map((feat, index) => {
        feat.feat_list.push(newTech.feature[index]) 
    })

    setTech(newTable)
    console.log(tech)
    // This console.log shows tech being updated but the changes are not reflected.
  }   

  return (
    <div className = "container">
      <AddTech onAddTech = {AddNewTechHandler}/>
      <ListFeatures tech = {tech} />
    </div>
  )
}

export default App

listFeatures.js

import React from 'react'

const ListFeatures = (props) => {
    return (
        <table>
            <tr>
                <th></th>
                {
                    props.tech.t_names.map((tech) => {
                        return (
                            <React.Fragment>
                                <th> {tech} </th>
                                <th></th>
                            </React.Fragment>
                        )
                    })
                }
            </tr>
            {
                props.tech.feature.map((techs) => {
                    return(
                        <tbody>
                            <tr>
                                <td>{techs.name}</td>
                                {
                                    techs.feat_list.map((feature) => {
                                        return(
                                            <React.Fragment>
                                                <td>{feature.desc}</td>
                                                <td>{feature.score}</td>
                                            </React.Fragment>
                                        )
                                    })
                                }
                            </tr>
                        </tbody>
                    )
                })    
            }
        </table>
    )
}

export default ListFeatures

addTech.js

import React, { useState } from 'react'

const tech_list = ['Python', 'Java', 'C++', 'JS'];

const AddTech = (props) => {

    const [techChosen, setTechChosen] = useState('')

    const addTechHandler = event => {
        event.preventDefault();
        console.log("Is this working ?")

        console.log(techChosen)

        const newTech = {
            // There will be an api call over here in the future.. for now dummy value.
            t_names: "Js",
            feature: [
                {
                    desc: "fast",
                    score: 8,
                },
                {
                    desc: "High",
                    score: 2
                },
                {
                    desc: "medium",
                    score: 8
                },
            ],
        }

        props.onAddTech(newTech);
    }

    const optionChangeHandler = event => {
        setTechChosen(event.target.value);
    }

    return (
        <div className = "container">
            <form onSubmit={addTechHandler}>
                <label>Choose Tech</label>
                <select name="tech" id="tech" onChange={optionChangeHandler}>
                    <option value="">Select a Tech</option>
                    {
                        tech_list.map((tech) => {
                            return(
                                <option value={tech}>{tech}</option>
                            )
                        })
                    }
                </select>
                <button type="submit">Add Tech</button>
            </form>
        </div>
    );
};

export default AddTech;
Adi
  • 3
  • 2
  • You are directly mutating state here which should be avoided (see: [React Hooks Update Nested Array](https://stackoverflow.com/questions/63358150/react-hooks-update-nested-array)), and also falling into the [console.log after setting state trap](https://stackoverflow.com/questions/54069253/usestate-set-method-not-reflecting-change-immediately). – pilchard Dec 28 '21 at 11:43

2 Answers2

0

When updating the state in app.js you need to clone the array, the code const newTable = tech will simply pass by reference to your array fo objects instead you can either do const newTable = [...tech] or when updating the state you set the state by calling setTech([...newTable])

 const AddNewTechHandler = (newTech) => {
  const newTable = tech;

  newTable.t_names.push(newTech.t_names);
  newTable.feature.map((feat, index) => {
    feat.feat_list.push(newTech.feature[index]);
  });
  // see how the state is set, setTech(newTable) is practically passing the same array so there is not effect
  setTech([...newTable]);
};

Faiz Hameed
  • 488
  • 7
  • 15
0

Consider the following 2 things and try your solution once again:

  1. const newTable = tech will simply pass the pointer reference of the tech to the newTable both pointing to the same object memory. Instead, try to create a new object/array when working with state variables. Try object destructuring: var newTable = {...tech}. This will create a new object.
  2. While working with the state where the new state change is dependent on the prev. state, use the useState function format instead of directly updating/mutating the state as you did in the AddNewTechHandler function. Eg: setTech(oldTech => { return oldTech.targetKey = 'newValue' });
  • Thanks..the solution worked. Actually there were 2 issues. Firstly I did not take into account the pointer reference and I actually forgot the fact that my **tech** in **app.js** was actually an object and not an array. I spent a lot of time trying to do `[...tech]` instead of `{...tech}`. I did not realize at first but by chance I happen to open the developer console in the browser and then noticed that the `tech` is not iterable. – Adi Dec 28 '21 at 13:44