Home > Software design >  Combining 2 arrays into their own arrays React
Combining 2 arrays into their own arrays React

Time:05-13

I have 2 arrays that I used the map() function to add to an array, but the problem I'm having is they are being grouped by name and grouped by review but I want them to return name review independently of each other when I click submit on my form. Here's an example of what's happening:

enter image description here

I want it so David's review ("great movie") is separate from Daniel's review ("my favorite").

I've tried all sorts of things to no avail. Here is my code:

import { Button, Form, Input } from "reactstrap";
import Stars from "./stars";

export default function ReviewForm() {
  const [reviewinput, setReviewInput] = useState("");
  const [reviewarray, setReviewArray] = useState([]);
  const [nameinput, setNameInput] = useState("");
  const [namearray, setNameArray] = useState([])
  const [starinput, setStarInput] = useState();
  const [stararr, setStarArr] = useState(0)

  const onChange = (e) => {
    setReviewInput(e.target.value);
  };
  const onChangeName = (e) => {
    setNameInput(e.target.value);
  };

  const onSubmit = (e) => {
    e.preventDefault();
    console.log('submitted');
    if (reviewinput) {
      reviewarray.push(reviewinput);
      setReviewArray(reviewarray);
    }
    if (nameinput) {
      namearray.push(nameinput);
      setNameArray(namearray);
    }
    if (starinput) {
      stararr.push(starinput);
      setStarArr(stararr);
    }
      setReviewInput('');
      setNameInput('');
      setStarInput(0)
  };
  console.log(reviewarray);
  return (
    <div className="form-container">
      <Stars setStar={setStarArr} />
      <Form onSubmit={onSubmit}>
      <Input
            className="form-control" type="text"
            placeholder="Enter your name"
            value={nameinput}
            onChange={onChangeName}
          />
        <Input
          className="form-control"
          type="textarea"
          placeholder="Enter your review"
          value={reviewinput}
          onChange={onChange}
        />
        <br></br>
        <Button type="submit" className="btn btn-primary">
          Submit
        </Button>
        <br></br>
        <br></br>
        <div className="card-header border boorder-dark">
          <h5>Reviews</h5>
          </div>
          <div className="card-body border border-secondary">
          {namearray.map((name, i) => <p key={i}>{name}</p>)}
          <br></br>
          {reviewarray.map((review, i) => <p key={i}>{review}</p>)}
          <p>I rate it this movie {stararr} stars!</p>
          </div>
      </Form>
    </div>
  );
}

// STAR COMPONENT \\

import React, { useState } from "react";
import { FaStar} from 'react-icons/fa'

const Stars = ({ setStar }) => {
    const [rating, setRating] = useState(0);
    const [hover, setHover] = useState(null);
  
    const handleClick = (ratingValue) => {
      setRating(ratingValue);
      setStar(ratingValue);
    };
  
    return (
      <div>
        {[...Array(5)].map((star, i) => {
          const ratingValue = i   1;
          return (
            <label key={i}>
              <input
                type="radio"
                name="rating"
                value={ratingValue}
                onClick={() => handleClick(ratingValue)}
              />
              <FaStar
                className="star"
                color={ratingValue <= (hover || rating) ? "gold" : "lightgray"}
                size={20}
                onm ouseEnter={() => setHover(ratingValue)}
                onm ouseLeave={() => setHover(null)}
              />
            </label>
          );
        })}
        <p>I rate this movie {rating   " stars"}</p>
      </div>
    );
  };
  
  export default Stars;```

CodePudding user response:

It would be simpler to do this using an object instead of combining two arrays.

Make each review an object that contains a review, name, and your stars like so:

{
    name: 'a',
    review: 'good',
    stars: 5
}

This way you could just use one array and push that object instead.

The reason your stars wasn't updating to 0 is because in your ./stars file you made a new stars state when you could have just re-used the one from your main component. Other than that, your code was fine.

updated code:

main file

import React, { useState } from "react";
import { Button, Form, Input } from "reactstrap";
import Stars from "./stars";

export default function ReviewForm() {
  const [reviewinput, setReviewInput] = useState("");
  const [reviewarray, setReviewArray] = useState([]);
  const [nameinput, setNameInput] = useState("");
  const [stararr, setStarArr] = useState(0);

  const onChange = (e) => {
    setReviewInput(e.target.value);
  };
  const onChangeName = (e) => {
    setNameInput(e.target.value);
  };

  const onSubmit = (e) => {
    e.preventDefault();
    console.log("submitted");

    const review = {};

    if (reviewinput) {
      review.review = reviewinput;
    }
    if (nameinput) {
      review.name = nameinput;
    }
    review.stars = stararr;
    setReviewArray([...reviewarray, review]);
    setReviewInput("");
    setNameInput("");
    setStarArr(0);
    const form = e.target
    form.reset() /* to reset radio buttons to initial */
  };
  console.log(reviewarray);
  return (
    <div className="form-container">
      <Form onSubmit={onSubmit}>
        <Stars setStar={setStarArr} stararr={stararr} />
        <Input
          className="form-control"
          type="text"
          placeholder="Enter your name"
          value={nameinput}
          onChange={onChangeName}
          required
        />
        <Input
          className="form-control"
          type="textarea"
          placeholder="Enter your review"
          value={reviewinput}
          onChange={onChange}
          required
        />
        <br></br>
        <Button type="submit" className="btn btn-primary">
          Submit
        </Button>
        <br></br>
        <br></br>
        <div className="card-header border boorder-dark">
          <h5>Reviews</h5>
        </div>
        <div className="card-body border border-secondary">
          <br></br>
          {reviewarray.map(({ review, name, stars }, i) => (
            <div key={i}>
              <p>name: {name}</p>
              <p>review: {review}</p>
              <p>stars: {stars}</p>
            </div>
          ))}
          <p>I rate it this movie {stararr} stars!</p>
        </div>
      </Form>
    </div>
  );
}

star component

import React, { useState } from "react";
import { FaStar } from "react-icons/fa";

const Stars = ({ setStar, stararr }) => {
  const [hover, setHover] = useState(null);

  const handleClick = (ratingValue) => {
    setStar(ratingValue);
  };

  return (
    <div>
      {[...Array(5)].map((star, i) => {
        const stararr = i   1;
        return (
          <label key={i}>
            <input
              type="radio"
              name="rating"
              value={stararr}
              onClick={() => handleClick(stararr)}
            />
            <FaStar
              className="star"
              color={stararr <= (hover || stararr) ? "gold" : "lightgray"}
              size={20}
              onm ouseEnter={() => setHover(stararr)}
              onm ouseLeave={() => setHover(null)}
            />
          </label>
        );
      })}
      <p>I rate this movie {stararr   " stars"}</p>
    </div>
  );
};

export default Stars;

Edit: To incorporate the stars input as well

For your stars component I just replaced wherever you had ratings to your original stars value.

CodePudding user response:

Like Bas bas told you, I think it's better to combine the name and the review in the same item in the reviews array.

I also wrote it a bit shorter and understandable:

import { Button, Form, Input } from "reactstrap";
import Stars from "./stars";

export default function ReviewForm() {
  const [nameInput, setNameInput] = useState("");
  const [reviewInput, setReviewInput] = useState("");
  const [starInput, setStarInput] = useState(0);
  const [reviews, setReviews] = useState([]);

  const onSubmit = (e) => {
    e.preventDefault();
    
    if (!nameInput || !nameInput.length || !reviewInput || !reviewInput.length || isNaN(starInput)) {
        return;
    }
    
    setReviews((prev) => [
      ...prev,
      {
        name: nameInput,
        review: reviewInput,
        rating: starInput
      }
    ]);

    clearForm();
  };
  
  const clearForm = () => {
    setNameInput('');
    setReviewInput('');
    setStarInput(0);
  };
  
  return (
    <div className="form-container">
      <Stars setStar={setStarInput} />
      <Form onSubmit={onSubmit}>
        <Input
          className="form-control" type="text"
          placeholder="Enter your name"
          value={nameInput}
          onChange={(e) => setNameInput(e.target.value)}
        />
        <Input
          className="form-control"
          type="textarea"
          placeholder="Enter your review"
          value={reviewInput}
          onChange={(e) => setReviewInput(e.target.value)}
        />
        <br></br>
        <Button type="submit" className="btn btn-primary">
          Submit
        </Button>
        <br></br>
        <br></br>
        <div className="card-header border boorder-dark">
          <h5>Reviews</h5>
                </div>
        <div className="card-body border border-secondary">
          {reviews.map(({ name, review }, i) => 
            <p key={i}>{name} - {review}</p>
          )}
         </div>
      </Form>
    </div>
  );
}

CodePudding user response:

There are many options to achieve this. What you want to do is to zip() (at least Python developers would use this term), so that means you want to group together the first, second, third, ... element of the two given arrays. Then you could map() over the resulting array and display the values as you please.

For your example you could just use something like the following using map():

const names = ["Thomas", "Peter", "Tom", "Mark", "John"];
const reviews = [
  "Well, this was shit.",
  "Love me some sharks",
  "Sharknadoooo!",
  "It's a terrible joy.",
  "I've seen a peanut stand, I've heard a rubber band, I've seen a needle wink it's eye, but I ain't never seen a Shark fly",
];

const result = names.map((name, idx) => ({ name: name, review: reviews[idx] }));

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

In a more general sense with any number of arrays you could use a generator function although you could do it without one. But this is a quite convenient and simple way to achieve what you want which generates the values as you need them when using for ... of.

const names = ["Thomas", "Peter", "Tom", "Mark", "John"]
const reviews = ["Well, this was shit.", "Love me some sharks", "Sharknadoooo!", "It's a terrible joy.", "I've seen a peanut stand, I've heard a rubber band, I've seen a needle wink it's eye, but I ain't never seen a Shark fly"]

/**
 * Zips any number of arrays. It will always zip() the largest array returning undefined for shorter arrays.
 * @param  {...Array<any>} arrays 
 */
function* zip(...arrays){
  const maxLength = arrays.reduce((max, curIterable) => curIterable.length > max ? curIterable.length: max, 0);
  for (let i = 0; i < maxLength; i  ) {
    yield arrays.map(array => array[i]);
  }
}

// put them in a array
const test = [...zip(names, reviews)]
console.log(test);

// or lazy generate the values
for (const [name, review] of zip(names, reviews)) {
  console.log(`${name}: ${review}`);  
}
.as-console-wrapper { max-height: 100% !important; top: 0; }

  • Related