axios.interceptors in hoc withErrorHandler work for clicked method in App.js, but do not work for componentWillMount or componentDidMount in App.js. How can I fix it?
App.js
class App extends Component {
componentDidMount() {
axios.get('https://wrongaddress')
.then(response => {
console.log(response);
});
}
clicked() {
axios.get('https://wrongaddress')
.then(response => {
console.log(response);
});
}
render() {
return (
<button onClick={this.clicked}>btn</button>
);
}
}
export default withErrorHandler(App, axios);
hoc/withErrorHandler.js
const withErrorHandler = ( WrappedComponent, axios ) => {
return class extends Component {
componentDidMount() {
axios.interceptors.request.use(req => {
console.log(req);
return req;
});
}
render() {
return (
<WrappedComponent {...this.props} />
);
}
}
};
You add the interceptor in the hoc just after the first render. And you use axios in componentWillMount in the App. componentWillMount is before the first render.
I suggest to place the axios call to componentDidMount in the App. It is recommended to put all side effects like load data to componentDidMount anyway. Check the documentation here: https://reactjs.org/docs/react-component.html#componentdidmount
class App extends Component {
componentdidMount() {
axios.get('https://wrongaddress')
.then(response => {
console.log(response);
});
}
...
Also you can move the interceptor handling in the HOC to componentWillMount.
const withErrorHandler = ( WrappedComponent, axios ) => {
return class extends Component {
componentWillMount() {
axios.interceptors.request.use(req => {
console.log(req);
return req;
});
}
Related
I'd like to call getAlbums() method so I can use the data from the get request and display album data on the client side. I don't know where to call it though. I tried to call it in render() but it creates an infinite loop.
Albums.js
import React, { Component } from "react";
import { useParams } from "react-router-dom";
import axios from "axios";
import AlbumCard from "./AlbumCard";
export class Albums extends Component {
constructor(props) {
super(props);
this.state = { albums: [] };
this.getAlbums = this.getAlbums.bind(this);
}
async getAlbums() {
const {
match: { params },
} = this.props;
console.log(params.id);
try {
const res = await axios.get(
`http://localhost:4000/albums/${encodeURIComponent(params.id)}`,
{
params: {
id: params.id,
},
}
);
console.log(`Returned album data from the server: ${res}`);
this.setState({ albums: res.data });
} catch (err) {
console.log(err);
}
}
render() {
return (
<>
<div className="container" style={{ color: "white" }}>
hello
</div>
</>
);
}
}
export default Albums;
I wanna do something like this inside the div.
this.state.albums.map((album) => (<AlbumCard img={album.img}/>))
The reason you get an infinite loop is because you're calling setState in render. Here is what's happening behind the scenes:
1.getAlbums is called in the render method.
2.The function triggers setState.
3.setState causes re-render.
4.In the render method, getAlbums is called again.
Repeat 1-4 infinitely!
Here's is what you could do:
Create a button and bind getAlbums as a method to the onClick event handler.
2.Run getAlbums on ComponentDidMount like so:
componentDidMount() {
this.getAlbums();
}
componentDidMount() is the best place for making AJAX requests.
The componentDidMount() method will set state after the AJAX call fetches data. It will cause render() to be triggered when data is available.
Here is the working example with componentDidMount()
import React, { Component } from "react";
import { useParams } from "react-router-dom";
import axios from "axios";
import AlbumCard from "./AlbumCard";
export class Albums extends Component {
constructor(props) {
super(props)
this.state = { albums: [] }
}
componentDidMount() {
axios.get(
`http://localhost:4000/albums/${encodeURIComponent(this.props.id)}`,
{ params: { id: this.props.id } }
)
.then(response => {
console.log(`Returned album data from the server: ${res}`)
this.setState({ albums: response.data })
}
)
.catch(e => {
console.log("Connection failure: " + e)
}
)
}
render() {
return (
<div>
{/* Code for {this.state.albums.map(item => )} */}
{/* render() method will be called whenever state changes.*/}
{/* componentDidMount() will trigger render() when data is ready.*/}
</div>
)
}
}
export default Albums
More information:
https://blog.logrocket.com/patterns-for-data-fetching-in-react-981ced7e5c56/
use componentDidMount()
componentDidMount(){
getAlbums()
}
I have a react project that is using redux-thunk. I created an action that will hit an endpoint, then set store to data received. Currently, I am using .then but when I call the action in the componentdidmount, the data is not there. The component renders before the data is available. To fix this, I decided to turn my action into an async action and then await in my componentdidmount. The problem is, as soon as I put async in my action, I get this error....
Unhandled Rejection (Error): Actions must be plain objects. Use custom middleware for async actions.
Here is my code
Action
export const getCasesSuccess = async (data) => {
return {
type: GET_ALL_CASES,
data
}
};
export const getAllCases = () => {
return (dispatch) => {
axios.get('https://corona.lmao.ninja/all')
.then(res => {
const cases = res.data
dispatch(getCasesSuccess(cases))
})
.catch(error => {
throw(error)
})
}
}
Component where action is called
import React from "react";
import { connect } from "react-redux";
import { getAllCases } from "../../store/actions/index";
import AllCases from '../../components/allcases/allCases';
class DataContainer extends React.Component {
constructor(props) {
super(props);
this.state = { }
}
componentDidMount = async () => {
await this.props.getAllCases()
}
render() {
return (
<div>
<AllCases allCases={this.props.allCases} />
</div>
);
}
}
const mapStateToProps = (state) => (
{
allCases: state.allCases
}
)
const mapDispatchToProps = dispatch => {
return {
getAllCases: () => dispatch(getAllCases()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(DataContainer);
Remove the async from componentDidmount and use the async and await in getAllCases method
export const getAllCases = async () => {
return (dispatch) => {
await axios.get('https://corona.lmao.ninja/all')
.then(res => {
const cases = res.data
dispatch(getCasesSuccess(cases))
})
.catch(error => {
throw(error)
})
}
}
As the error messages says, Redux actions must be plain objects. Since you're using thunk middleware, you can dispatch functions. But you're returning a promise. Since the data loading is asynchronous, your component should check if the data exists and if it doesn't, render a loading indicator or something. In your reducer, you can set a default state for allCases to null which the DataContainer component will use when the component mounts.
export const getCasesSuccess = (data) => {
return {
type: GET_ALL_CASES,
data
}
};
import React from "react";
import { connect } from "react-redux";
import { getAllCases } from "../../store/actions/index";
import AllCases from '../../components/allcases/allCases';
class DataContainer extends React.Component {
componentDidMount() {
this.props.getAllCases()
}
render() {
const { allCases } = this.props
if (!allCases) {
return <div>Loading...</div>
}
return (
<div>
<AllCases allCases={this.props.allCases} />
</div>
);
}
}
const mapStateToProps = (state) => ({
allCases: state.allCases
})
const mapDispatchToProps = {
getAllCases,
}
export default connect(mapStateToProps, mapDispatchToProps)(DataContainer);
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 years ago.
In React ... I am trying to read the response return from API and get undefined, what is the problem?
Undefined occurs when calling the function retrieveItems() from the component.
**// item service class**
import axios_o from 'axios';
class ItemService {
retrieveItems() {
axios_o.get("https://jsonplaceholder.typicode.com/posts")
.then(response => {
return response;
}).catch();
}
}
**// component calling the item service**
import React from 'react'
import ItemService from "../Services/ItemService";
class Posts extends React.Component {
constructor(props) {
super(props);
}
componentDidMount = () => {
this.itemservice=new ItemService();
**console.log(this.itemservice.retrieveItems())**
}
render() {
return (
<h1>Posts List</h1>
);
}
}
export default Posts;
class ItemService {
retrieveItems() {
return axios_o.get("https://jsonplaceholder.typicode.com/posts")
.then(response => response)
.catch(error => error)
}
}
componentDidMount = () => {
this.itemservice=new ItemService();
this.itemservice.retrieveItems().then(res=>{
console.log(res);
}).catch(error=>{
console.log(error)
});
}
As I mentioned in the comment the method retrieveItems is not returning a value. To fix this return the axios call
retrieveItems() {
return axios_o.get("https://jsonplaceholder.typicode.com/posts")
.then(response => {
return response;
}).catch(
);
}
or rewrite it to async/await for better readability
async retrieveItems() {
try {
return await axios_o.get("https://jsonplaceholder.typicode.com/posts")
}catch(e) {
// do some error handling or move the try/catch to caller side
}
}
Now in your console log you should see not the real response of the API call but a Promise. To get the real response you also have to wait for the answer on caller side:
class Posts extends React.Component {
constructor(props) {
super(props);
}
componentDidMount = () => {
this.retrieveItems()
}
retrieveItems = async () => {
this.itemservice=new ItemService();
const response = await this.itemservice.retrieveItems()
console.log(response)
}
render() {
return (
<h1>Posts List</h1>
);
}
}
With this you should see the response in the console log.
The issue is the typical pitfall of wanting to return something from within a callback function to the outer function. That's can't work, because the outer function (retrieveItems) has already finished. You need to stay in the asynchronous pattern. The easiest is probably this:
import axios_o from 'axios';
class ItemService {
retrieveItems() {
return axios_o.get("https://jsonplaceholder.typicode.com/posts");
}
}
import React from 'react'
import ItemService from "../Services/ItemService";
class Posts extends React.Component {
componentDidMount = () => {
this.itemservice = new ItemService();
this.itemservice.retrieveItems().then((res) => {
console.log(res);
});
}
render() {
return (<h1>Posts List</h1>);
}
}
export default Posts;
I am trying to wrap my head around ReactJS and I am stumped with an issue where I want to be able to update the value of a local variable and return the updated value.
I've read about state and I've used that when working with React Components, however, this class is just defined as const and it doesn't extend React.Component.
Is there a different way I should be defining setting the variable?
Here is a simplified version of my code:
import React from 'react';
const WelcomeForm = ({welcome}) => {
var welcomeMsg = 'Test';
DynamicContentApi.loadDynamicContent('welcome_test').then((response) => {
// response.text has content
welcomeMsg = response.text;
}).catch(() => {
welcomeMsg = '';
});
return (
<p>{welcomeMsg}</p> // Returns 'Test'
);
};
export default WelcomeForm;
The easiest option here is to change your stateless component to a stateful component.
Stateless components are just JavaScript functions. They take in an
optional input, called prop.
Stateful components offer more features, and with more features comes more baggage. The primary reason to choose class components (stateful) over functional components (stateless) is that they can have state, that is what you want to update to re-render.
Here is what you can do:
class WelcomeForm extends React.Component {
state = {
welcomeMsg: ''
}
fetchFromApi() {
DynamicContentApi.loadDynamicContent("welcome_test")
.then(response => {
this.setState({welcomeMsg: response.text});
})
.catch((e) => console.log(e));
}
componentDidMount() {
fetchFromApi();
}
render() {
return (
<p>{welcomeMsg}</p>
);
}
};
If you want, for any reason, to keep your component stateless, you will have to put the loadDynamicContent() function on the Parent and pass the text to WelcomeForm as a prop. For example:
// Your WelcomeForm Component
const WelcomeForm = ({welcomeMsg}) => (
<p>{welcomeMsg}</p>
);
// Whatever it's Parent Component is
class Parent extends React.Component {
state = {
welcomeMsg: ''
}
fetchFromApi() {
DynamicContentApi.loadDynamicContent("welcome_test")
.then(response => {
// response.text has content
this.setState({welcomeMsg: response.text});
})
.catch((e) => console.log(e));
}
componentDidMount() {
fetchFromApi();
}
render() {
<WelcomeForm welcomeMsg={this.state.welcomeMsg} />
}
}
As suggested in the comments, you can pass the DynamicContentApi logic to outside:
import ReactDOM from 'react-dom'
DynamicContentApi.loadDynamicContent('welcome_test').then((response) => {
ReactDOM.render(<WelcomeForm data={response.text} />, document.getElementById('where you wanna render this'));
}).catch(() => {
console.log('error while fetching...');
});
And where you have your component:
import React from 'react';
export default class WelcomeForm extends React.Component {
render() {
return (
<p>{this.props.data}</p>
);
}
}
I have a component, which has to download a JSON file and then iterate over it and display each element from the JSON on the screen.
I'm kinda new with React, used to be ng dev. In Angular, I used to do it with lifecycle hooks, e.g. ngOnInit/ngAfterViewInit (get some JSON file and then lunch the iteration func). How can I achieve it in React? Is it possible to reach it with lifecycle hooks, like ComponentWillMount or ComponentDidMount.
My code (it's surely wrong):
export default class ExampleClass extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentWillMount(){
getData();
}
render() {
return (
<ul>
{this.state.data.map((v, i) => <li key={i}>{v}</li>)}
</ul>
)
};
}
const getData = () => {
axios.get(//someURL//)
.then(function (response) {
this.setState({data: response.data});
})
.catch(function (error) {
console.log(error);
})
};
How to force React to get the JSON before rendering the component?
Thank you so much.
Making an AJAX request in ComponentWillMount works. https://facebook.github.io/react/docs/react-component.html#componentwillmount
You could also just work that logic into your constructor depending on your exact needs.
https://facebook.github.io/react/docs/react-component.html#constructor
export default class ExampleClass extends React.Component {
constructor(props) {
super(props)
this.state = {
data: [],
}
axios.get(/*someURL*/)
.then(function (response) {
this.setState({data: response.data});
})
.catch(function (error) {
console.log(error);
})
}
}
You can do a simple if statement in your render function.
render () {
if (Boolean(this.state.data.length)) {
return <ul>{this.state.data.map((v, i) => <li key={i}>{v}</li>)}</ul>
}
return null
}
You can also use a higher order component to do the same thing.
const renderIfData = WrappedComponent => class RenderIfData extends Component {
state = {
data: []
}
componentWillMount() {
fetchData()
}
render() {
if (Boolean(this.state.data.length)) {
return <WrappedComponent {...this.state} />
}
return null
}
}
Then you can wrap the presentational layer with the HOC.
renderIfData(ExampleClass)
Not sure what version of React you are using but you may need to use <noscript> instead of null.
This is essentially preventing your component from rendering until it has all the data.