Execute Two method at the same time - javascript

Goal:
When you are pressing the button 'GO HOME!' you should execute the code "fetch(https://jsonplaceholder.typicode.com/users)" and then being pushed to '/home'
Problem:
How do I apply that the button can execute the fetch and push at the same time?
Info:
*I'm new in react JS
Stackblitz:
https://stackblitz.com/edit/react-router-history-push-yux12z?file=components/User.js
import React from "react";
export class User extends React.Component {
fetchUsers() {
fetch(`https://jsonplaceholder.typicode.com/users`)
.then(response => response.json())
.then(data =>
this.setState({
users: data,
isLoading: false
})
)
.catch(error => this.setState({ error, isLoading: false }));
}
render() {
return (
<div>
<h3>THE USER PAGE</h3>
<p>USER ID: {this.props.match.params.id}</p>
<button
onClick={() => {
this.props.history.push("/home");
}}
>
GO HOME!
</button>
</div>
);
}
}

The setSate function takes another optional input, a function to be executed after the state was updated.
import React from "react";
export class User extends React.Component {
fetchUsers() {
fetch(`https://jsonplaceholder.typicode.com/users`)
.then(response => response.json())
.then(data =>
this.setState({
users: data,
isLoading: false
}, () => {
// this will be executed after the state was updated
// the data was fetched and the state was update
this.props.history.push("/home")
})
)
.catch(error => this.setState({ error, isLoading: false }, ()=>{
// the state was updated, you can redirect to home or an error page
this.props.history.push("/home")
}));
}
render() {
return (
<div>
<h3>THE USER PAGE</h3>
<p>USER ID: {this.props.match.params.id}</p>
<button
onClick={this.fetchUsers.bind(this)}
>
GO HOME!
</button>
</div>
);
}
}

Related

How to correctly use componentDidMount and componentDidUpdate in React.js?

I have a problem. I want to search for an index based on a url. Everything is sent to the components as it should, but there is an error after loading:
Cannot read property 'indexOf' of undefined
Data sent from JSON is sure to be transmitted and received correctly and is correctly assigned. The problem is most likely caused by badly applied 'componentDidMount' and 'componentDidUpdate'. How should it look correctly?
The data sent based on the URL of the page is 'this.props.brand'
Code:
class CarPage extends Component {
state = {
isLoading: true,
carData: [],
id: null
}
findMyIndex = () => {
this.setState({
id: this.carData.indexOf(this.props.brand),
})
}
componentDidUpdate() {
this.findMyIndex()
}
componentDidMount() {
fetch("/data.json")
.then(response => response.json())
.then(data => {
this.setState({
carData: data,
isLoading: false,
})
})
}
render() {
return (
<>
{!this.state.isLoading && (
<p>{this.state.carData[this.state.id].model}</p>
)}
</>
);
}
}
export default CarPage;
You don't need componentDidUpdate lifecycle method at all. You can do it like this:
class CarPage extends Component {
state = {
isLoading: true,
carData: [],
id: null
}
findMyIndex = () => {
return this.state.carData.map(el => el.brand).indexOf(this.props.brand);
}
componentDidMount() {
fetch("/data.json")
.then(response => response.json())
.then(data => {
this.setState({
carData: data,
isLoading: false,
})
})
}
render() {
return (
<>
{!this.state.isLoading && (
<p>{this.state.carData[this.findMyIndex(this.props.brand)].model}</p>
)}
</>
);
}
}
export default CarPage;
It seems that findMyIndex returns -1 and this.state.carData[this.state.id] is equal to undefined. Check if CarData indeed has a this.props.brand entry.

Implement follow and unfollow button in React.js

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

Fetching data from an api and manipulating state

I'm trying to make a quote generator, and I'm having a weird problem while passing the data provided from the api to my state, can someone help me with that? (I don't know why, but when I click the button to generate a quote the browser throws the error: '_this2.setState is not a function') Here are the two components used on the App:
import React from "react";
import "./styles.css";
import QuoteComponent from "./QuoteComponent";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
qouteData: ""
};
}
fetchData() {
fetch("https://api.quotable.io/quotes")
.then(res => {
return res.json();
})
.then(data => {
this.setState({
quoteData: data
});
});
}
componentDidMount() {
this.fetchData();
}
render() {
const { qouteData } = this.state;
return (
<div className="App">
<QuoteComponent
quote={this.state.qouteData.content}
author={qouteData.author}
handleClick={this.fetchData}
/>
</div>
);
}
}
export default App;
Quote Component:
import React from "react";
import "./styles.css";
function QuoteComponent(props) {
return (
<div className="card">
<div className="container">
<div className="flex">
<div id="quote">
<h2>{props.quote}</h2>
</div>
<div id="quote-a">
<h4>{props.author}</h4>
</div>
<div id="button">
<button onClick={props.handleClick}>New Quote</button>
</div>
</div>
</div>
</div>
);
}
export default QuoteComponent;
fetchData() {
fetch("https://api.quotable.io/quotes")
.then(res => {
return res.json();
})
.then(data => {
this.setState({
quoteData: data
});
});
}
in this code this is bound to its context which is fetchData. And fetchData has no member setState.
In order to avoid this being bound to the function fetchData you can use an arrow function:
fetchData = () => {
fetch("https://api.quotable.io/quotes")
.then(res => {
return res.json();
})
.then(data => {
this.setState({
quoteData: data
});
});
}
Here is more information about binding this in react

How to make multiple axios.get() requests with componentDidMount() and assign a response value of the 1st to the 2nd?

I am trying to build a web application with Wordpress REST API.
I am making an initial GET request to an endpoint and parsing through the res.data to get some values. But, one of the values featured_media is a parameter for the 2nd GET request I am trying to make. I am finding it difficult to get this value out of that state onto the second GET request.
Here are the states.
state = {
graduatepost: {},
category: '',
featured_media: '',
imgURL: '',
isLoaded: false
}
Here is componentDidMount()
componentDidMount() {
const { featured_media } = this.props;
axios.get(`http://localhost:8000/wp-json/wp/v2/blog/${this.props.match.params.id}`)
.then(res => this.setState({
graduatepost: res.data,
category: res.data.categories[0],
featured_media: res.data.featured_media,
isLoaded: true
}))
.catch(err => console.log(err));
const getImageURL = axios.get(`http://localhost:8000/wp-json/wp/v2/media/${featured_media}`);
Promise.all([getImageURL]).then(res => {
this.setState({
imgURL: res[0].data.media_details.sizes.full.source_url,
isLoaded: true
});
});
}
1st GET request: http://localhost:8000/wp-json/wp/v2/blog/${this.props.match.params.id}
2nd GET request: http://localhost:8000/wp-json/wp/v2/media/${featured_media}
As you can see the 2nd request requires the value featured_media which is in the response of the 1st GET request.
I am rendering the component like this.
render() {
const { graduatepost, category, isLoaded, featured_media, imgURL } = this.state;
if(isLoaded) {
return (
<Styles>
<Fragment>
<Link to='/graduate-posts'>Go Back</Link> // Ignore this
<hr />
<h1>{graduatepost.title.rendered}</h1>
<div dangerouslySetInnerHTML={{__html: graduatepost.content.rendered}}></div>
<h4>Category: {category}</h4>
<h4>featured_media: {featured_media}</h4>
<h4>imgURL: {imgURL}</h4>
</Fragment>
</Styles>
)
}
return <h3>Loading...</h3> // Ignore this
}
When I do the render the component. There is a 404 console error for the 2nd GET request, which states.
GET http://localhost:8000/wp-json/wp/v2/media/undefined 404 (Not Found)
Uncaught (in promise) Error: Request failed with status code 404
at createError (createError.js:16)
at settle (settle.js:17)
at XMLHttpRequest.handleLoad (xhr.js:61)
I am assuming this is because featured_media is empty/undefined but I cannot figure out how to extract that value from the 1st GET request, response.
This may seem like an obvious one but I'm relatively new to working with React.js and APIs together. Your help would be greatly appreciated.
Thank you.
The best way to access the setted data immediately is to use callback.
this.setState accept the callback as its second argument (setState(updater, [callback])), so we should make our second request in our callback statement.
Your code should be something like this:
axios
.get(`http://localhost:8000/wp-json/wp/v2/blog/${this.props.match.params.id}`)
.then((res) =>
this.setState(
{
graduatepost: res.data,
category: res.data.categories[0],
featured_media: res.data.featured_media,
isLoaded: true,
},
() =>
axios
.get(
`http://localhost:8000/wp-json/wp/v2/media/${this.state.featured_media}`
)
.then((res) => {
this.setState({
imgURL: res[0].data.media_details.sizes.full.source_url,
isLoaded: true,
})
})
)
)
.catch((err) => console.log(err))
Have you tried Async function? https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
async componentDidMount() {
....
await axios.get ...
....
}
Maybe request it in the response of the 1st axios.get. The reason it isn't working is because this.setState is an async function in React so it's undefined when you access it immediately below.
Try something like:
axios.get(`http://localhost:8000/wp-json/wp/v2/blog/${this.props.match.params.id}`)
.then((res) => {
const state = {
graduatepost: res.data,
category: res.data.categories[0],
featured_media: res.data.featured_media,
isLoaded: true
}
this.setState(state)
return axios.get(`http://localhost:8000/wp-json/wp/v2/media/${state.featured_media}`);
})
.then((res) => {
// do something with res
})
.catch((err) => {
// handle err
});
I have prepared one example where it shows all users and if you click to see posts button, it will show all the posts for that user.
App.js
class App extends React.Component {
render() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/posts">Posts</Link>
</li>
</ul>
<hr/>
<Switch>
<Route exact path="/">
<UserList/>
</Route>
<Route path="/posts">
<PostListPageByUser/>
</Route>
</Switch>
</div>
</Router>
);
}
}
export default App;
UserList Component
import React from 'react';
import axios from 'axios';
import PostListPageByUser from "./PostListPageByUser";
import {withRouter} from "react-router-dom";
class UserList extends React.Component {
state = {
users: [],
showPostList: false,
user: {}
};
componentDidMount() {
axios.get(`https://jsonplaceholder.typicode.com/users`)
.then(res => {
const users = res.data;
console.log(users);
this.setState({users});
})
}
handleClick = (user) => {
console.log(user);
this.setState({showPostList: true, user: user});
this.props.history.push({
pathname: '/posts',
user: user
});
};
render() {
return (
<div>
<ul>
{this.state.users ? this.state.users.map(user => <div key={user.id}>
<span style={{minWidth: 400}}>{user.name} </span>
<button onClick={() => {
this.handleClick(user)
}}>See Posts
</button>
</div>) : null}
</ul>
{this.state.showPostList ? <PostListPageByUser user={this.state.user}/> : null}
</div>
)
}
}
export default withRouter(UserList);
PostListByUser Component
import React from "react";
import axios from 'axios';
import {withRouter} from "react-router-dom";
class PostListPageByUser extends React.Component {
signal = axios.CancelToken.source();
state = {
posts: [],
};
componentDidMount() {
if(!this.props.location.user){
alert('Please click see posts button');
this.props.history.push('/');
return;
}
axios.get(`https://jsonplaceholder.typicode.com/posts?userId=${this.props.location.user.id}`, {
cancelToken: this.signal.token,
})
.then(res => {
this.setState({posts: res.data});
console.log(res.data, 'Posts');
}).catch(err => {
console.log(err);
});
}
render() {
return (
<div>
<ul>
{
this.state.posts ? this.state.posts.map(post => <li key={post.id}>{post.title}</li>) : null
}
</ul>
</div>
)
}
}
export default withRouter(PostListPageByUser);

How do I manipulate data that is located in state and display to page?

I'm making three separate axios calls that each set the state with some data. Where do I do my data manipulation with the state data not to change the state but to display something else where?
For example out of the transactionItems state, I want to get all transactions for the current date. All transaction items have the date set automatically when its added to the database.
I'm having issues parsing the data because my setstate seems to update 3 times with all the axios calls.
There are other data manipulations I would like to be able to do as well but I feel like I'll hit another roadblock.
import React, { Component } from "react";
import axios from "axios";
import moment from "moment";
import TransactionSummary from "./TransactionSummary";
import BudgetSummary from "./BudgetSummary";
import DebtSummary from "./DebtSummary";
class DashboardTable extends Component {
constructor(props) {
super(props);
this.state = {
transactionItems: [],
budgetItems: [],
debtItems: [],
spentToday: ""
};
}
componentDidMount() {
this.getTransactionData();
this.getBudgetData();
this.getDebtData();
}
getTransactionData = () => {
axios.defaults.headers.common["Authorization"] = localStorage.getItem(
"jwtToken"
);
axios
.get("/api/transactions")
.then(res =>
this.setState({
transactionItems: res.data
})
)
.catch(err => console.log(err));
};
getBudgetData = () => {
axios.defaults.headers.common["Authorization"] = localStorage.getItem(
"jwtToken"
);
axios
.get("/api/budgets")
.then(res =>
this.setState({
budgetItems: res.data
})
)
.catch(err => console.log(err));
};
getDebtData = () => {
axios.defaults.headers.common["Authorization"] = localStorage.getItem(
"jwtToken"
);
axios
.get("/api/debts")
.then(res =>
this.setState({
debtItems: res.data
})
)
.catch(err => console.log(err));
};
render() {
return (
<div>
<div className="content">
<TransactionSummary transactionItems={this.state.transactionItems} />
<BudgetSummary budgetItems={this.state.budgetItems} />
<DebtSummary debtItems={this.state.debtItems} />
</div>
</div>
);
}
}
export default DashboardTable;
Here's DebtSummary component
import React from "react";
const DebtSummary = props => {
let sumOfDebtItems = props.debtItems.reduce((a, c) => {
return a + c["balance"];
}, 0);
return (
<div>
<p>Debt Summary</p>
{sumOfDebtItems}
</div>
);
};
export default DebtSummary;
Like Hemadri said, the easiest way to do this is to move the 3 axios calls into their respective component
You can also move the data manipulation into a separate method and call it in the render method. You can write as many of these as you need, they can all read from the same state variable
DebtSummary example:
import React from "react";
class DebtSummary extends React.Component {
constructor(props) {
super(props);
this.state = {
debtItems: []
}
}
getDebtData = () => {
axios.defaults.headers.common["Authorization"] = localStorage.getItem(
"jwtToken"
);
axios
.get("/api/debts")
.then(res =>
this.setState({
debtItems: res.data
})
)
.catch(err => console.log(err));
};
// Do some data manipulation, in the case computing the debt sum
sumOfDebtItems = () => {
return this.state.debtItems.reduce((a, c) => {
return a + c["balance"];
}, 0);
}
// Load the debt data once the component has mounted
componentDidMount() {
this.getDebtData()
}
render() {
return (
<div>
<p>Debt Summary</p>
{this.sumOfDebtItems()}
</div>
);
}
};
export default DebtSummary;

Categories

Resources