My search input and pagination aren't triggering anything in Reactjs - reactjs

I'm fairly new to react.
My search input and pagination buttons aren't triggering anything and nothing comes up in the console, what is wrong with my code ?
I tried putting every functions in App.js to get it cleaner.
App.js
import React, { Component } from "react";
import List from './List';
let API = 'https://swapi.co/api/people/';
class App extends Component {
constructor(props) {
super(props);
this.state = {
results: [],
search: '',
currentPage: 1,
todosPerPage: 3
};
this.handleClick = this.handleClick.bind(this);
this.updateSearch = this.updateSearch.bind(this);
}
componentWillMount() {
this.fetchData();
}
fetchData = async () => {
const response = await fetch(API);
const json = await response.json();
this.setState({ results: json.results });
};
handleClick(event) {
this.setState({
currentPage: Number(event.target.id)
});
}
updateSearch(event) {
this.setState({
search: event.target.value.substr(0, 20)
});
}
render() {
return (
<div>
<List data={this.state} />
</div>
);
}
}
export default App;
List.js
import React, { Component } from 'react';
import Person from './Person';
class List extends Component {
render() {
const { data } = this.props;
const { results, search, updateSearch, handleClick, currentPage, todosPerPage } = data;
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = results.slice(indexOfFirstTodo, indexOfLastTodo).filter(item => {
return item.name.toLowerCase().indexOf(search) !== -1;
});
const renderTodos = currentTodos.map((item, number) => {
return (
<Person item={item} key={number} />
);
});
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(results.length / todosPerPage); i++) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map(number => {
return (
<li className="page-link" key={number} id={number} onClick={handleClick} style={{cursor: "pointer"}}>{number}</li>
);
});
return (
<div className="flex-grow-1">
<h1>Personnages de Star Wars</h1>
<form className="mb-4">
<div className="form-group">
<label>Rechercher</label>
<input
className="form-control"
type="text"
placeholder="luke skywalker..."
value={search}
onChange={updateSearch}
/>
</div>
</form>
<div className="row mb-5">{renderTodos}</div>
<nav aria-label="Navigation">
<ul id="page-number" className="pagination justify-content-center">{renderPageNumbers}</ul>
</nav>
</div>
);
}
}
export default List;
The value of the input doesn't change one bit if I type in it and if I right click on a page number, the console gets me Uncaught DOMException: Failed to execute 'querySelectorAll' on 'Element': '#4' is not a valid selector.
Any idea ?

The issue is that in the List class you attempt take updateSearch and handleClick out of data (which in turn comes from this.props). But updateSearch and handleClick are never placed inside data. If you log either of these methods to the console you'll see they are undefined.
To fix this, you need to pass updateSearch and handleClick from App to List. You can do this either by including the methods inside the data prop, or by passing them directly as their own props (which I would recommend).
For example, you can change the render method of App to look something like this:
render() {
return (
<div>
<List
data={this.state}
updateSearch={ this.updateSearch }
handleClick={ this.handleClick }
/>
</div>
);
}
Then in the render method of List you can do this:
const { data, updateSearch, handleClick } = this.props;
and remove the definitions of the two methods from the destructuring of data below.

Related

I am not able to see the value updated that I insert manually

I am trying to implement an onChange method that when the user type something it gets updated in real time and displayed in the div. The component that I am talking about is at the end of the code and it's called and it is an input that will be rendered 4 times on the dom. For a reason no value get shown on the div I mean {this.state.stake}. Could anyone help me in fixing that? Thanks
import React, { Component } from 'react';
import Stake from './stake';
class FetchRandomBet extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
bet: null,
value: this.props.value,
stake: ''
};
}
async componentDidMount() {
const url = "http://localhost:4000/";
const response = await fetch(url);
const data = await response.json();
this.setState({
loading: false,
bet: data.bets,
});
}
changeStake = (e) => {
this.setState({
stake: [e.target.value]
})
}
render() {
const { valueProp: value } = this.props;
const { bet, loading } = this.state;
if (loading) {
return <div>loading..</div>;
}
if (!bet) {
return <div>did not get data</div>;
}
return (
< div >
{
loading || !bet ? (
<div>loading..</div>
) : value === 0 ? (
<div className="bet-list">
<ol>
<p>NAME</p>
{
bet.map(post => (
<li key={post.id}>
{post.name}
</li>
))
}
</ol>
<ul>
<p>ODDS</p>
{
bet.map(post => (
<li key={post.id}>
{post.odds[4].oddsDecimal}
<div className="stake-margin">
<Stake
onChange={this.changeStake} />
{this.state.stake}
</div>
</li>
))
}
</ul>
</div>
Pass this.state.stake as a prop of Stake component.
<Stake
onChange={this.changeStake}
stake={this.state.stake}
/>
Then inside of the Stake component assign stake prop to value on an the input. It would look something like this.
const Stake =({stake, onChange})=>{
return <input value={stake} onChange={onChange} />
}

How to render a stateless functional component from another component

I'm new on React. I wrote a project on which there is a search component. the search works fine ( I checked on console.log) but I don't know how to call the stateless function component on which the search results should be shown?
class SearchCard extends Component {
// qQuery is a variable for query input
state = { qQuery: "" };
HandleSearch= async (e) => {
e.preventDefault();
const {data:cards} = await cardService.getAllCards();
var searchResults = cards.filter((item) =>
item.qTopic.includes(this.state.qQuery) ||
item.qArticle.includes(this.state.qQuery)
);
this.setState({ cards : searchResults });
// console.log('search results ',searchResults, ' cards ',this.state);
return <CardRender cards={cards}/>
}
render() {
return (
<React.Fragment>
<form className="form" onSubmit={ this.HandleSearch }>
<div className="input-group md-form form-sm form-1 pl-4 col-12">
const CardRender = ({cards,favs,onHandleFavs}) => {
return (
<div className="row">
{cards.length > 0 &&
cards.map((card) =>
<Card key={card._id}
card={card}
favs={favs}
onHandleFavs={() => onHandleFavs(card._id)}
/>
}
</div>
);
}
export default CardRender;
screenshot
You should add the <CardRender cards={cards}/> to the element render returns (at the place you want it to be) and render it if state.cards is not empty.
Something like this
class SearchCard extends Component {
// qQuery is a variable for query input
state = { qQuery: "" };
HandleSearch= async (e) => {
// ...
this.setState({ cards : searchResults });
}
render() {
return (
<div>
...
{cards?.length && <CardRender cards={cards}/>}
</div>
);
}
}

Problem in setState in react component updating data received by google maps API

I am trying to setState of the address and coordinates as given by the "react-places-autocomplete".
I have already stored those components by only issue is in updating it
However, the changes are not updating and even though the program compiles as soon as I select a place it crashes.
Especially for getLatLng there isn't enough documentation for me to understand if I have to access another component within that.
How do I update the state of the address and coordinates via handleSelect?. Thanks!
import React, { Component } from "react";
import PlacesAutocomplete, {
geocodeByAddress,
getLatLng
} from "react-places-autocomplete";
export default class Itinerary extends Component {
constructor(props) {
super(props);
this.state = {
address:"",
coordinates:{lat: null,lng: null}
};
}
Create2DArray(rows,columns) {
var x = new Array(rows);
for (var i = 0; i < rows; i++) {
x[i] = new Array(columns);
}
return x;
}
handleChange = address => {
this.setState({ address });
};
handleSelect = address => {
const results = geocodeByAddress(address);
const latLng = getLatLng(results[0]);
this.setState({ coordinates: latLng });
};
render() {
return (
<div className="container">
<div className="row">
<div className="col-md-6 mt-5 mx-auto">
<PlacesAutocomplete
value={this.state.address}
onChange={this.handleChange}
onSelect={this.handleSelect}
>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div>
<h1>Latitude: {this.state.coordinates.lat}</h1>
<h1>Longitude: {this.state.coordinates.lng}</h1>
<input size="50" height="40"{...getInputProps({ placeholder: "Type address" })} />
<div>
{loading ? <div>...loading</div> : null}
{suggestions.map(suggestion => {
const style = {
backgroundColor: suggestion.active ? "#41b6e6" : "#fff"
};
return (
<div {...getSuggestionItemProps(suggestion, { style })}>
{suggestion.description}
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
</div>
</div>
</div>
);
}
}
geocodeByAddress and getLatLng are asynchronous functions so you have to wait these function until receiving data (read more about geocodeByAddress here). In handleSelect, results[0] is undefined when selecting, so that why your component is crashed. You should try this:
export default class Itinerary extends Component {
...
handleSelect = async address => {
const results = await geocodeByAddress(address);
const latLng = await getLatLng(results[0]);
this.setState({ coordinates: latLng });
};
...
}

React: onClick event.currentTarget.textContent returning onClick of undefined

In my website, it currently shows users a list of movies based on their input.
When user clicks on the title of the movie that is rendered, I want to setState(chosenOne: the movie title they clicked).
Currently, when I click on movie title, it returns an error stating the following:
Uncaught TypeError: Cannot read property 'onClick' of undefined
at onClick (Fav.js:62)
Any way to fix this?
Any help is greatly appreciated.
import React, { Component } from 'react'
import axios from 'axios';
import '../styles/Rec.scss'
export class Fav extends Component {
constructor (props) {
super (props)
this.state = {
inputOne: '',
chosenOne: '',
movies:[],
};
}
onChangeOne = (event) => {
this.setState({
inputOne: event.target.value
},()=>{
if(this.state.inputOne && this.state.inputOne.length > 1) {
this.getInfo()
} else {
}
})
}
onClick = (event) =>{
this.setState({
chosenOne: event.currentTarget.textContent
})
console.log(this.state.chosenOne)
}
onSubmit = (event) => {
event.preventDefault();
}
getInfo = () => {
let url = `https://api.themoviedb.org/3/search/movie?api_key=''&language=en-US&query='${this.state.inputOne}'&page=1&include_adult=false`
axios.get(url)
.then(res => {
if (res.data) {
const movieData = res.data.results.filter(movie => movie.poster_path != null);
this.setState({movies: movieData});
}
console.log(this.state.movies)
})
}
render() {
return (
<div>
<h1>Favorite Movie of All Time</h1>
<form onSubmit={this.onSubmit}>
<input onChange={this.onChangeOne}/>
<div className="rec__container">
{this.state.movies && this.state.movies.slice(0,3).map(function(movie, genre_ids) {
return(
<div className="rec__sample">
<img className="rec__img" src={`https://image.tmdb.org/t/p/w500/${movie.poster_path}`} alt="movie poster"/>
<p onClick={event => this.onClick(event)}>{movie.title}</p>
</div>
)
})}
</div>
</form>
</div>
)
}
}
export default Fav
I am quite sure the problem is that this in this.onClick is undefined. This happens when it is not bound correctly to the the class.
I would recommend to change the function declared after map to an arrow function.
<div className="rec__container">
{this.state.movies &&
this.state.movies.slice(0, 3).map((movie, genre_ids) => {
return (
<div className="rec__sample">
<img className="rec__img" src={`https://image.tmdb.org/t/p/w500/${movie.poster_path}`} alt="movie poster" />
<p onClick={event => this.onClick(event)}>{movie.title}</p>
</div>
);
})}
</div>;
Also, you are binding onClick function in your constructor as well as using it as an arrow function. Bind does not work on arrow functions.
Either remove this.onClick = this.onClick.bind(this) or convert onClick into a simple function rather than an arrow function.

How to pass variables when calling components

I'm building a mini app and I want to get it cleaner.
So basically I want to have 3 components : App, List and Person.
Here is the code :
App.js
import React, { Component } from "react";
import List from './List';
class App extends Component {
constructor(props) {
super(props);
this.state = {
results: [],
search: '',
currentPage: 1,
todosPerPage: 3
};
this.handleClick = this.handleClick.bind(this);
}
componentWillMount() {
this.fetchData();
}
fetchData = async () => {
const response = await fetch(API);
const json = await response.json();
this.setState({ results: json.results });
};
handleClick(event) {
this.setState({
currentPage: Number(event.target.id)
});
}
updateSearch(event) {
this.setState({ search: event.target.value.substr(0, 20) });
}
render() {
return (
<List />
);
}
}
export default App;
List.js
import React, { Component } from 'react';
import Person from './Person';
class List extends Component {
render() {
const { results, currentPage, todosPerPage } = this.state;
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = results.slice(indexOfFirstTodo, indexOfLastTodo).filter(item => {
return item.name.toLowerCase().indexOf(this.state.search) !== -1;
});
const renderTodos = currentTodos.map((item, index) => {
return (
<Person item={this.state.item} index={this.state.index}/>
);
});
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(results.length / todosPerPage); i++) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map(number => {
return (
<li className="page-link" key={number} id={number} onClick={this.handleClick} style={{cursor: "pointer"}}>{number}</li>
);
});
return (
<div className="flex-grow-1">
<h1>Personnages de Star Wars</h1>
<form className="mb-4">
<div className="form-group">
<label>Rechercher</label>
<input
className="form-control"
type="text"
placeholder="luke skywalker..."
value={this.state.search}
onChange={this.updateSearch.bind(this)}
/>
</div>
</form>
<div className="row mb-5">{renderTodos}</div>
<nav aria-label="Navigation">
<ul id="page-number" className="pagination justify-content-center">{renderPageNumbers}</ul>
</nav>
</div>
);
}
}
export default List;
Person.js
import React, { Component } from 'react';
function Person(item, index) {
return (
<div className="col-lg-4 mb-4" key={index}>
<div className="card">
<div className="card-header">
<h4 className="mb-0">{item.name}</h4>
</div>
<div className="card-body">
<h5 className="card-title">Caractéristiques</h5>
<ul>
<li>Année de naissance : {item.birth_year}</li>
<li>Taille : {item.height} cm</li>
<li>Masse : {item.mass}</li>
<li>Couleur des yeux : {item.eye_color}</li>
<li>Couleur de cheveux : {item.hair_color}</li>
<li>Couleur de peau : {item.skin_color}</li>
</ul>
Sa fiche
</div>
</div>
</div>
)
}
export default Person;
My issue is that I get TypeError: Cannot read property 'results' of null when rendering.
Is it possible to have variable go into every file if I define them all in App.js ?
You are not passing the data the correct way. Try this:
In App.js pass to List component the needed data:
render() {
return (
<List data={this.state}/>
);
}
Then in render() method in List.js get the passed data prop, then extract the data from there:
render() {
const { data } = this.props;
const { results, search, currentPage, todosPerPage } = data;
// ...
// in currentTodos function dont use this.state.search but just "search", that we got above from the data variable
// ...
// also your renderTodos should look like this - use the item and index variables
const renderTodos = currentTodos.map((item, index) => {
return (
<Person item={item} index={index}/>
);
});
// ...
}
So your List.js should look like this:
import React, { Component } from 'react';
import Person from './Person';
class List extends Component {
render() {
// get the data
const { data } = this.props;
// get the properties
const { results, search, currentPage, todosPerPage } = data;
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = results.slice(indexOfFirstTodo, indexOfLastTodo).filter(item => {
// use "search" variable
return item.name.toLowerCase().indexOf(search) !== -1;
});
const renderTodos = currentTodos.map((item, index) => {
return (
// use item and index
<Person item={item} index={index}/>
);
});
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(results.length / todosPerPage); i++) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map(number => {
return (
<li className="page-link" key={number} id={number} onClick={this.handleClick} style={{cursor: "pointer"}}>{number}</li>
);
});
return (
<div className="flex-grow-1">
<h1>Personnages de Star Wars</h1>
<form className="mb-4">
<div className="form-group">
<label>Rechercher</label>
<input
className="form-control"
type="text"
placeholder="luke skywalker..."
value={search} // use search variable here too
onChange={this.updateSearch.bind(this)}
/>
</div>
</form>
<div className="row mb-5">{renderTodos}</div>
<nav aria-label="Navigation">
<ul id="page-number" className="pagination justify-content-center">{renderPageNumbers}</ul>
</nav>
</div>
);
}
}
export default List;
And your function in Person.js should have the following declaration, because the parameters are extracted from the passed props:
function Person({item, index}) {
// ...
}
You can use pass variables in your props of <List /> component by passing state inside render function of App.js while calling <List /> like this
render() {
//Passing Data inside props
<List data={this.state} />
}
and inside your List.js, You can access the data variable
const { results, currentPage, todosPerPage } = this.props.data;

Resources