Search functionality and fetch api in React - reactjs

I'm working on an app that makes a call to Unsplash's API and displays the response. I was able to get/display the response easily with just the /photos endpoint when I had the fetch request in the componentDidMount(), but I want to make the app searchable, so I added performSearch() with a default query.
But adding performSearch caused this error:
TypeError: cannot read property 'map' of undefined
This is the JSON I'm getting back when I test:
Search endpoint + query
I've tried other solutions I've found on the forums, but so far nothing's fixed the problem. I'm definitely getting back an array, so shouldn't map work?
class App extends Component {
constructor() {
super();
this.state = {
results: [],
loading: true
};
}
componentDidMount() {
this.performSearch();
}
performSearch = (query = 'camping') => {
fetch(`https://api.unsplash.com/search/photos?page=3&query=${query}&client_id=${client_id}`)
.then(response => response.json())
.then(responseData => {
this.setState({
results: responseData.data,
loading: false
});
})
.catch(error => {
console.log('Error fetching and parsing data', error);
});
}
render() {
return (
<div className = "App">
<SearchPhotos onSearch = {this.performSearch} />
<div>
{
(this.state.loading) ? <p>Loading</p> :<PhotoList results={this.state.results} />
}
</div>
</div>
);
}
}
export default App;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
import React from 'react';
const PhotoList = props =>
<ul>
{props.results.map((result, index) =>
<li key={index}>
<img src={result.urls.small} key={result.id} />
</li>
)}
</ul>;
export default PhotoList;
import React, { Component } from 'react';
class SearchPhotos extends Component {
state = {
searchText: ''
}
onSearchChange = e => {
this.setState({
searchText: e.target.value
});
}
handleSubmit = e => {
e.preventDefault();
this.props.onSearch(this.query.value);
e.currentTarget.reset();
}
render() {
return(
<form className="search-form" onSubmit={this.handleSubmit}>
<input type="search"
onChange={this.onSearchChange}
name="search"
ref={(input) => this.query = input}
placeholder="Search..." />
<button className="search-button" type="submit" id="submit">Go!</button>
</form>
);
}
}
export default SearchPhotos;

performSearch = (query = 'camping') => {
fetch(`https://api.unsplash.com/search/photos?page=3&query=${query}&client_id=${client_id}`)
.then(response => response.json())
.then(responseData => {
this.setState({
results: responseData.results,
loading: false
});
})
.catch(error => {
console.log('Error fetching and parsing data', error);
});
}
responseData.results is the array that your are looking for.

Related

React Loader - Trying to get a loader when api request is made and to stop it when response is fetched

What I want is that, when I click on search button, then a loader/spinner should appear on screen until the data is fetched, when the data is fetched it should disappear.
Container.jsx
import React from 'react';
import './container.css'
import Weather from './weather';
var Loader = require('react-loader');
class Container extends React.Component {
constructor(props) {
super(props);
this.state = {
location: "",
weather: [],
loaded:false
};
}
handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
componentDidMount() {
this.setState.loaded=false;
}
continue = (e) => {
this.setState({loaded:true});
const { location } = this.state;
const rawurl = 'http://api.weatherstack.com/current?access_key=d8fefab56305f5a343b0eab4f837fec1&query=' + location;
const url = rawurl;
e.preventDefault();
if (location.length < 1) {
return alert('Enter the details');
}
else {
fetch(url)
.then(response => response.json())
.then(data =>{
this.setState({weather:[data],loaded:false});
})
.catch(err => console.log("error ",err))
}
};
render() {
console.log(this.state.weather);
const weather =
this.state.weather.length> 0 ?
this.state.weather.map(item => (<Weather location={item.location.name} temperature={item.current.temperature} weather={item.current.weather_descriptions[0]} windSpeed={item.current.wind_speed} windDegree={item.current.wind_degree} windDir={item.current.wind_dir} humidity={item.current.humidity} visibility={item.current.visibility} />
))
:<span></span>
return (
<div id="container">
<div class="searchicon">
<input type="search" placeholder="Enter City !!" type="text" name="location" value={this.state.location} onChange={this.handleChange}></input>
<label class="icon">
<button onClick={this.continue} id="btn"><span class="fa fa-search"></span></button>
</label>
</div>
<div>
<Loader loaded={this.state.loaded}>
{weather}
</Loader>
</div>
</div>
);
}
}
export default Container;
What I am using here is react-loader
But right now,its not happening in the way I want, sometime before clicking the serach button it appears and when data is fetched it stops, i want to start it when the api req is made after click on search button and to stop when data is fetched.
first of all you should in the setState after fetching the data to make
this.setState({weather:[data],loaded:true});
second there's another way to do it you can separate the code in the return function like
{ !this.state.loaded ? <Loader loaded={false} options={options} className="spinner" />:{weather}}
as per the Doc in npm you can check it react-loader

How make post request without issues with setState in react?

I have value that i want to update and send an update post request , so i tried this:
EDIT
i add more code that represenet the all component:
import React from 'react'
import axios from 'axios'
import {Card} from 'react-bootstrap'
class SubmitPage extends React.Component{
constructor(props) {
super(props)
this.state={formname:'',
fields:[''],
values:[''],
submissions:0
}
}
handleChange=this.handleChange.bind(this)
saveChanges=this.saveChanges.bind(this)
componentDidMount=this.componentDidMount.bind(this)
//get the Object from DB and initialize new state
componentDidMount()
{
axios.get('http://localhost:2000/form/'+this.props.match.params.id).then(response =>{
this.setState({
formname:response.data.formname,
fields:response.data.fields,
submissions:response.data.submissions
})
})
.catch(function(error){
})
}
handleChange(e,index)
{
this.setState({
values: {
...this.state.values,
[index.index]: e.target.value
}
});
}
async saveChanges(e)
{
e.preventDefault();
//update the values in field array
const {values}=this.state;
const fields=this.state.fields;
Object.keys(values).map(key =>
fields[key].push(values[key])
)
//get submission and update it
axios.get('http://localhost:2000/form/'+this.props.match.params.id)
.then(response =>{
this.setState({
submissions: response.data.submissions
})
console.log(this.state.submissions)
})
.catch(function(error){
console.log(error);
})
//let submissionsUpdate=this.state.submissions;
// submissionsUpdate=submissionsUpdate+1;
this.setState({
submissions: this.state.submissions+1}, ()=> {
const form={
formname:this.state.formname,
fields:this.state.fields,
submissions: this.state.submissions
}
axios.post('http://localhost:2000/form/update/'+this.props.match.params.id,form) //post after sumbmission value update
.then(res => console.log(res.data));
});
window.location='/'
}
render(){
const {fields}=this.state
return(
<div style={{width:'35%' , margin:"0 auto" ,marginTop:"3%"}}>
<Card >
<Card.Header as="h5">{this.state.formname}</Card.Header>
<Card.Body>
<Card.Text>
<div>{fields.length!==0&&fields.map((fields,index)=> {
return (
<div key={fields[0]}>
<div style={{fontSize:'15px' , marginBottom:"-1px"}}>{fields[0]}</div>
<div><input onChange={(e) => this.handleChange(e, {index})} type={fields[1]}></input></div>
</div>
)
})
}
</div>
<button style={{width:100, marginTop:"10px", marginBottom:"20px"}} type="submit" onClick={this.saveChanges} className="btn btn-dark"> Submit</button>
</Card.Text>
</Card.Body>
</Card>
</div>)
}
}
export default SubmitPage
my goal is to increase counter by 1 , but because the setState is asych, something goes wrong , and the result is or increasing by strange number or no change.
what i am doing wrong?

Calling Fetch in React App.js with Prop Drilling

So I would like to call fetch from a function (submitURL) in App.js. If I create "componentDidMount()" in App.js and call fetch there, it works, but not from submitURL. I believe this is because submitURL is called via prop drilling. How would I call fetch from submitURL?
App.js
class App extends Component {
state = {
channelURL: '',
videos: []
}
submitURL = (value) => {
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => console.log(json))
this.setState({
channelURL: value
});
}
render() {
console.log(this.state)
return (
<div className="App">
<h1> Title </h1>
<Channel submitURL={this.submitURL} url={this.state.channelURL}/>
<Videos videos={this.state.videos}/>
</div>
);
}
}
export default App;
Channel.js
class Channel extends Component {
state = {
value: this.props.url
}
handleChange = (e) => {
this.setState({
value: e.target.value
});
}
render() {
return (
<div>
<h1> Enter Channel URL </h1>
<form onSubmit={this.props.submitURL.bind(this, this.state.value)}>
URL: <input type="text" name="url" value={this.state.value} onChange={this.handleChange}/>
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
export default Channel;
submitURL = (value) => {
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => this.setState({
channelURL: json
}))
}

typeError: cannot read property 'users' of null in React

I'm trying to create a change password page in react and i'm getting typeError: cannot read property 'users' of null. The code works for other form pages(where i'm doing PUT and CREATE) but not this one
I tried binding the submit handler to the this keyword but that didn't work.
Also tried binding the handlePasswordChange to the this keyword
./formchange
import React from "react";
import { Link } from "react-router-dom";
var createReactClass = require("create-react-class");
var FormChange = createReactClass({
//setting initial state
getInitialState() {
return {
password: {}
};
},
handlePasswordChange(e) {
this.setState({
password: e.target.value
});
},
handleSubmit(e) {
e.preventDefault();
this.props.onSubmit(this.state);
this.props.history.push("/");
},
render() {
return (
<form
name="categories_post"
className="form-horizontal"
onSubmit={this.handleSubmit}
>
<div id="change_password">
<div className="form-group">
<label
className="col-sm-2 control-label required"
htmlFor="password"
>
Password
</label>
<div className="col-sm-10">
<input
type="text"
value={this.state.password}
onChange={this.handlePasswordChange}
id="password"
className="form-control"
/>
</div>
</div>
<button
type="submit"
id="formChangeSubmit"
className="btn btn-default"
>
Submit
</button>
</div>
</form>
);
}
});
export default FormChange;
./passwordupdate
import React from "react";
import { updateUsers, fetchUsers } from "./actions/appactions";
import FormChange from "./formchange";
var createReactClass = require("create-react-class");
const Update = createReactClass({
getIntitialState() {
return {
users: {}
};
},
componentWillReceiveProps(props) {
this.setState(props);
},
componentDidMount() {
fetchUsers(this.props.match.params.usersId)
.then(data => {
this.setState(state => {
state.users = data;
return state;
});
})
.catch(err => {
console.error("error", err);
});
},
handleSubmit(data) {
updateUsers(this.state.users.id, data);
},
render() {
return (
<div>
<FormChange
onSubmit={this.handleSubmit.bind}
password={this.state.users.password}
/>
</div>
);
}
});
export default Update;
//fetchusers function
export function fetchUsers(id) {
return fetch("https://localhost:44341/api/users/" + id, {
method: "GET",
mode: "cors"
})
.then(res => res.json())
.catch(err => err);
}
<FormChange
onSubmit={this.handleSubmit.bind(this)}
password={this.state.users.password}
/>
make this change and check
I'm not sure but you have data in handleSubmit as parameter but you don't pass it.
try this
You can call function like this:
handleSubmit=(data)=> {
updateUsers(this.state.users.id, data);
},
and call it
onSubmit={(data)=> this.handleSubmit(data)}
The problem was in ComponentDidMount(). The state was always null, had to change it to this
componentDidMount() {
fetchTheUsers(this.props.match.params.usersId)
.then(data => {
this.setState({
users: data
});
})
I did it that way initially because that's how it worked for my other update files. Hope this is useful to someone else.

How to update data between components?

Image of the consoleI'm trying to create a website using the movie DB API. I've created a carousel with some movies and want to open a new page with comprehensive information about a movie by clicking on the movie poster. I'm using componentDidMount to fetch data in one component to create a UI and I pass a movie ID to another component where I use componentWillReceiveProps to fetch another data by using the ID. It worked until I started using state, so now it shows two arrays in the console before I click on a movie poster and when I click on a poster it shows an array and loads a movie data from API then if I click on another poster it loads two different objects with the previous and current movie. I found out that componentWillReceiveProps is dangerous to use but componentDidUpdate works in the same manner.
The main idea is when a user clicks on a poster it gets its id and sends it to another component where the id goes to a link
with complete information about the movie. Are there any patterns to achieve it?
class Data extends Component {
state = {
movies: [],
movieId: null
};
onClick = e => {
this.setState({
movieId: e.target.id
});
console.log(e.target.id);
};
componentDidMount() {
fetch(url)
.then(res => res.json())
.then(data => {
let movies = data.results.map(item => {
return (
<Link to="/movieInfo">
<div className="overlay" onClick={this.onClick}>
<img
src=
{`https://image.tmdb.org/t/p/w500/${item.poster_path}`}
alt={item.title}
id={item.id}
/>
</div>
</Link>
);
});
this.setState({
movies: movies
});
})
.catch(err => console.log(err));
}
render() {
const { movies, movieId } = this.state;
return (
<div className="carousel">
<Slider movie={movies} />
<div className="notShow">
<AdditionalInfo id={movieId} />
</div>
</div>
);
}
}
class AdditionalInfo extends Component {
state = {
movie: []
};
componentDidUpdate(prevProps) {
if (prevProps.id !== null && prevProps.id !== this.props.id) {
fetch(
`https://api.themoviedb.org/3/movie/${
prevProps.id
}?api_key=81f382d33088c6d52099a62eab51d967&language=en-US`
)
.then(res => res.json())
.then(data =>
this.setState({
movie: data
})
);
} else {
return null;
}
}
render() {
const { movie } = this.state;
console.log(movie);
return (
<div className="movieInfo-container">
{/* <section className="title" />
<section className="cast">{movie.id}</section> */}
work
</div>
);
}
}
let movieArr = [];
class Slider extends Component {
state = {
currentIndex: 0,
translateValue: 0
};
createNestedArr = () => {
while (this.props.movie.length) {
movieArr.push(this.props.movie.splice(0, 5));
}
return movieArr.map((item, i) => {
return <Slide key={i} movieGroup={item} />;
});
};
nextPic = () => {
if (this.state.currentIndex === movieArr.length - 1) {
return this.setState({
currentIndex: 0,
translateValue: 0
});
}
this.setState(prevState => ({
currentIndex: prevState.currentIndex + 1,
translateValue: prevState.translateValue - this.slideWidth()
}));
};
prevPic = () => {
if (this.state.currentIndex === movieArr.length + 1) {
return this.setState({
currentIndex: 0,
translateValue: 0
});
} else if (this.state.currentIndex === 0) {
return this.setState({
currentIndex: 0,
translateValue: 0
});
}
this.setState(prevState => ({
currentIndex: prevState.currentIndex - 1,
translateValue: prevState.translateValue + this.slideWidth()
}));
};
slideWidth = () => {
return document.querySelector(".new-releases-slide").clientWidth;
};
render() {
return (
<React.Fragment>
<div
className="movie-carousel"
style={{
transform: `translateX(${this.state.translateValue}px)`,
transition: "transform ease-out 0.45s"
}}
>
{this.createNestedArr()}
</div>
<LeftArrow prevPic={this.prevPic} />
<RightArrow nextPic={this.nextPic} />
</React.Fragment>
);
}
}
const Slide = props => {
const { movieGroup } = props;
return <div className="new-releases-slide">{movieGroup}</div>;
};
Use componentDidMount in your AdditionalIno component. You need to pass the id of the clicked movie to MovieInfo component. This <Link to="/movieInfo"> needs to <Link to={'/movieInfo/${item.id}'}> and in your MovieInfo component access the id using const { id } = this.props.match.params;.
import React, { Component } from 'react';
import Loader from "react-loader-spinner";
class AdditionalInfo extends Component {
state = {
movie: [],
isLoading: true,
};
componentDidMount = () => {
const { id } = this.props;
if (!id) {
return;
}
fetch(
`https://api.themoviedb.org/3/movie/${id}?api_key=81f382d33088c6d52099a62eab51d967&language=en-US`
)
.then(res => res.json())
.then(data =>
this.setState({
movie: data,
isLoading: false,
})
);
}
render() {
const { movie } = this.state;
return (
<div className="movieInfo-container">
{this.state.isLoading
? <Loader type="Puff" color="#00BFFF" height="100" width="100" />
: <div><section className="title" />
<h1>{movie.title}</h1>
<section className="cast">ID: {movie.id}</section>
<h2>Overview</h2>
<p>{movie.overview}</p></div>
}
</div>
);
}
}
export default AdditionalInfo;
then in your Data component change your componentDidMount
componentDidMount = () => {
fetch(url)
.then(res => res.json())
.then(data => {
let movies = data.results.map(item => {
return (
<Link to={`/movieInfo/${item.id}`}>
<div className="overlay" onClick={this.onClick}>
<img
src=
{`https://image.tmdb.org/t/p/w500/${item.poster_path}`}
alt={item.title}
id={item.id}
/>
</div>
</Link>
);
});
this.setState({
movies: movies
});
})
.catch(err => console.log(err));
}
In your MovieInfo do something like
class MovieInfo extends Component {
render() {
const {id} = this.props.match.params;
return (
<div>
<AdditionalInfo id={id} />
</div>
)
}
}
Your router should be like
<Route path="/movieInfo/:id" exact component={MovieInfo} />
Working Demo

Resources