Using two props in a component - javascript

I want to use two props in the grap_test component, one prop comes from a test.js component and the other comes from App.js. I have made a attempt where in graph_test.js i call them and use them in a temporary example to see if they are getting passed through.
However it says:
TypeError: Cannot read property 'map' of undefined
on the 17 line of graph_test.
Does annybody have any suggestions?
Graph_Test:
import React from 'react';
import $ from "jquery";
//<h1>{props.testing.map}</h1>
const Graph_Test = props => {
return(
<div>
<div>
{props.testing.map((item, idx) => {
return (
<label key={idx}>
<input className="region" type="radio" value={idx} />
<span>{idx}</span>
</label>
);
})}
</div><div>
<h1>{props.array[0]}</h1>
</div>
</div>
);
};export default Graph_Test;
Test
import React from 'react';
import $ from "jquery";
import Graph_Test from "./Graph_Test.js";
const Test = props => {
const total_regions = (JSON.parse(JSON.stringify(props.test)).length); // gets the number of regions
var ROI = [];
for (var i = 0; i < total_regions.length; i++) { // Array to represent which regions need to be displayed
ROI[i] = 0; // deault setting all regions equal 1 as must be displayed
}
//when a radio button is clicked change its corresponding in the array
const handleClick = (item, idx) => {
if(ROI[idx] == 1){ // if region displayed and clicked -> undisplay region
ROI[idx] = 0;
}else{ // if region not displayed and clicked -> display region
ROI[idx] = 1;
}
console.log(`Array ${ROI[idx]} with index ${idx} clicked`); // Used to test functionality of array
};
return (
// displays radio buttons depending on the number of objects in json
<div>
<div>
{props.test.map((item, idx) => {
return (
<label key={idx}>
<input className="region" type="radio" value={idx} onClick={() => handleClick(item, idx)}/>
<span>{idx}</span>
</label>
);
})}
</div>
<div>
<Graph_Test array = {ROI}/>
</div>
</div>
);
};
export default Test;
App.js
import "bootstrap/dist/css/bootstrap.css";
import React from "react";
import ReactPlayer from 'react-player'
import LeftPane from "./components/LeftPane.js";
import Video from "./components/Video.js";
//import Footer from "./components/Footer.js";
import Test from "./components/Test.js";
import Graph_Test from "./components/Graph_Test.js";
//import './App.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = { apiResponse: [] };
}
// Comunicate with API
callAPI() {
fetch("http://localhost:9000/IntensityAPI") //React app talks to API at this url
.then(res => res.json())
.then(res => this.setState({ apiResponse: res }));
}
componentWillMount() {
this.callAPI();
}
render() {
return (
<div className="App">
<div class="row fixed-top fixed-bottom no-gutters" >
<div class="col-3 fixed-top fixed-bottom">
<LeftPane></LeftPane>
</div>
<div class="offset-md-3 fixed-top fixed-bottom" >
<Video></Video>
</div>
<div class=" col-3 fixed-bottom">
<Graph_Test testing = {this.state.apiResponse}/>
<Test test = {this.state.apiResponse}/>
</div>
</div>
</div>
);
}
}
export default App;

The main problem here is in function component you don't have this. You were trying to use with this.props.testing.map(). Try to remove this which most probably solves the issue.
Instead try as the following:
return <div>
{props.testing && props.testing.map((item, idx) => {
return (
<label key={idx}>
<input className="region" type="radio" value={idx} />
<span>{idx}</span>
</label>
);
})}
</div><div>
<h1>{props.array[0]}</h1>
</div>
+1 suggestion after question has been updated:
I have extended my solution for null or undefined checks with &&. If you add props.testing && props.testing.map() then you won't have the error what you face in the second problem.
Also one good solution is you need to pass testing as well once you use the component:
<Graph_Test array={ROI} testing={[]} /> { /* passing testing with [] */ }
Or passing array with []:
<Graph_Test testing={this.state.apiResponse} array={[]} />
You need to have both props with default values or checking for undefined or null values in the <Graph_Test /> component.
I hope this helps!

In your first file, you reference this.props, but 'this' doesn't exit in a functional component. Just remove it

Related

How do I use the output of one axios request as a dependency for another when rendering components in React?

I have been struggling with this for some time and I am not sure how to solve the issue.
Basically, I am trying to render some components onto my Index page, this is my code below:
App.js
import Index from "./Components/Index"
import axios from "axios"
export default function App() {
const [movieList, setMovieList] = React.useState([])
let featured = []
let coming = []
let showing = []
React.useEffect(() => {
console.log("Ran App Effects")
axios.get(`API_CALL_TO_GET_LIST_OF_MOVIES`)
.then(res =>{
setMovieList(res.data)
})
}, [])
return(
<div>
{movieList.map(movie =>{
if(movie.status === 'featured'){
featured.push(movie.api_ID)
} else if (movie.status === 'upcoming'){
coming.push(movie.api_ID)
} else{
showing.push(movie.api_ID)
}
})}
<Index featured={featured} coming={coming} showing={showing}/>
</div>
)
}
In the code above I am receiving an array of Objects and based on what is in their status I am putting them in some empty arrays and sending them as props into my Index component.
This is what my index component looks like:
import React from "react"
import Header from "./Header"
import Footer from "./Footer"
import MovieCard from "./MovieCard"
import axios from "axios"
export default function Index(props) {
const [featuredMovies, setFeaturedMovies] = React.useState([])
const [comingMovies, setComingMovies] = React.useState([])
//const featured = [419704,338762,495764,38700,454626,475557]
//const coming = [400160,514847,556678,508439,524047,572751]
React.useEffect(() => {
console.log("Ran Effect")
axios.all(props.featured.map(l => axios.get(`API_CALL_TO_GET_SPECIFIC_MOVIE/${l}`)))
.then(axios.spread(function (...res){
setFeaturedMovies(res)
}))
.catch((err) => console.log(err))
axios.all(props.coming.map(l => axios.get(`API_CALL_TO_GET_SPECIFIC_MOVIE/${l}`)))
.then(axios.spread(function (...res){
setComingMovies(res)
}))
.catch((err) => console.log(err))
}, [])
return(
<body>
<Header />
<section className="home">
<div className="container">
<div className="row">
<div className="col-12">
<a className="home__title">FEATURED MOVIES</a>
</div>
{ featuredMovies.map(movie =>{
return <MovieCard movie={movie} featured={true} />
}) }
{console.log(props.featured)}
</div>
</div>
</section>
<section className="home">
<div className="container">
<div className="row">
<div className="col-12">
<a className="home__title">COMING SOON</a>
</div>
{ comingMovies.map(movie =>{
return <MovieCard movie={movie} featured={false} />
})}
</div>
</div>
</section>
<Footer/>
</body>
)
}
The issue I am running into is, whenever I run the app for the first time it works fine but then when I hit the refresh button the components do not render anymore
The only time it re-renders when I refresh the page is when I uncomment,
//const featured = [419704,338762,495764,38700,454626,475557]
//const coming = [400160,514847,556678,508439,524047,572751]
and replace the props.featured.map and props.coming.map with featured.map and coming.map hence using the hard coded values and not the values passed in from the props.
Any help with this would be much appreciated as I am completely stuck and currently pulling my hair out.
I took the liberty to tinker with your code. In the example below I've rearranged the data into three sets with the help of useMemo and by checking the status property of each movie. It is important to keep any data related logic outside of the render logic.
I also moved around some of your HTML structure. You were outputting a <body> tag inside of a <div>. The outer layer should be in control of the outer HTML structure, so I moved that HTML to the App component.
import { useState, useEffect, useMemo } from 'react'
import Header from "./Components/Header"
import Footer from "./Components/Footer"
import Index from "./Components/Index"
import axios from "axios"
export default function App() {
const [movieList, setMovieList] = useState([])
const featuredMovies = useMemo(() => {
return movieList.filter(({ status }) => status === 'featured');
}, [movieList]);
const upcomingMovies = useMemo(() => {
return movieList.filter(({ status }) => status === 'upcoming');
}, [movieList]);
const showingMovies = useMemo(() => {
return movieList.filter(({ status }) => status !== 'featured' && status !== 'upcoming');
}, [movieList]);
useEffect(() => {
axios.get(`API_CALL_TO_GET_LIST_OF_MOVIES`)
.then(res =>{
setMovieList(res.data)
})
}, [])
return (
<body>
<Header />
<Index data={featuredMovies} title="Featured Movies" featured={true} />
<Index data={upcomingMovies} title="Coming Soon" />
<Index data={showingMovies} title="Showing Now" />
<Footer/>
</body>
)
}
Since we now have three sets of movies (featured, upcoming, and playing) it would also make sense to have three components that handle those data sets instead of having one that handles multiple. Each Index component gets its own data set and other props to render the movies within it.
import MovieCard from "./MovieCard"
export default function Index({ data, title, featured = false }) {
return (
<section className="home">
<div className="container">
<div className="row">
<div className="col-12">
<a className="home__title">{title}</a>
</div>
{data.map(movie => {
return <MovieCard movie={movie} featured={featured} />
})}
</div>
</div>
</section>
);
}

array in a react component is getting converted into a number(which is of length of that array) after pushing an object into it

this is the code of App.js
import React,{useState,useRef,useEffect,createContext,useReducer} from 'react'
import "./App.css"
import NavBar from "./components/NavBar"
import Main from "./components/Main"
import Modal from "./components/Modal"
const Cart_ctx = createContext()
const App = () => {
const[cartItemCount,setCartItemCount] = useState(0)
const[modalOpen,setModalOpen] = useState(false)
const[cartItems,setCartItems] = useState([])
return (
<>
<Cart_ctx.Provider
value={{cartItemCount,
setCartItemCount,
modalOpen,
setModalOpen,cartItems,setCartItems}}>
<NavBar/>
<Main cartItems={cartItems}/>
<Modal modalOpen={modalOpen} cartItems={cartItems}/>
</Cart_ctx.Provider >
</>
)
}
export default App
export {Cart_ctx}
and this is the code of another component which returns jsx for single food item. i have passed product info object as props and this is the child of Main component
import React,{useContext,useState} from 'react'
import {Cart_ctx} from "../App"
const Item = ({product}) => {
console.log(product);
const cart_ctx = useContext(Cart_ctx)
const [addItem,setAddItem] = useState(1)
const handleclick=()=>{
cart_ctx.setCartItemCount((prev)=>prev+addItem)
cart_ctx.setCartItems((prev)=>prev.push(product)) // this line throws error when i clicking the ADD button twice
setAddItem(1)
console.log(cart_ctx.cartItems);
}
return (
<>
{
<div className="item">
<div className="item-info">
<h2>{product.name}</h2>
<p >{product.price}</p>
<i>{product.discription}</i>
</div>
<div className="add-cart">
<label htmlFor="qty"> Qty :</label>
<input id="qty" type="number" value={addItem} onChange={(e)=>setAddItem(parseInt(e.target.value))} min="1" max="5"/><br/>
<button className="button" onClick={handleclick}> ADD </button>
</div>
</div>
}
</>
)
}
export default Item
if i press the ADD button in above component twice ,react throws an error saying .push() is not a function, although it perfectly pushes the product object to the cartItem array the first time. when i log the cartItem value in other component it print the length of the cartItems array.
import React from 'react'
const Modal = ({modalOpen,cartItems}) => {
console.log(cartItems); //-----> (** this line logs exact length of the array **)
if (!modalOpen) return null
return (
<div className="overlay">
<div className="cart-modal">
<h1>YOUR CART</h1>
{cartItems.map((item)=>{
return(
<div>{item.name}</div>
)
})}
</div>
</div>
)
}
export default Modal
You are right to suspect there is a problem with this line:
cart_ctx.setCartItems((prev)=>prev.push(product))
The first thing to know is that the array push function returns the new length of the array. Second, the setCartItems function will set the state of cart items to be whatever the provided function returns. Since your provided function, (prev)=>prev.push(product), returns the result of push, cart items becomes a number instead of an array.
What you actually want is for the inner function return an array, which you can do by creating a new array:
cart_ctx.setCartItems((prev) => [...prev, product])
I sometimes forget this too about the setWhatever methods; to help me remember, I mentally think of them as assignment operators. Doing cartItems = cartItems.push(product) doesn't make sense, but cartItems = [...cartItems, product] does.

React functional component with mapped Child functional component with onClick function

Hi I have mapped some json data named "projectsData" and I am trying to "bind" an onClick event with a setState hook. The mapping works except for the "onClick" does not work when clicking the grid item. In my case I want to update filterproject value with the project.id value from that target.
Right now when I click an item it does nothing.
How do I successfully map a function to "onClick" while using functional components?
Below is the parent Component
import React, { useEffect, useState } from "react";
import projectsData from '../data/projectsData';
import Project from './Projects';
const App = (props) => {
const [projects] = useState(() => (projectsData.map((project) => <Project id={project.id} project={project} onClick={() => {setFilterProject(project.id)}}/>)));
const [filterproject, setFilterProject] = useState(null);
return (
<body>
<div id='sepLine'>
<div id="visHolder">
<div id="visContainer" style={{position: "relative", width: "840px", height: "1823px"}} >
{projects}
</div>
</div>
</div>
</body>
);
}
export default App;
And here is the Child Component - "Project"
import React, { useRef } from "react";
const Project = (props) => {
const {projectClick, project} = props;
return (
<div className={`lineDiv gridItem y${project.start}-${project.end} ${project.kind}`} style={{positon: "absolute"}} onClick={projectClick}>
<h5>{project.title}</h5>
<br></br>
<p className="year">
<span className="yearsstart">{project.start}</span> - <span className="yearsend">{project.end}</span>
<br></br>
<span className="kind">{project.kind}</span>
</p>
</div>
)
}
export default Project
below is a screen grab of Console showing one of the mapped projects and it's onClick parameters. I can see it but when I click nothing happens. Any help would be great!
You pass click handler to a prop called onClick when setting initial state
const [projects] = useState(() => projectsData.map((project) => (
<Project
id={project.id}
project={project}
onClick={() => {setFilterProject(project.id)}}
/>
));
but access it as projectClick in the component
const { projectClick, project } = props;
...
<div
className={`lineDiv gridItem y${project.start}-${project.end} ${project.kind}`}
style={{positon: "absolute"}}
onClick={projectClick}
>
...
</div>
Fix by accessing the correct prop
const { onClick, project } = props;
...
<div
className={`lineDiv gridItem y${project.start}-${project.end} ${project.kind}`}
style={{positon: "absolute"}}
onClick={onClick}
>
...
</div>

Moving data between react components

So I'm trying to break the component on my App.js into a smaller component, that being my Sidebar.js. I took a small section of the code and put it in its own Sidebar.js file but no matter what I've tried, I cant call my function getNotesRows() from App.js without it being unable to find it or this.states.notes being undefined.
I just want it to send the code back and forth. This is a demo app, so I know it's not the most practical.
import React, { Component } from "react";
import classNames from "classnames";
import logo from "./logo.svg";
import checkMark from "./check-mark.svg";
import "./App.css";
import Sidebar from "./components/Sidebar.js";
class App extends Component {
constructor(props) {
super(props);
this.state = {
notes: [],
currentNoteIndex: 0
};
this.markAsRead = this.markAsRead.bind(this);
this.selectNote = this.selectNote.bind(this);
console.log("Test started 2.25.19 19:23");
}
componentWillMount() {
fetch('/notes')
.then(response => response.json())
.then(
notes => {
this.setState({
notes: notes,
currentNoteIndex: 0
})
}
)
.catch(
error => {
console.log('Ooops!');
console.log(error);
}
);
}
markAsRead() {
this.setState(currentState => {
let marked = {
...currentState.notes[currentState.currentNoteIndex],
read: true
};
let notes = [...currentState.notes];
notes[currentState.currentNoteIndex] = marked;
return { ...currentState, notes };
});
}
selectNote(e) {
this.setState({ currentNoteIndex: parseInt(e.currentTarget.id, 10) });
}
getTotalUnread() {
let unreadArray = this.state.notes.filter(note => {
return note.read === false;
})
return unreadArray.length;
}
getNotesRows() {
return this.props.notes.map(note => (
<div
key={note.subject}
className={classNames("NotesSidebarItem", {
selected:
this.props.notes.indexOf(note) === this.props.currentNoteIndex
})}
onClick={this.selectNote}
id={this.props.notes.indexOf(note)}
>
<h4 className="NotesSidebarItem-title">{note.subject}</h4>
{note.read && <img alt="Check Mark" src={checkMark} />}
</div>
));
}
// TODO this component should be broken into separate components.
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Notes Viewer Test App</h1>
<div>
Unread:
<span className="App-title-unread-count">
{this.getTotalUnread()}
</span>
</div>
</header>
<div className="Container">
<Sidebar />
<section className="NoteDetails">
{this.state.notes.length > 0 && (
<h3 className="NoteDetails-title">
{this.state.notes[this.state.currentNoteIndex].subject}
</h3>
)}
{this.state.notes.length > 0 && (
<p className="NoteDetails-subject">
{this.state.notes[this.state.currentNoteIndex].body}
</p>
)}
{this.state.notes.length > 0 && (
<button onClick={this.markAsRead}>Mark as read</button>
)}
{this.state.notes.length <= 0 && (
<p>
No Notes!
</p>
)}
</section>
</div>
</div>
);
}
}
export default App;
Above is my App.js
and below is the Sidebar.js that I'm trying to create
import React, { Component } from "react";
import "../App.css";
import App from "../App.js";
class Sidebar extends React.Component{
constructor(props) {
super(props);
}
render(){
return (
<section className="NotesSidebar">
<h2 className="NotesSidebar-title">Available Notes:</h2>
<div className="NotesSidebar-list">{App.getNotesRows()}</div>
</section>
)}}
export default Sidebar;
You cannot access a method like that. You need to pass the method as a prop and use it in the child.
<Sidebar getNotesRows={this.getNotesRows} />
and in Sidebar use
<div className="NotesSidebar-list">{this.props.getNotesRows()}</div>
In your sidebar, you're trying to call getNotesRows() from App, but Sidebar doesn't need access to app (you shouldn't have to import App in Sidebar.js). Instead, you should pass the function from App to your Sidebar component, and reference it from Sidebar's props.
In App.js, you'll need to bind getNotesRows and pass it to sidebar.:
<Sidebar getNotesRows={ this.getNotesRows } />
Then in Sidebar.js, you'll need to reference getNotesRows in your render method:
render() {
const notes = this.props.getNotesRows();
return (
<section className="NotesSidebar">
<h2 className="NotesSidebar-title">Available Notes:</h2>
<div className="NotesSidebar-list">{ notes }</div>
</section>
);
}
It seems like the problem here is that you are trying to use a class function as a static property, to put it simply, you have not initialized the App class when you import it into your sidebar(?), thus no static function was found on your App class so you can call App.getNotesRows() maybe you should re-think your components and separate them in container-components using a Composition Based Programming approach instead of OO approach.

Array being populated with JSX Elements not rendering/updating

This may be a quick fix but I have been racking my brain for the past little while, and could really use another set of eyes to take a look.
Basically I am trying to render an array full of generated JSX elements. I fell like I have done this a million times, but it does not seem to work here.
Heres the code:
import React, { Fragment } from 'react'
import css from './Search.scss';
import Header from '../SectionHeader/Header';
import SearchItem from '../SearchItem/SearchItem';
const Search = (props) => {
const { coinObject, coinKeys } = props;
let searchResults = []; // Array in question
const findResults = (searchText) => {
searchResults = []; // Reset the array to blank for each new character typed in input
for(let i = 0; i < coinKeys.length; i++) {
const { FullName } = coinObject[coinKeys[i]]; // App specific logic, not important, or the problem here
if(FullName.toLowerCase().includes(searchText) && (searchResults.length < 5)) {
console.log(FullName, searchText); // Prints the correct Full name based off of the searched text
searchResults.push(<SearchItem key={i} searchText={FullName} />);
}
}
console.log(searchResults); // Prints the updated array with all react elements
}
return (
<Fragment>
<Header title='Find Coins!' />
<div className={css.searchContainer}>
<div className={css.inputContainer}>
<input onChange={input => findResults(input.target.value)} className={css.searchInput} type='text' placeholder='Start Typing a Coin'/>
</div>
{ searchResults }
</div>
</Fragment>
);
}
export default Search;
And the SearchItem Component, which is super simple:
import React from 'react'
import css from './SearchItem.scss';
const SearchItem = (props) => {
return (
<div className={css.searchItem}>
{props.searchText}
</div>
)
}
export default SearchItem;
For a little bit of context, this component just gets a giant object of data, and will display the first 5 instances of what matches the input text. I am trying to make one of those search filter things, where as you type it suggests things that match from the data.
The array gets updated, and I can see the JSX objects in the array, they just do not render. I have a feeling it is due to the array not re-rendering?
Any help is much appreciated. Thanks!
You could make the Search component into a stateful component and store the searchResults in your state instead, so that when it is updated your component will be re-rendered.
Example
class Search extends React.Component {
state = { searchResults: [] };
findResults = searchText => {
const { coinObject, coinKeys } = this.props;
const searchResults = [];
for (let i = 0; i < coinKeys.length; i++) {
const { FullName } = coinObject[coinKeys[i]];
if (
FullName.toLowerCase().includes(searchText) &&
searchResults.length < 5
) {
searchResults.push(FullName);
}
}
this.setState({ searchResults });
};
render() {
return (
<Fragment>
<Header title="Find Coins!" />
<div className={css.searchContainer}>
<div className={css.inputContainer}>
<input
onChange={event => findResults(event.target.value)}
className={css.searchInput}
type="text"
placeholder="Start Typing a Coin"
/>
</div>
{this.state.searchResults.map((fullName, i) => (
<SearchItem key={i} searchText={fullName} />
))}
</div>
</Fragment>
);
}
}

Categories