I'm new to react. I have a component here that will automatically load the data from a database (mongo) via express server url when the page was loaded.
componentDidMount() {
var self = this;
fetch('/movies')
.then((resp) => resp.json())
.then(movies => self.setState({
movies: movies
}))
.catch((err) => console.log(err));
}
this will load all data and display them on screen and when the user types in to the textbox it will update the display to the search term:
movieSearch(term){
var self = this;
axios.get('/moviesbytitle', {
params: {
term: term
}
})
.then(movies => self.setState({
movies: movies.data
}))
.then(() => console.log('this is is a state >>>>+++', self.state))
.catch((err) => console.log(err));
}
render(){
const movieSearch = _.debounce((term) => { this.movieSearch(term) }, 300);
return (
<div>
<MovieTopBar />
<SearchBar onSearchTermChange={movieSearch}/>
<MovieList movies={ this.state.movies }/>
<MovieFooter />
</div>
);
}
};
class SearchBar extends Component{
constructor(props){
super(props);
this.state = { term: '' };
this.onInputChange = this.onInputChange.bind(this);
}
<input type="text" value={this.state.term} onChange={event => this.onInputChange(event.target.value)} className="form-control" id="" placeholder="Enter the title of the movie..." />
onInputChange(term){
this.setState({ term });
this.props.onSearchTermChange(term);
}
}
I am trying to find a way how can I update the data searched -> to the data when it was first mounted w/c displays the whole data whenever the textbox for search is emptied. Is there a specific lifecycle method to do that? or do I need to do a separate function? Thanks for the help in advance!
I would suggest
this.movieSearch = _.debounce((term) => { this.movieSearch(term) }, 300);
move it to constructor
so you can avoid creating a new function each render, this will be expensive on memory
you also do not need var self = this;
using anonymous functions like () => {} will preserve this
and coming back to question, if I understood it, you want to show original movie list when user deletes input in search,
componentDidMount() {
var self = this;
fetch('/movies')
.then((resp) => resp.json())
.then(movies => {
self.setState({ movies: movies });
this.originalResults = movies;
})).catch((err) => console.log(err));
}
you can do it by keeping reference to original results
and then you can add
movieSearch(term){
if (term === '') {
this.setState({movies:this.originalResults})
return; // exit the search function
} ...rest of function
As #AlexGvozden said, you should move your movieSearch to be part of the component class, so
class ShowMoviesList {
constructor() {
this.movieSearch = this.movieSearch.bind(this);
}
componentDidMount() {
this.getAllMovies();
}
getAllMovies() {
fetch('/movies')
.then((resp) => resp.json())
.then(movies => this.setState({
movies: movies
}))
.catch((err) => console.log(err));
}
getMovieByTitle(term) {
axios.get('/moviesbytitle', {
params: {
term: term
}
})
.then(movies => this.setState({
movies: movies.data
}))
.then(() => console.log('this is is a state >>>>+++', this.state))
.catch((err) => console.log(err));
}
movieSearch(term) {
// remember prevents blank spaces
const currentTerm = term ? term.trim() : term;
// remember clear last getMovieByTitle call
if (this.timer) {
clearTimeout(this.timer);
}
// if term is empty, so call getAllMovies again
// prevent call cache movies, because cache movie will always return the first call
// but won't render new movies if exists
if (!currentTerm) {
return this.getAllMovies();
}
this.timer = setTimeout(() => { this.getMovieByTitle(currentTerm); }, 300);
}
render() {
return (
<div>
<MovieTopBar />
<SearchBar onSearchTermChange={this.movieSearch}/>
<MovieList movies={ this.state.movies }/>
<MovieFooter />
</div>
)
}
}
if better call getAllMoviesfunction again when termis empty, because you will show the real current movies instead a cache movies version from first call.
I hope it can help you.
Related
I'm trying to implement a follow and unfollow button in my app.
I've been trying to update it in the state, but when I click the follow button it changes all the user's buttons (not only the one clicked).
Now I'm a bit confused on how to show unfollow button if the user is already following the other user and make it work when clicked.
Here is my code:
class Followers extends React.Component {
constructor(props) {
super(props)
this.state = {
users: [],
follower: [],
following: [],
button: "Follow"
}
}
componentDidMount = () => {
this.getUsers()
}
getUsers = () => {
axios(`http://localhost:7001/api/users`)
.then(response => {
this.setState({ users: response.data})
console.log(response.data)
})
.catch(error => {
this.setState({ error: true })
})
}
followUser = (e) => {
e.preventDefault();
const userId = this.props.user[0].id
const followedId = e.target.value
axios.post(`http://localhost:7001/api/users/${userId}/follow/${followedId}`, {
userId,
followedId,
createdAt: new Date().toISOString().slice(0, 10),
updatedAt: new Date().toISOString().slice(0, 10)
})
.then(response => {
console.log(response.data)
this.setState(state => ({
button: "Unfollow",
loggedIn: !state.loggedIn
}))
})
.catch(error => {
console.log(error)
})
}
unfollowUser = (e) => {
e.preventDefault();
const userId = this.props.user[0].id
const followedId = e.target.value
axios.delete(`http://localhost:7001/api/users/${userId}/unfollow/${followedId}`)
.then(response => {
console.log(response)
this.setState({ button: "Follow" })
})
.catch(error => {
this.setState({ error: true })
})
}
render() {
const { users, button } = this.state
const userId = this.props.user[0].id
return (
<div>
<h2>Users in Unax</h2>
<ul>
{users.map((user, index) => {
if(user.id !== userId) {
return (
<Card className="users" key= {index}>
<CardBody>
<CardTitle>{user.user_name}</CardTitle>
<Button id="btn" value={user.id} onClick={this.followUser}>Follow</Button>
<Button id="btn" value={user.id} onClick={this.unfollowUser}>Unfollow</Button>
</CardBody>
</Card>
)}
})}
</ul>
</div>
)
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
My last try was to add two buttons and make a conditional, but I cannot think a way to compare if the relationship already exists, should this be done in the backend?
It looks as though all of your follow/unfollow buttons are linked to the same single piece of state, meaning that when its true they are all true and when false they are all false.
One way to achieve this is to have a 'followed' property in the user object. Then you can alter the button based on whether that user is already being followed or not.
You can then update the database and the local state to give the user the most responsive experience.
For example your user object could look something like:
{id: 1, name: Bob, followed: false, image: ....}
This would allow you to understand what state your button should be in.
In depth description of managing a friendship database
I'm just getting started with React. As a simple exercise, I wanted to create some components for viewing data retrieved from the JsonMonk API. The API contains 83 user records and serves them in pages of 10.
I am trying to develop a component for viewing a list of users one page at a time which I called UserList. The code for it is below:
class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
pageNumber: 1,
users: [],
};
this.onPageNext = this.onPageNext.bind(this);
}
componentDidMount() {
this.fetchUsers(this.state.pageNumber)
.then((users) => this.setState({users: users}));
}
async fetchUsers(pageNumber) {
const response = await fetch(`https://jsonmonk.com/api/v1/users?page=${pageNumber}`);
const jsonResponse = await response.json();
return jsonResponse.data.records;
}
onPageNext() {
// ...
}
render() {
const postElements = this.state.users.map(
(props) => <User key={props._id} {...props} />);
return (
<div>
{postElements}
<div>
<button onClick={this.onPageNext}>Next</button>
</div>
</div>
);
}
}
The problem I am having pertains to the onPageNext method of my component. When the user clicks the "Next" button, I want to make a fetch for the next page of data and update the list.
My first attempt used an asynchronous arrow function passed to setState like so:
onPageNext() {
this.setState(async (state, props) => {
const nextPageNumber = state.pageNumber + 1;
const users = await this.fetchUsers(nextPageNumber);
return {pageNumber: nextPageNumber, users: users}
})
}
However, it does not seem React supports this behavior because the state is never updated.
Next, I tried to use promise .then syntax like so:
onPageNext() {
const nextPageNumber = this.state.pageNumber + 1;
this.fetchUsers(nextPageNumber)
.then((users) => this.setState({pageNumber: nextPageNumber, users: users}));
}
This works but the problem here is that I am accessing the class's state directly and not through setState's argument so I may receive an incorrect value. Say the user clicks the "Next" button three times quickly, they may not advance three pages.
I have essentially run into a chicken-or-the-egg type problem. I need to pass a callback to setState but I need to know the next page ID to fetch the data which requires calling setState. After studying the docs, I feel like the solution is moving the fetch logic out of the UsersList component, but I'm not entirely sure how to attack it.
As always, any help is appreciated.
You need to change onPageNext as below:
onPageNext() {
this.setState( prevState => {
return {pageNumber: prevState.pageNumber + 1}
}, () =>{
this.fetchUsers(this.state.pageNumber).then(users => this.setState({users: users}) )
});
}
Here is the Complete Code:
import React from "react";
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
pageNumber: 1,
users: [],
};
this.onPageNext = this.onPageNext.bind(this);
}
componentDidMount() {
this.fetchUsers(this.state.pageNumber)
.then((users) => {
console.log(users, 'users');
this.setState({users: users})
}
);
}
async fetchUsers(pageNumber) {
const response = await fetch(`https://jsonmonk.com/api/v1/users?page=${pageNumber}`);
const jsonResponse = await response.json();
return jsonResponse.data.records;
}
onPageNext() {
this.setState( prevState => {
return {pageNumber: prevState.pageNumber + 1}
}, () =>{
this.fetchUsers(this.state.pageNumber).then(users => this.setState({users: users}) )
});
}
render() {
const postElements = this.state.users.map(
(user) => <User key={user._id} {...user} />);
return (
<div>
{postElements}
<div>
<button onClick={this.onPageNext}>Next</button>
</div>
</div>
);
}
}
function User(props) {
return (
<div>
<div style={{padding: 5}}>Name: {props.first_name} {props.last_name}</div>
<div style={{padding: 5}}>Email: {props.email}</div>
<div style={{padding: 5}}>Phone: {props.mobile_no}</div>
<hr/>
</div>
);
}
Here is the Code Sandbox
I am using React js and want to render data that I got it from API using Fetch .
the problem is I can't display the fetch results because the Return scope of React excute befor the fetch method done!!
Please any help of how I can solve this ???
this is a part of the function that do the Fetch :
initFourSquare = () => {
return fetch(FourSquareAPI)
.then(data => {
return data.json()
})
.catch((error) => {
console.log(error.message)
})
};
this is the render part of react where I called the function (initFourSquare)
render() {
var info = []
this.initFourSquare().then(response => {
info = response.response.venues
console.log(info) //the result is appear here
})
setTimeout(function(){ console.log(info[0].name) }, 1000);//the result is appear here
return (
<div>
<h1>{info}</h1> // it display nothing !!!
</div>
);
}
}
Any API/asynchronous call shouldn't be made in render function instead, you should do that in componentDidMount function if it is to be done once which seems to be your case and set the response in state which you can use in render. Make sure that you either initialise the state correctly or provide a conditional check for existence before using state variable
class App extends React.Component {
state = {
info: []
}
componentDidMount() {
var info = []
this.initFourSquare().then(response => {
info = response.response.venues
this.setState({info})
})
}
initFourSquare = () => {
return fetch(FourSquareAPI)
.then(data => {
return data.json()
})
.catch((error) => {
console.log(error.message)
})
};
render() {
const { info } = this.state;
return (
<div>
<h1>{info}</h1>
</div>
);
}
}
So far I am calling an api in componentDidMount() and set it to select option.
also call another conditional api from user input.
But Problem is it is calling the api non stop.
**getRates(){
const base = this.handlePrint(this.state.value);
fetch(`https://exchangeratesapi.io/api/latest?base=${base}`)
.then(data => data.json())
.then(data =>{
this.setState({
rate: data.rates,
})
console.log(data.rates)
})
.catch(err => console.log(err))
}**
And my console screen shot:
console
I just need one time api call based on user input.
full code: https://codeshare.io/5MwXzq
I think there is a problem with the state but I am new in reactjs and could not understand how to solve that.
Anyone can help please.
This is happening not because of anything in componentDidMount()
Based on the code you shared on codeshare.io, you're calling getRates() function in your render() method. Also, you're setting the state using setState within the getRates method. This causes a re-render, calling render() again, and so you get the infinite loop of calls.
Remove the call to getRates() from your render method and it'll work.
EDIT:
Since there were small changes but strewn across your code to get it to work, I've modified your class and posting it here:
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
currencies: [],
value: "?",
base: "?",
input: "?",
rate: 0
};
this.getRates = this.getRates.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handlePrint = this.handlePrint.bind(this);
}
componentDidMount() {
fetch("https://exchangeratesapi.io/api/latest?symbols=USD,GBP,AUD,JPY")
.then(data => data.json())
.then(data => {
const currencies = [];
currencies.push(
data.base,
...Object.entries(data.rates).map(rates => rates[0])
);
this.setState({
currencies
});
})
.catch(err => console.log(err));
}
getRates() {
const base = this.handlePrint();
console.log(this.state); fetch(`https://exchangeratesapi.io/api/latest?base=${base}`)
.then(data => data.json())
.then(data => {
this.setState({
rate: data.rates
});
console.log(data.rates);
})
.catch(err => console.log(err));
}
//Dropdown
DropDown = function(list) {
return <option value={list}>{list}</option>;
};
handleChange(e) {
this.setState({ value: e.target.value });
}
handlePrint() {
console.log(this.state)
if (this.state.value) {
return this.state.value;
}
};
render() {
const { currencies } = this.state;
// const input = this.getRates();
return (
<div>
<span>SELECT your Base: </span>
<select autoFocus onChange={this.handleChange}>
<option inputcurrency={currencies} selected data-default>
SELECT BASE
</option>
{currencies.map(this.DropDown)}
</select>
<button onClick={this.getRates}>GET RAtes</button>
<p>selected base:{this.handlePrint()} </p>
</div>
);
}
}
The changes are:
1. Bound getRates() method in the constructor
2. Removed the call to getRates() in render start
3. Removed unnecessary items passed to handlePrint
4. Changed the button onClick to point to getRates
I'm making a Ajax request to a Json file that return some movies.
state = { movies: [] };
componentWillMount()
{
this.getMovies();
}
/*
Make an ajax call and put the results in the movies array
*/
getMovies()
{
axios.get('https://pastebin.com/raw/FF6Vec6B')
.then(response => this.setState({ movies: response.data }));
}
/*
Render every movie as a button
*/
renderMovies()
{
const { navigate } = this.props.navigation;
return this.state.movies.map(movie =>
<ListItem key={ movie.title }
title={ movie.title }
icon={{ name: 'home' }}
onPress={() =>
navigate('Details', { title: movie.title, release: movie.releaseYear })
}
/>
);
}
render() {
return(
<List>
{ this.renderMovies() }
</List>
);
}
The error I get is the following: this.state.map is not a function. This is because movies is still empty.
When I console.log response.data it returns all the rows from the JSON file. So the problem is most likely in this line:
.then(response => this.setState({ movies: response.data }));
Does someone know what's wrong?
You put initial state in the wrong place. Do this instead:
constructor(props) {
super(props);
this.state = { movies: [] };
}
From document:
In general, you should initialize state in the constructor, and then
call setState when you want to change it.
Update you ajax request as following:
/*
Make an ajax call and put the results in the movies array
*/
getMovies()
{
let self = this;
axios.get('https://pastebin.com/raw/FF6Vec6B')
.then(response => self.setState({ movies: response.data }));
}
Also, you can bind your function inside constructor as:
constructor(props){
super(props);
this.getMovies = this.getMovies.bind(this);
}