Returning values from an array in reactjs without using this.state

Viewed 90

I have 3 arrays of elements(Users, Comments, Ratings) and I need for 2 of them to output the coresponding value from the first. Something like:

functionComments(users_id)
{
returns the coresponding comments after iterating through}

functionRatings(users_id){returns the coresponding ratings after iterating}

{this.state.Users.map(u=>{

{<Card >{functionComments(u.id)<Card/>}
{<Card >{functionRatings(u.id)<Card/>}
})} 

But I have no idea how to return the value without using this.state

elements in a User array look like this

Users: {
                "firstName": "diana",
                "lastName": "diana",
                "age": "0",
                "email": "mari@yahoo.com",
                "password": "123456",
                "name": "diana",
                "username": "Rn9h8fJfs1XvTv1Y9Sr1mXWB7ib2",
                "description": "12346",
                "address": null,
                  "id":45
    }
    
    Comments:
    
       {
                "username": "Rn9h8fJfs1XvTv1Y9Sr1mXWB7ib2",
                "commentsList": [
                    {
                        "comment": "loc",
                        "id": 40,
                        "username": "Rn9h8fJfs1XvTv1Y9Sr1mXWB7ib2",
                        "movieID": "tt6048922"
                    }
                ]
            }
    
    Ratings
    
    {
                "username": "Rn9h8fJfs1XvTv1Y9Sr1mXWB7ib2",
                "ratingsList": [
                    {
                        "rating": 5,
                        "id": 50,
                        "username": "Rn9h8fJfs1XvTv1Y9Sr1mXWB7ib2",
                        "movieID": "tt3089630"
                    }
                ]
            }
    

The output JSON I want to get:

  {
    username: usernameleters,
    firstName: userFirstName,
    commentsList:[
       {
            comments:"comm"
            movieID:"movie"
       }],
    ratingsList:[
        {
          rating:"rrt"
          movieID:"mm"
        }] 

}

The fetches for the 3 arrays look like this :

async  reloadObjectiveList() {
        await  UserApiService.fetchUsers()
            .then((res) => {
                this.setState({
                    Users: res.data.result
                })
            });

     await   CommentApiService.fetchComments()
            .then((res) => {
                console.log( res.data.result)
                this.setState({

                    Comments:  res.data.result
                })
            });    console.log(this.state.Comments)
     await   RatingApiService.fetchrating()
            .then((res) => {
                console.log( res.data.result)
                this.setState({

                    Ratings:  res.data.result
                })
            });
     }
   componentDidMount()
    {
        this.reloadObjectiveList();
        const userData = (this.mergeData(this.state.Users, this.state.Comments, this.state.Ratings));
        console.log("user data "+ this.userData)
        this.setState({UserData:this.userData})


    }
2 Answers

I think there's no way around using local component state to help improve the efficiency of your component. Using a utility function to merge all the data into an initial state object, you can run this once and won't have to recompute it again and again, for each render cycle. This especially avoids the nested O(n) search-looping through each array to find the corresponding array element matching username, making the runtime of the render function really O(n^2)(!!) since you're already looping over the user array once per render cycle.

You can create a utility function that takes the three arrays and gracefully merges comments and ratings lists into the user array.

const mergeData = (users, comments, ratings) => {
  const mapData = (arr, userKey, propertyKey) => arr.reduce((map, curr) => {
    map[curr[userKey]] = (curr[propertyKey] || []).map(el => {
      const elCopy = {...el}
      delete elCopy[userKey]; // remove duplicated key from child array elements
      return elCopy;
    });
    return map;
  }, {});

  // Generate map objects
  const commentsMap = mapData(comments, 'username', 'commentsList');
  const ratingsMap = mapData(ratings, 'username', 'ratingsList');

  return users.map(user => ({
    ...user,
    // Grab comments and ratings lists from maps using current username
    commentsList: commentsMap[user.username],
    ratingsList: ratingsMap[user.username],
  }));
};

const users = [
  {
    firstName: "diana",
    lastName: "diana",
    age: "0",
    email: "mari@yahoo.com",
    password: "123456",
    name: "diana",
    username: "Rn9h8fJfs1XvTv1Y9Sr1mXWB7ib2",
    description: "12346",
    address: null,
    id: 45
  }
];

const comments = [
  {
    username: "Rn9h8fJfs1XvTv1Y9Sr1mXWB7ib2",
    commentsList: [
      {
        comment: "loc",
        id: 40,
        username: "Rn9h8fJfs1XvTv1Y9Sr1mXWB7ib2",
        movieID: "tt6048922"
      }
    ]
  }
];

const ratings = [
  {
    username: "Rn9h8fJfs1XvTv1Y9Sr1mXWB7ib2",
    ratingsList: [
      {
        rating: 5,
        id: 50,
        username: "Rn9h8fJfs1XvTv1Y9Sr1mXWB7ib2",
        movieID: "tt3089630"
      }
    ]
  }
];

const mergeData = (users, comments, ratings) => {
  const mapData = (arr, userKey, propertyKey) => arr.reduce((map, curr) => {
    map[curr[userKey]] = (curr[propertyKey] || []).map(el => {
      const elCopy = {...el}
      delete elCopy[userKey];
      return elCopy;
    });
    return map;
  }, {});

  const commentsMap = mapData(comments, 'username', 'commentsList');
  const ratingsMap = mapData(ratings, 'username', 'ratingsList');

  return users.map(user => ({
    ...user,
    commentsList: commentsMap[user.username],
    ratingsList: ratingsMap[user.username],
  }));
};

console.log(mergeData(users, comments, ratings));

When the component mounts you can pass these three arrays to mergeData and set initial state (i.e. in componentDidMount or a state initializer in useState hook in functional components).

function App() {
  const [userData] = useState(mergeData(users, comments, ratings));

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>

      {userData.map(({ name, username, commentsList, ratingsList }) => (
        <div key={username}>
          <div>User: {name}</div>
          <Card>
            Comments:{" "}
            {commentsList.map(({ movieID, comment }) => (
              <div key={movieID}>
                Movie: {movieID}, Comment: {comment}
              </div>
            ))}
          </Card>
          <Card>
            Ratings:{" "}
            {ratingsList.map(({ movieID, rating }) => (
              <div key={movieID}>
                Movie: {movieID}, Rating: {rating}
              </div>
            ))}
          </Card>
        </div>
      ))}
    </div>
  );
}

Edit Merge 3 arrays for initial state object

Edit

For fetch utility and componentDidMount

You are mixing up async/await with promise chains. You need only await the resolved fetch responses, and you can pass the response data to the mergeData utility, then update state. componentDidMount can simply call reloadObjectiveList.

By using a promise chain and updating state intermittently you are queuing up state updates that are only accessible in later render cycles, componentDidMount has already exited by then.

async function reloadObjectiveList() {
  try {
    const usersRes = await UserApiService.fetchUsers();
    const commentsRes = await CommentApiService.fetchComments();
    const ratingsRes = await RatingApiService.fetchrating();

    const userData = this.mergeData(
      usersRes.data.result,
      commentsRes.data.result,
      ratingsRes.data.result,
    );

    this.setState({ userData });
  } catch {
    // Error, maybe set some error state for the UI?
  }

}

function componentDidMount() {
  this.reloadObjectiveList();
}

maybe you can follow this simple approach

I'm Assuming your inputs looks like this

const users = [
  {
    firstName: 'diana',
    lastName: 'diana',
    age: '0',
    email: 'mari@yahoo.com',
    password: '123456',
    name: 'diana',
    username: 'Rn9h8fJfs1XvTv1Y9Sr1mXWB7ib2',
    description: '12346',
    address: null,
    id: 45,
  },
];

const Comments = [
  {
    username: 'Rn9h8fJfs1XvTv1Y9Sr1mXWB7ib2',
    commentsList: [
      {
        comment: 'loc',
        id: 40,
        username: 'Rn9h8fJfs1XvTv1Y9Sr1mXWB7ib2',
        movieID: 'tt6048922',
      },
    ],
  },
];

const Ratings = [
  {
    username: 'Rn9h8fJfs1XvTv1Y9Sr1mXWB7ib2',
    ratingsList: [
      {
        rating: 5,
        id: 50,
        username: 'Rn9h8fJfs1XvTv1Y9Sr1mXWB7ib2',
        movieID: 'tt3089630',
      },
    ],
  },
];

this will generate the response as you expected

const getUserdata = () => {
  const finalUsersData = users.map(user => {
    const userOutput = {
      username: user.username,
      firstName: user.firstName,
    };
    const userComments = Comments.find(comment => comment.username === user.username);
    userOutput.commentsList = userComments ? userComments.commentsList : [];

    const userRatings = Ratings.find(rating => rating.username === user.username);
    userOutput.userRatings = userRatings ? userRatings.ratingsList : [];

    return userOutput;
  });

  return finalUsersData;
};

Output will be like this

  {
    username: usernameleters,
    firstName: userFirstName,
    commentsList:[
       {
            comments:"comm"
            movieID:"movie"
       }],
    ratingsList:[
        {
          rating:"rrt"
          movieID:"mm"
        }] 

}

please update the references as necessary. If you are storing users , comments and ratings in state make sure you refrence then using this.state.users and so on.

Related