8

I am new to react Hooks (I should say: haven't even started with them yet), but I need assistance with this small problem. This component renders stars and allows a user to select the rating. I want to pass the {starSelected} value from this component to a parent component.

import React, { useState } from "react";
import "./styles.css";

const Star = ({ selected = false, onClick = f => f }) => (
  <div className={selected ? "star selected" : "star"} onClick={onClick} />
);

const StarRating = ({ totalStars }) => {
  const [starsSelected, selectStar] = useState(0);
  return (
    <div className="star-rating">
      {[...Array(totalStars)].map((n, i) => (
        <Star
          key={i}
          selected={i < starsSelected}
          onClick={() => selectStar(i + 1)}
        />
      ))}
      <p>
        {starsSelected} of {totalStars} stars
      </p>
    </div>
  );
};

I would appreciate any help!!!

Parent Component


class RatingNFeedback extends Component {
    constructor(props) {
        super(props);
        this.state = {
            user: {},
            modal: false,
            ratings: '',
            feedback: '',
            feedbackTitle: '',
            errors: {}
        };
        this.toggle = this.toggle.bind(this);
    }

    toggle() {
        this.setState(prevState => ({
            modal: !prevState.modal,
            ratings: '',
            feedback: '',
            feedbackTitle: ''
        }));
    }

    handleRating = (assetId, accessToken) => {
        const {rating, feedback, feedbackTitle} = this.state;
        const ratings = {
            rating,
            feedbackTitle,
            feedback,
            asset: assetId
        };
        this.props.addRatingNComment(ratings, accessToken);
        this.toggle();
    };

    onChange = e => {
        this.setState({
            [e.target.name]: e.target.value
        });
    };

    isModalOpen() {
        return this.state.modal;
    }

    render() {
        const {ratingCount, commentCount, assetId, accessToken} = this.props;
        let stars = [];
        //loop 5 times for stars
        for (let i = 1; i <= 5; i++) {
            let path = require('../../assets/rateFull.svg');
            if (i > ratingCount) {
                path = require('../../assets/RateZero.svg');
            }
            stars.push(<img src={path} alt="" className="card-category-rating" />);
        }
        let userpic = defaultPic;
        if (this.props.user) {
            userpic =
                'http://people.com/User%20Photos/Profile%20Pictures/' +
                this.props.user.profile +
                '_LThumb.jpg';
        }

        return (
            <React.Fragment>

                <Modal
                    isOpen={this.isModalOpen()}
                    toggle={this.toggle}
                    //style={{height: '500px', width: '500px'}}
                    centered
                    className="modal"
                    animation="true"
                >
                    <ModalHeader toggle={this.toggle} className="modal-header">
                        <span className="modal-title"> How would you rate this report? </span>
                    </ModalHeader>
                    <ModalBody className="modal-body ">
                        <div className="rating-modal-header">
                            {' '}
                            <div className=" image-name-box">
                                <div className="circular-landscape image-box">
                                    <img src={userpic} alt="Employee Image" className="user-profile" />{' '}
                                </div>
                                <div className="name-box">
                                    {' '}
                                    <p className="normal-text-2">
                                        {' '}
                                        {`${this.props.userName} ${this.props.familyName}`}{' '}
                                    </p>{' '}
                                    <p className="small-text-2 "> Your review will be posted on Our Website.</p>
                                </div>
                            </div>{' '}
                        </div>
                        <div className="heading3">Your Ratings</div>

                        <StarRating totalStars={5} />

                        <FormTextInput
                            label="Your Feedback:"
                            name="feedbackTitle"
                            value={this.state.feedbackTitle}
                            onChange={this.onChange}
                            placeholder="Title goes here..."
                            type="text"
                        />
                        <FormTextInput
                            //label="Your Feedback:"
                            name="feedback"
                            value={this.state.feedback}
                            onChange={this.onChange}
                            placeholder="Write your feedback here..."
                            type="textarea"
                        />
                    </ModalBody>
                    <ModalFooter>
                        <Button
                            color="primary"
                            onClick={() => this.handleRating(this.props.assetId, this.props.accessToken)}
                        >
                            Submit
                        </Button>{' '}
                        <Button color="secondary" onClick={this.toggle}>
                            Cancel
                        </Button>
                    </ModalFooter>
                </Modal>
            </React.Fragment>
        );
    }
}
Ada_lovelace
  • 637
  • 2
  • 6
  • 18

1 Answers1

16

This can be done by passing a function from the parent component into the child component and then calling that to update the state in the parent. You could also use this method to completely manage the state in the parent if you wanted to, but this example doesn't do that.

Parent component:

import React, { useState } from "react";
import "./styles.css";

const ParentComponent = () => {
  const [selectedStar, setSelectedStar] = useState();

  const updateStars = (star) => {
    setSelectedStar(star);
  }

  return (
    <StarRating
      totalStars={10}
      onStarUpdate={updateStars}
    />
  );
}

Then the star component:

import React, { useState } from "react";
import "./styles.css";

const Star = ({ selected = false, onClick = f => f }) => (
  <div className={selected ? "star selected" : "star"} onClick={onClick} />
);

const StarRating = ({ totalStars, onStarUpdate }) => {
  // you should call your 'setter' `set` and then the name of the const
  // rather than 'selectStar'
  const [starsSelected, setStarsSelected] = useState(0);

  const handleOnClick = (index) => {
    setStarsSelected(index + 1);
    // this next line will update the state in the parent component
    onStarUpdate(index + 1);
  }

  return (
    <div className="star-rating">
      {[...Array(totalStars)].map((n, i) => (
        <Star
          key={i}
          selected={i < starsSelected}
          onClick={() => handleOnClick(i)}
        />
      ))}
      <p>
        {starsSelected} of {totalStars} stars
      </p>
    </div>
  );
};

By passing a function from the parent into the child, and then calling it in the child, you can update the state in the parent.

Like I say you could also use this to completely handle the state, then you could write the StarRating like this:

<StarRating
  totalStars={10}
  onStarUpdate={updateStars}
  selectedStar={selectedStar}
/>
Brett East
  • 4,022
  • 2
  • 20
  • 31