Working with kriasoft/react-starter-kit to create a web application.
It fetches objects from my api server, and shows them on a page.
The app should show a loading icon while fetching the data.
My code doesn't re-render a component after fetching objects.
It continues to show 'Loading...', even though I want to see 'Data loaded!'.
import React from 'react';
import fetch from 'isomorphic-fetch'
class Search extends React.Component {
constructor(...args) {
super(...args);
this.getObjectsFromApiAsync = this.getObjectsFromApiAsync.bind(this);
this.state = {
isLoading: true,
};
}
async getObjectsFromApiAsync() {
try {
const response = await fetch('http://localhost:3030/api');
const content = await response.json();
// console.log(content)
this.setState({isLoading: false});
} catch(e) {
console.error(e);
}
}
componentWillMount() {
this.getObjectsFromApiAsync();
};
render() {
if (this.state.isLoading) {
return (
<p>Loading...</p>
);
}
return (
<p>Data loaded!!</p>
);
}
}
Solved the problem by adding res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000'); to my api server.
The problem was not in the React.
Here is the solution resource:
https://stackoverflow.com/a/18311469/6209648
Related
I am learning react and I would still consider myself to be a beginner. My goal is to click a button on my child component so that I could re render the parent component. This is the code I have.
Parent Component
import React, { Component } from 'react';
import Activity from './Components/Activity'
class App extends Component {
state = {
activity: ''
}
handleClick = () => {
// I have read that forceUpdate is discouraged but this is just an example
this.forceUpdate()
}
async componentDidMount() {
const url = 'http://www.boredapi.com/api/activity/'
const response = await fetch(url);
const data = await response.json();
this.setState({
activity: data.activity
})
}
render() {
return (
<div>
<Activity act={this.state.activity} click={this.handleClick}/>
</div>
);
}
}
export default App;
Child Component
import React, { Component } from 'react';
class Activity extends Component {
render() {
return (
<div>
<h1>Bored? Here is something to do</h1>
<p>{this.props.act}</p>
<button onClick={this.props.click}>Something Else</button>
</div>
);
}
}
export default Activity;
As you can see I am trying to click a button so that I could get another fetch and a different activity renders on my child component. I am trying to keep my child component stateless but if keeping it stateless doesn't make sense or is just plain wrong I would love to know.
You can try to move fetching function outside componentDidMount
for the example:
handleClick = () => {
this.fetchdata();
}
async fetchdata(){
const url = 'http://www.boredapi.com/api/activity/'
const response = await fetch(url);
const data = await response.json();
this.setState({
activity: data.activity
})
}
componentDidMount() {
this.fetchdata();
}
You can make a class method for fetching the new activity,
Call it after the app first mounted with componentDidMount() and again when you call it from the child component Activity.
You should mentioned in the your question that the response body is different in each request you make.
import React, { Component } from 'react';
import Activity from './Activity'
class App extends Component {
state = {
activity: ''
}
handleClick = () => {
this.getActivity()
}
componentDidMount() {
this.getActivity();
}
async getActivity() {
const url = 'https://www.boredapi.com/api/activity/'
const response = await fetch(url);
const data = await response.json();
this.setState({
activity: data.activity
})
}
render() {
console.log(this.state);
return (
<div>
<Activity act={this.state.activity} click={this.handleClick}/>
</div>
);
}
}
export default App;
Here is also a sandbox:
https://codesandbox.io/s/dreamy-noether-q98rf?fontsize=14&hidenavigation=1&theme=dark
I am already using an AbortController signal to cancel fetching on unmounting, but is there an elegant way to prevent memory leaks when trying to setState on an unmounted component, without setting this.mounted = true on componentDidMount, and false on componentWillUnmount, combining with this.mounted && ... conditionals?
In the following example It might only be 2 occasions which isn't a big deal, but just wondering if there is a better way/pattern
import React from 'react';
import {fetchPopularRepos} from '../utils/api';
export default class Popular extends React.Component {
constructor(props) {
super(props);
const AbortController = window.AbortController;
this.controller = new AbortController();
this.signal = this.controller.signal;
this.state = {
selectedLanguage: 'All',
repos: new Map(),
error: null
};
this.updateLanguage = this.updateLanguage.bind(this);
}
componentDidMount() { this.updateLanguage(this.state.selectedLanguage); }
componentWillUnmount() { this.controller.abort(); }
async updateLanguage(selectedLanguage) {
this.setState({selectedLanguage, error: null});
if (this.state.repos.has(selectedLanguage))
return;
try {
const data = await fetchPopularRepos(selectedLanguage, this.signal);
this.setState(({repos})=> ({
repos: repos.set(selectedLanguage, data)
}));
} catch (err) {
console.warn('Error fetching repos ...', err);
this.setState({error: err.message});
}
}
// rest of code ...
}
I'm trying to implement generic error page for any unhandled exceptions in React application. However, it seems like the error boundary doesn't work with exceptions thrown by promises such as API operations. I know I could catch the exception at component level and re-throw it at the render method. But this is boilerplate I would like to avoid. How to use error boundary with promises?
I'm using latest React with hooks and react-router for navigation.
You can do by creating a HOC which will take two parameters First one is the component and second the promise. and it will return the response and loading in the props Please find the code below for your reference.
HOC
import React, { Component } from "react";
function withErrorBoundary(WrappedComponent, Api) {
return class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false, response: null, loading: false };
}
async componentDidMount() {
try {
this.setState({
loading: true
});
const response = await Api;
this.setState({
loading: false,
response
});
console.log("response", response, Api);
} catch (error) {
// throw error here
this.setState({
loading: false
});
}
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return (
<WrappedComponent
response={this.state.response}
loading={this.state.loading}
{...this.props}
/>
);
}
};
}
export default withErrorBoundary;
How to use HOC
import ReactDOM from "react-dom";
import React, { Component } from "react";
import withErrorBoundary from "./Error";
class Todo extends Component {
render() {
console.log("this.props.response", this.props.response);
console.log("this.props.loading", this.props.loading);
return <div>hello</div>;
}
}
const Comp = withErrorBoundary(
Todo,
fetch("https://jsonplaceholder.typicode.com/todos/1").then(response =>
response.json()
)
);
ReactDOM.render(<Comp />, document.getElementById("root"));
Please check the codesandbox for your refrence
I used react.js to connect the api.
And i want to show the identifier on the page.
Here is response to console.log(this.state.weathers.cwbopendata)
After i console.log(this.state.weathers.cwbopendata.identifier)
,I got the error
What should i do to show the identifier on the page?
Here is the code:
import React,{Component} from 'react';
class App extends Component {
constructor(){
super();
this.state = {
weathers: {},
};
}
componentDidMount(){
fetch('https://opendata.cwb.gov.tw/fileapi/v1/opendataapi/F-C0032-001?Authorization=CWB-BB78764B-9687-4C1C-B180-66CB616129E5&format=JSON')
.then(response=> response.json())
.then( JSON=> this.setState({weathers:JSON}))
}
render(){
return (
<div className="App">
{console.log(this.state.weathers.cwbopendata.identifier)}
</div>
);
}
}
export default App;
This is a classic problem many newcomers face. You need to add a state to let your component know that data fetching is in progress, is completed or there is an error. So the component can show real data when it successfully fetched it, until then something you can display to the UI to let users know that the App is fetching data... I'd write it something like:
import React, { Component } from "react";
class App extends Component {
constructor() {
super();
this.state = {
weathers: {},
isFetching: true
};
}
componentDidMount() {
fetch(
"https://opendata.cwb.gov.tw/fileapi/v1/opendataapi/F-C0032-001?Authorization=CWB-BB78764B-9687-4C1C-B180-66CB616129E5&format=JSON"
)
.then(response => response.json())
.then(json => this.setState({ weathers: json, isFetching: false }));
}
render() {
const { isFetching, weathers } = this.state;
return (
<div className="App">
{isFetching ? "Loading.." : weathers.cwbopendata.identifier}
</div>
);
}
}
export default App;
In your case you tried to render the data at first mount, and at this point of time weathers is just holding an empty object {}. Thus weathers.cwbopendata returns undefined, and undefined.identifier throws the error as you see in the browser console.
Initially render method is being called before componentDidMount.
By the time it was called, data hadn't been fetched yet.
So you should properly handle situation when you have empty state.
import React,{Component} from 'react';
class App extends Component {
constructor(){
super();
this.state = {
weathers: {},
};
}
componentDidMount(){
fetch('https://opendata.cwb.gov.tw/fileapi/v1/opendataapi/F-C0032-001?Authorization=CWB-BB78764B-9687-4C1C-B180-66CB616129E5&format=JSON')
.then(response=> response.json())
.then( JSON=> this.setState({weathers:JSON}))
}
render(){
return (
<div className="App">
{console.log(this.state.weathers.cwbopendata && this.state.weathers.cwbopendata.identifier)}
</div>
);
}
}
export default App;
I have a lower order page component that needs to fetch data in the getInitialProps(). It successfully fetches the data, but it does return as prop in the component.
Below is the code I'm working on
import React, { Component } from 'react';
import DefaultPage from '../hocs/DefaultPage';
import PageLayout from '../components/PageLayout';
import { getJwtFromLocalCookie, getJwtFromServerCookie } from '../utils/auth';
import { getUser } from '../services/users';
class Profile extends Component {
static async getInitialProps(ctx) {
let user;
const jwt = process.browser ? getJwtFromLocalCookie() : getJwtFromServerCookie(ctx.req);
try {
const {data} = await getUser(ctx.query.id, jwt);
user = data;
}
catch (err) {
if(err.code === 404)
ctx.res.statusCode = 404;
}
console.log(user);
return { user };
}
constructor(props) {
super(props);
this.state = { user: null };
}
render() {
console.log(this.props);
return (
<PageLayout
active=""
loggedUser={this.props.loggedUser}
>
</PageLayout>
);
}
}
export default DefaultPage(Profile);
The console.log() in the getInitialProps() does display the correct data, but the console.log() in render() doesn't have the user prop.
Ok, I found the solution, turns out in the getInitialProps() of the higher order component the getInitialProps() of the lower order component was returning a promise and needed to be handled
So, below is the before code of my hoc getInitialProps
static getInitialProps (ctx) {
const loggedUser = process.browser ? getUserFromLocalCookie() : getUserFromServerCookie(ctx.req)
const pageProps = Page.getInitialProps && Page.getInitialProps(ctx);
return {
...pageProps,
loggedUser,
currentUrl: ctx.pathname,
isAuthenticated: !!loggedUser
}
}
And the following is corrected code
static async getInitialProps (ctx) {
const loggedUser = process.browser ? getUserFromLocalCookie() : getUserFromServerCookie(ctx.req)
const pageProps = await (Page.getInitialProps && Page.getInitialProps(ctx));
return {
...pageProps,
loggedUser,
currentUrl: ctx.pathname,
isAuthenticated: !!loggedUser
}
}