Tony Stark
Tony Stark

Reputation: 45

React Js Updating a value inside a array with onChange to a child component

I have a list with opening_hours that I map inside my render function. I map them to a child component. I want to change the value of each object inside my parent state when the child component state is changed. Only problem is that my values don't change in the child component nor the parent state. I have passed the onChange method to each child component, but the problem lies in my onChange function in the parent state.

I have tried to pass the onChange to my child component and update the state with [e.target.name] : e.target.value. But that didn't work. So I tried to copy the arrayList and update the state based on the index of the mapped arrayList. That also didn't work.

    handleOnChange =  (index) => {
        let newData = [...this.state.opening_hours];
        this.setState(
            newData.map((barbershop, j) => {
                if (j === index) {
                    return barbershop;
                    console.log(barbershop)
                }
            return {
                newData,
            };
        }));
        console.log(newData)
    };

    render() {
        return (
            <>
                <div className="container mb-2">
                    <h4>Opening hours </h4>
                    <Form>
                        {this.state.opening_hours.map((barbershop, index) => (
                            <Day
                                key={barbershop.id}
                                day={barbershop.day}
                                open_time={barbershop.open_time}
                                close_time={barbershop.close_time}
                                index = {index}
                                onChange={() => this.handleOnChange(index)}/>
                        ))}
                    </Form>


                    <Button onClick={this.handleSubmit} variant="primary" type="submit">
                        Update opening hours
                    </Button>
                </div>
            </>
        );
    }
}

My child component looks like this

class Day extends Component {

    constructor(props) {
        super(props);
        this.state = {
            day: this.props.day,
            open_time: this.props.open_time,
            close_time: this.props.close_time,
        };
        console.log(this.state)
    }

    handleChange = () => {
        this.props.onChange(this.props.index)
    }

    render() {
        return (
                <Form.Group as={Row}>
                <Form.Label column sm="3">
                    {ENUM[this.state.day]}
                </Form.Label>
                <Col sm="3">
                    <Form.Control
                        name="open_time"
                        value={this.state.open_time}
                        onChange={this.handleChange}
                        type="time"
                        min="08:00"
                        max="24:00"/>
                </Col>
                <Col sm="3">
                    <Form.Control
                        name="close_time"
                        value={this.state.close_time}
                        onChange={this.handleChange}
                        type="time"
                        min="08:00"
                        max="24:00"/>
                </Col>
            </Form.Group>
        );
    }
}

EDIT:

enter image description here

Upvotes: 2

Views: 5026

Answers (2)

Neil
Neil

Reputation: 968

So I have done some investigation, I believe the problem is that you are not returning data back to the consumer.

Here is what I think, the solution is:

import React, { Component, useState, useEffect } from "react";

class UI extends Component {
  handleOnChange = ({ index, ...newHours }) => {
    // update array item
    const opening_hours = this.state.opening_hours.map((item, i) => {
      const newData = i === index ? newHours : {};

      return {
        ...item,
        ...newData
      };
    });

    // update item in opening_hours => state
    this.setState({ opening_hours });
  };

  render() {
    return (
      <div className="container mb-2">
        <h4>Opening hours</h4>

        <Form>
          {this.state.opening_hours.map(({ id, ...barbershop }, index) => (
            <Day
              key={id}
              {...barbershop}
              {...{ index }}
              onChange={this.handleOnChange}
            />
          ))}
        </Form>

        <Button onClick={this.handleSubmit} variant="primary" type="submit">
          Update opening hours
        </Button>
      </div>
    );
  }
}

const Day = ({ day, onChange, index, ...props }) => {
  const [open_time, setOpenTime] = useState(props.open_time);
  const [close_time, setCloseTime] = useState(props.close_time);

  useEffect(() => {
    onChange({ index, open_time, close_time });
  }, [open_time, close_time, onChange, index]);

  const sharedProps = {
    type: "time",
    min: "08:00",
    max: "24:00"
  };

  return (
    <Form.Group as={Row}>
      <Form.Label column sm="3">
        {ENUM[day]}
      </Form.Label>

      <Col sm="3">
        <Form.Control
          {...sharedProps}
          name="open_time"
          value={open_time}
          onChange={({ target }) => setOpenTime(target.value)}
        />
      </Col>

      <Col sm="3">
        <Form.Control
          {...sharedProps}
          name="close_time"
          value={close_time}
          onChange={({ target }) => setCloseTime(target.value)}
        />
      </Col>
    </Form.Group>
  );
};

Upvotes: 2

Jbonez87
Jbonez87

Reputation: 175

You're passing a function that calls this.handleChange(index), but you should really be passing that method as a prop to your <Day /> component and call it in there. You're already passing in the index (although I would use barbershop.id instead)

So in your parent component, I would do this instead:

<Day 
  key={barbershop.id}
  day={barbershop.day}
  open_time={barbershop.open_time}
  close_time={barbershop.close_time}
  index = {barbershop.id}
  handleChange={this.handleOnChange}
/>

Then in your this.handleChange method in the parent component, if you're just trying to get this.state.opening_hours for a specific barbershop, I would do something like this instead:

handleChange = (id) => {
  this.setState(() => ({
    opening_hours: this.state.opening_hours.filter((barbershop) => { 
      return barbershop.id === id;
    })
  }))
}

Upvotes: 1

Related Questions