Josh
Josh

Reputation: 1235

React: I have a component that renders user data and I want to display an error message underneath the section if there's an error rendering the data

I have a section component that fetches user data and then displays that data under 3 separate headers. I want to add a separate render if the data fetch fails that would display a single error message underneath the headers and I'm not quite sure what best practices would be for doing this. I have my component and the service to fetch the data listed below, any suggestions?

User Info Component
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import {
  Col,
  Row,
  Input,
  Checkbox
} from 'antd'


const CustomerDetails = ({ customer }) =>
  !!Object.keys(customer).length && (
    <Container>
      <h2>
        {/* Name */}
        Test Test
      </h2>
      <Row>
        <Col span={8}>
          <Ul>
            <h3><strong>Primary Contact</strong></h3>
            <li>Jane Doe</li>
            <li>212-333-3333</li>
          </Ul>
        </Col>
        <Col span={8}>
          <Ul>
            <h3><strong>Service Address</strong></h3>
            <li>1234 Stone Ave N</li>
            <li>STE FR6</li>
            <li>Seattle, WA 12345</li>
          </Ul>
        </Col>
        <Col span={8}>
          <Ul>
            <h3><strong>Billing Address</strong></h3>
            <li>1234 Stone Ave N</li>
            <li>STE FR6</li>
            <li>Seattle, WA 12345</li>
          </Ul>
        </Col>
      </Row>
      <br />
      <Row>
        <Col span={10}>
          <h4>PRIMARY CONTACT EMAIL</h4>
        </Col>
      </Row>
      <Row>
        <Col span={8}>
          <StyledInput />
        </Col>
        <Col span={12}>
          <StyledCheckbox /> EMAIL OPT OUT
        </Col>
      </Row>
      <br />
      <Row>
        <Col>
          <StyledCheckbox /> TAX EXEMPT
        </Col>
      </Row>
      <br />
      <Row>
        <Col>
          <h4>GO TO BUNDLE BUILDER</h4>
        </Col>
      </Row>
    </Container>
  )

CustomerDetails.propTypes = {
  customer: PropTypes.object
}

CustomerDetails.defaultProps = {
  customer: {}
}

const Container = styled.div`
  margin: 15px 5px;
`
const StyledCheckbox = styled(Checkbox)`

`
const StyledInput = styled(Input)`
  max-width: 75%;
`
const Ul = styled.ul`
  list-style-type: none;

  li {
    font-size: 1rem;
  }
`

export default CustomerDetails
API service to fetch user data
import Axios from 'axios'
import { logError } from './logging'

export async function getCustomer(customer = {}) {
  try {
    const { customerId } = customer
    console.info('Customer ID:', customerId)

    const { data } = await Axios.get('https://getUserInfoAPI.com')

    return new Promise(res => {
      setTimeout(() => res(data), 3000)
    })
  } catch (error) {
    logError(error)
    throw error
  }
}

Upvotes: 0

Views: 411

Answers (1)

Dude
Dude

Reputation: 931

How/Where are you making the API call and passing it to your component? React has a new hooks api that lets you use function-based components for most things, but it sounds like you are pretty new to react, and the dichotomy between class and function components might be helpful for you while you start out. For now, think of it this way:

  • Function Components: dumb. just takes data and renders it.
  • Class Compoennts (or function components with hooks): renders data, but also has its own state, and lifecycle methods

So as I'm sure you realize, its not only important to have a way of getting data, and rendering data, its also important to have a way of managing state. For example, you want a place that you can store data and access it later, or perhaps state information like "loading" (t/f), or "error" (t/f).

Another useful concept is component composition. We will leverage a higher order component, that handles the API call for now (there are more sophisticated solutions with libraries like redux / redux-sagas, etc), and conditionally displays the table, or else an error message.

class MyComponent extends react.Component {
  constructor(props){
    super(props);
    this.state = {
      loading: false,
      error: false,
      data: {}
    }
  }

  //this is just a utility function so that you can use async / await with setState
  setStateAsync(state) {
    return new Promise((resolve) => {
      this.setState(state, resolve)
    });
  }

  //will run after your component is mounted to the dom
  async componentDidMount() {
    try{
      await this.setStateAsync({...this.state, loading: true})
      let data = await getCustomer();
      await this.setStateAsync({...this.state, loading: false, data})
    } catch(e) {
      this.setState({error: true, loading: false, data: {}})
    }
  }

  //stick what you would normally return in a function component here
  render() {
    return (
      {this.state.loading ? (<div>"...loading"</div>) :
         this.state.error ? (<div style={{color: 'red'}}>ERROR!</div> :
         (<CustomerDetails customer={this.state.data} />)
    )
  }
}

Read through this for more info on class components. The example above is pretty simple, but notice that:

  1. There is local state
  2. The component renders based on both props AND local state
  3. The return statement wraps javascript in {}. This includes multiple ternaries to conditionally determine what to render. You can replace the loading and error scenarios with more fleshed out components that you can define elsewhere, as you will, and based on your stylistic needs.

Upvotes: 1

Related Questions