react fetching and mapping data - javascript

import React,{useState, useEffect} from 'react'
import { useParams } from 'react-router-dom'
import Home from './Home'
import './detailpage.css'
function DetailPage({name,
info,
genre,
_id,
episodeNumber,
poster}) {
const [shows, setShows]= useState([{name:'',
info:'',
airingDate:'',
_id:'',
genre:'',
episodeNumber:'',
poster:''
}])
const params= useParams();
useEffect(()=>{
fetch("/home")
.then(res => res.json())
.then(jsonRes => setShows(jsonRes))
}, [])
const b = JSON.stringify(params);
const newShows = shows.filter(a=>a._id===b)
console.log(newShows)
return (
<div>
<h2>.</h2>
<h2>.</h2>
<h2>.</h2>
<h2>{JSON.stringify(params)}</h2>
<h2>{shows.genre}</h2>
{newShows.map(a=>
<div>
<div className='container'>
<img className='showImg' src={a.poster} alt=''></img>
<h2 className='showTitle'>{a.title}</h2>
<h3>{a.genre}</h3>
<p className='showInfo'>{a.info} </p>
</div>
</div>
)}
<h2>{episodeNumber}</h2>
<h2>{shows.info}</h2>
</div>
)
}
export default DetailPage
I have tv shows on my Home page and after clicking the image I want it to load the detail page about the clicked show however I couldn't manage to do it. I tried 'filter' method in the code but it didn't work I also tried like this
const newShows = shows.filter(a=>a.genre.length>5)
it works but this is not what I want. I would be really happy if someone could've helped. Thank you so much.

If I were you, I wouldn't use this fetch, as when you click on the image from your home you already know which tv show you want to display more details about.
I would use something like useLocation from react-router-dom, and while changing pages (home -> detail page about a tv show) carry a state variable with the specific tv show details.
https://v5.reactrouter.com/web/api/Hooks/usehistory
const handleClick = (state) => {
history.push({ pathname: "/detail-page", state })
}
<YourTvShowImage onClick={() => handleClick(TvShowData)} />
Then on your detail page class you use something like
https://v5.reactrouter.com/web/api/Hooks/uselocation
const location = useLocation()
const [tvShowData, setTvShowData] = useState()
useEffect(() => {
if (location.state) {
setTvShowData(location.state)
}
}, [location])

Related

Data not being passed from parent to child. (ReactJS)

I am currently making a Trello Clone. It has been going well so far and I've had a lot of help from everyone here, so thank you!
My current issue is that I am trying to pass the state of modalData in App.js to <ModifyModal />.
I have tried researching and Googling, and even re-writing functions and creating new ones. However, nothing had worked. I know that the state is being updated with the correct text since I made the title from Trello Clone! to {modalData} and it worked. I want the data of modalData to be passed from App.js to <ModifyModal />.
Edit: Made a functional component and it is still showing undefined for the data.
App.js:
import React, { Component } from 'react';
import './App.css';
import Todobox from './Todobox';
import ModifyModal from './ModifyModal';
import Item from './Item';
const Widget = ({parentCallback2}) => <Todobox parentCallback2={parentCallback2}/>
const Widget2 = () => <ModifyModal />
class App extends Component {
constructor(props){
super(props);
this.handleCallback = this.handleCallback.bind(this);
this.state={
elements: [],
modal: [],
modalData: null
}
}
// Creates new element box
handleNewElement = () => {
const newElement = [...this.state.elements, Widget];
this.setState({
elements: newElement
});
}
handleCallback = (itemWidget, itemData) =>{
const newModal = [...this.state.modal, itemWidget];
const newData = itemData;
this.setState({
modal: newModal,
modalData: newData
});
}
render() {
const { elements, modal, modalData } = this.state;
return (
<>
<div className='page-container'>
<div className='header'>
<a className='header-title'>{modalData}</a>
<a className='header-button' onClick={this.handleNewElement.bind(this)}>Create a list</a>
</div>
<div className='element-field'>
{elements.length !== 0 &&
elements.map((Widget, i) => <Widget key={i} parentCallback2={this.handleCallback}/>)}
</div>
</div>
{modal.length !== 0 &&
modal.map((Widget2, i) => <Widget2 key={i} itemDataToChild={modalData} />)}
</>
);
}
}
export default App;
ModifyModal.jsx:
import React from "react";
import { useState } from "react";
import trash from './trash_can.png';
import './App.css'
function ModifyModal({ itemDataToChild }){
const [hideModal, setHideModal] = useState(false);
const [content, setContent] = useState(itemDataToChild);
const handleCancel = () =>{
setHideModal(true);
}
return(
<>
<div className={`modify-modal-container ${hideModal ? 'modify-modal-container-hide' : ''}`}>
<div className='modify-modal'>
<a className='modify-title'>{content}</a>
<textarea className='modify-input' />
<div className='modify-buttons'>
<a className='modify-btn' id='modify-update-btn'>Update</a>
<a className='modify-btn' id='modify-cancel-btn' onClick={handleCancel}>Cancel</a>
<img src={trash} id='modify-delete'/>
</div>
</div>
</div>
</>
)
}
export default ModifyModal;
Any help is appreciated since I am new to this. :)
The problem is when you declared and initialized Widget2.
const Widget2 = () => <ModifyModal />
What is actually happening under the hood is that Widget2 received a function which returns a JSX.Element, it didn't actually become ModifyModal, the functional component.If you look at the line above is actually doing right.
const Widget = ({parentCallback2}) => <Todobox parentCallback2={parentCallback2}/>
There is 2 solution for this.
you can do just as Widget.
const Widget2 = ({itemDataToChild}) => <ModifyModal itemDataToChild={itemDataToChild}/>
Which I think should be the best approach since you can just rename your imports if was exported as default, and deleting the line const Widget2 = () => <ModifyModal />
import Widget2 from './ModifyModal';
Keeping in mind that the second approach would result error if used for Named Exports. Imports Reference.
For broad your understanding of JSX element and functional component I recommend take a look at their official documentation.
JSX, Components and Props

Creating Group Chat Feature

I have been stunned on this problem for a bit now, so I've come to ask for help. I am creating a private messaging app with a group chat feature. I am using firebase 7.14.0 (yes I know that's old) when you click on a contact in my app you can create a groupchat which will create another chat with just you in it (I know its not optimal I just want to get functionality working first) then in that chat you can add people to the chat.
Here is the code I need help with.
import React, { useEffect, useState, useCallback } from 'react';
import firebase from "../firebase"
import { NavLink } from 'react-router-dom';
import { Avatar } from '#material-ui/core';
import './Gccontact.css';
const Gccontact = props => {
const [contact, setContact] = useState()
const [conversation, setConversation] = useState()
const getContact = useCallback(async () => {
const id = props.contact
const db = firebase.db
const unsubscribe = db.collection("users").doc(id).onSnapshot(snapshot => {
setContact(snapshot.data())
})
return unsubscribe
},[props.contact])
useEffect(() => {
getContact()
}, [props])
const addtogroup = useCallback(async uid => {
const members = [contact.uid];
await firebase.db.collection("conversations").doc('x8DGIeBhW96ziGh0MEGf').update({ members });
}, []);
/* db.collection("users").doc(currentUser.uid).update({
status
}) */
return (
<li className="contact">
{contact && <div className="wrap" onClick={addtogroup}>
<span className={`contact-status ${contact.status}`}></span>
<div className="img-container">
<Avatar src={contact.profilePicture} alt={contact.name + " Profile Picture"} />
</div>
<div style={{display: "inline-block"}} className="meta">
<p className="display-name">{contact.name}</p>
{props.recent && <p className="preview">
{props.recent?.sender === firebase?.auth?.currentUser?.uid && <span>You: </span>}
{props.recent?.attachments?.length === 0 ? props.recent?.body : "Picture"}
</p>}
</div>
</div>}
</li>
);
}
export default Gccontact;
This is the contact file, when you click on the contact I want it to add them to the groupchat. When I try to add the members to group im getting an error saying contact.uid is undefined, so I tried contact.name and it still didn't work. As far as the
await firebase.db.collection("conversations").doc('x8DGIeBhW96ziGh0MEGf').update({ members });
goes I don't know how to get each individuals documents id, so I have a set one just to test. and with .update I noticed it gets rid of all the members and just adds that one that a defined.

Forwarding props from parent to child component

I have 2 components list of posts and when clicking on link on post card i'm entering into post.
I can't access props.postDetails in child component. When I console log the props, I have {history: {…}, location: {…}, match: {…}, staticContext: undefined} only this without props.postDetails.
Can somebody help?
Code for parent component is:
mport {useState, useEffect} from 'react';
import {BrowserRouter as Router, Switch, Route, Link, withRouter} from "react-router-dom";
import logo from "./assets/images/logo.jpg";
import Post from './Post';
const Home = () => {
const [posts, setPosts] = useState([]);
useEffect(() => {
getResults();
},[]);
const getResults =() => {
fetch("https://blog-d8b04-default-rtdb.europe-west1.firebasedatabase.app/posts.json")
.then(response => response.json())
.then(data => {setPosts(data)});
}
const postsArr = [];
Object.values(posts).forEach((post, key) => {
postsArr.push(post);
});
return(
<div>
<div className="container-fluid">
<div className="row">
<div className="posts-container col-md-12">
<div className="row">
{
postsArr.map((post, key) => (
<div className="col-md-4">
<Link to={`/post/${key}`} >
<div className="pic-wrapper">
<img className="img-fluid" src={post.pic} alt={post.title}/>
</div>
<h4>{post.title}</h4>
<Post postDetails={post}/>
</Link>
</div>
))
}
</div>
</div>
</div>
</div>
</div>
)
}
Code for child component:
import {withRouter} from "react-router-dom";
const Post = (props) => {
const {pic, title, author, description} = props.postDetails;
return(
<div className="container">
<div className="pic-wrapper">
<img className="img-fluid" src={pic} alt={title}/>
</div>
<h4>{title}</h4>
<p>{author}</p>
</div>
)
}
export default withRouter(Post);
Issue
Ok, it's as I started to suspect. You are rendering a Post component in more than 1 place.
The issue here is that in Home.js you are passing a postDetails prop, (<Post postDetails={post.pic} />), but in app.js you are only passing the route props from Route, (<Route path="/post/:postId" exact strict component={Post} />). This Post component is the one triggering the error.
Solution
An easy solution is to simply pass the post data along with the route transition.
<Link
to={{
pathname: `/post/${key}`,
state: {
post
}
}}
>
...
<Post postDetails={post.pic} />
</Link>
And access the route state on the receiving end in Post. Try to read the post details from props first, and if they is falsey (null or undefined) assume it was passed in route state and access it there.
const Post = (props) => {
const { state } = props.location;
const { pic, title, author, description } = props.postDetails ?? state.post;
return (
<div className="container">
<div className="pic-wrapper">
<img className="img-fluid" src={pic} alt={title} />
</div>
<h4>{title}</h4>
<p>{author}</p>
</div>
);
};
Of course there is room to make this a bit more robust but this is a good start.
Additional Suggestion
Instead of saving post state that isn't formed correctly for what/how you want to render it, you can transform the response data before saving it into state. This save the unnecessary step of transforming it every time the component rerenders.
const getResults = () => {
setLoading(true);
fetch(
"https://blog-d8b04-default-rtdb.europe-west1.firebasedatabase.app/posts.json"
)
.then((response) => response.json())
.then((data) => {
setPosts(Object.values(data));
setLoading(false);
});
};
Then map as per usual. Make sure to place the React key on the outer-most mapped element, the div in your case.
{posts.map((post, key) => (
<div className="col-md-4" key={key}>
...
</div>
))}
Demo
That is indeed an expected behaviour, because you are actually mapping what appears to be an empty array - see postArr; on your first render it will result as an empty array and since that's not a state, it will never re render your child component with the appropriate props.
I don't really see why you fetch the data, set them to your posts useState and then copy them over to a normal variable; Instead, remove your postArr and on the map replace it with your posts directly.
Since that's a state, react will listen to changes and rerender accordingly, fixing your problem

actions are dispatching every time i render a child component

I am new to react and kind of experimenting with the hooks and i am stuck in a problem where i am using useEffect to dispatch an action to redux store. so everything works fine but the problem i am facing is every time i render a child component in my main component it dispatch the action. Like there are cards that are basically child components and whenever i click on of them to show more details it dispatch the actions that are in parent components same if i close the component so my question how can i prevent that from happening and only render the items once. Let me know if you need any other code..
Parent Component
import React, { useEffect } from "react";
//ANIMATION AND STYLED
import styled from "styled-components";
import { motion, AnimatePresence, AnimateSharedLayout } from "framer-motion";
//REDUX and ROUTER
import {
AllPopularGame,
NextPage,
PrevPage,
} from "../Actions/popularGameActions";
import { useSelector, useDispatch } from "react-redux";
import { Link, useLocation, useHistory } from "react-router-dom";
//COMPONENTS
import Game from "./games";
import GameDetail from "./gameDetail";
const PopularGames = () => {
//GETTNG PATH
const Location = useLocation();
const History = useHistory();
const pathId = Location.pathname.split("/")[4];
//Redux store
const { allPopularGame, gameCount, currentPage, gameLoading } = useSelector(
(state) => state.popular
);
//No of pages
const totalPage = Math.ceil(gameCount / 36);
//SCROLL TO TOP
useEffect(() => {
window.scrollTo(0, 0);
}, [currentPage]);
//Handlers
const PrevHandler = () => {
if (currentPage <= 1) {
return;
} else {
dispatch(PrevPage());
History.push(`/popular/games?page=${currentPage - 1}`);
}
};
const NextHandler = () => {
if (currentPage >= totalPage) {
console.log("Hello");
return;
} else {
dispatch(NextPage());
History.push(`/popular/games?page=${currentPage + 1}`);
}
};
//Fetch all popular games
const dispatch = useDispatch();
useEffect(() => {
async function fetchGames(page) {
const games = dispatch(AllPopularGame(page));
return games;
}
fetchGames(currentPage);
}, [dispatch, currentPage]);
// {`${currentPage} /popular/games/${popularGames.id}`}
return (
<Popular>
<h2>Popular Games </h2>
<AnimateSharedLayout type="crossfade">
<AnimatePresence>
{pathId && <GameDetail pathId={pathId} curPage={currentPage} />} //child component
</AnimatePresence>
{gameLoading ? (
<h2>Loading</h2>
) : (
<Games>
{allPopularGame.map((popularGames) => (
<Link
to={`/popular/games/${currentPage}/${popularGames.id}`}
key={popularGames.id}
>
<Game
name={popularGames.name}
img={popularGames.background_image}
rating={popularGames.rating}
id={popularGames.id}
key={popularGames.id}
released={popularGames.released}
/>
</Link>
))}
</Games>
)}
</AnimateSharedLayout>
<Page>
<Button onClick={PrevHandler}>
<span>Prev</span>
</Button>
<p>{currentPage}</p>
<Button onClick={NextHandler}>
<span>Next</span>
</Button>
</Page>
</Popular>
);
};
Github repo
Current code
Thanks for sharing the repository! It's quite clear now. You're using a link and re-routing the page - so although the components are the same and there isn't a re-paint on the UI, the entire page still mounts again. That's why your useEffect gets triggered everytime you click on a card (and even when you close one!).
Your UI state is managed by the path in the URL - this is really bad practice - especially considering that you aren't making any API calls to fetch data based on the URL.
Here's what I would suggest -
Replace the pathID variable that you are currently reading from the URL with a useState hook:
const [activeGameID, setActiveGameID]=useState(null);
Replace the Link component that you use to wrap the Game with a regular div and pass it an onClick handler to setActiveGameID:
<Games>
{popular.map((popularGames) => (
<div
onClick={() => setActiveGameID(popularGames.id)}
key={popularGames.id}
>
<Game
name={popularGames.name}
img={popularGames.background_image}
rating={popularGames.rating}
id={popularGames.id}
key={popularGames.id}
released={popularGames.released}
/>
</div>
))}
</Games>```
I think the problem you may be facing is that your animations are dependent on URL changes - in that case I would urge you to use some other way to animate your cards.

React problem with getting image from API

I am currently practicing React, and my goal is to build a simple app that searches movies and display some short info about them as results. I managed to pull data from API and store em in React hooks. I can access any data, but when I try to pull images I get error:
TypeError: Cannot read property 'medium' of null.
Here are the API results:
http://api.tvmaze.com/search/shows?q=$girls
I find an image that I want to use stored in {show.image.medium}
Here is my React code:
import React, {useState, useEffect} from 'react';
import Movie from './Movie';
const App = () => {
const [movies, setMovies] = useState([]);
useEffect(() => {
getMovies();
}, []);
const getMovies = async () => {
const response = await fetch(`http://api.tvmaze.com/search/shows?q=$girls`);
const data = await response.json();
setMovies(data);
console.log(data)
;}
return (
<div>
<form className='search-form'>
<input type='text' className='search-bar' placeholder='search movie'>
</input>
<button type='submit' className='search-button'>
Search
</button>
</form>
{movies.map(movie => (
<Movie title={movie.show.name} image={movie.show.image.medium} />
))}
</div>
);
};
export default App;
and Movie.js file:
import React from 'react';
const Movie = ({title, image}) => {
return(
<div>
<h1>{title}</h1>
<img src={image} alt=''/>
</div>
);
}
export default Movie;
so I basically mapped the results in movie array, but {movie.show.image.medium} just won't work, while pulling any other data work just fine.
I know that this is probably an easy fix, but I tried everything and searched for an answer for hours and still, nothing worked. I would really appreciate it if someone can explain to me what I am doing wrong. Thanks in advance!
In the API call there is one value where movie.show.image is technically null. For null you could not get any properties, even medium.
What you can do as a solution is the following:
{
movies.map(movie =>
movie.show.image ?
<Movie title={movie.show.name} image={movie.show.image.medium} /> :
null)
}
Additionally you need to return from Array.prototype.map().
Iteration from the API on my console:
I hope that helps!

Categories

Resources