1

Hi there I am newbie to React and learning about reusable functions throughout my project.

I would like to fetch my JSON data but not have to call it everytime in my component.

App.js

import React, { Component } from 'react';
import { Route, NavLink, HashRouter } from "react-router-dom";

import logo from '../assets/logo.png';
import './app.css';

import About from "../about/about";
import Services from "../services/services";
import Testimonials from "../testimonials/testimonials";
import Contact from "../contact/contact";

class App extends Component {
constructor(props) {
    super(props);
    this.state = {
       items : []
    };
}
componentDidMount(){
  this.getItems();
}
getItems(){
  fetch('./data/data_arr.js')
  .then(results => results.json())
  .then(results => this.setState({'items': results}));
}
render() {
 return (
  <HashRouter>
    <div className="container">
      <div className="header">
        <div className="App-logo"><NavLink exact to="/"><img src={logo} alt="logo" /></NavLink></div>
        <nav className="Nav-Desktop">
        {this.state.items.map((item, index) => (
          <div key={index}>
          {
            item.links.map((link, i) => (
            <NavLink key={i} exact to={link.url}>{link.text}</NavLink>
          ))}
          </div>
        ))}
        {
          this.state.items.map((item, index) => {
            return <div key={index}><a href={item.mainContact.phoneHref + item.mainContact.phone}><i className="fa fa-phone"></i><strong> {item.mainContact.phone}</strong></a></div>
          })
        }
        </nav>
      </div>
      <main className="content">
         <Route exact path="/" component={About}/>
         <Route path="/services" component={Services}/>
         <Route path="/testimonials" component={Testimonials}/>
         <Route path="/contact" component={Contact}/>
      </main>
      {this.state.items.map((item, index) => {
        return <footer key={index}>&copy; Copyright {item.title} {(new Date().getFullYear())}</footer>
      })
      }
    </div>
    </HashRouter>
    );
  }
}
export default App;

I am successfully mapping my data and displaying it, but I have other files that include this snippet

constructor(props) {
    super(props);
    this.state = {
       items : []
    };
}
componentDidMount(){
  this.getItems();
}
getItems(){
  fetch('./data/data_arr.js')
  .then(results => results.json())
  .then(results => this.setState({'items': results}));
}

I have tried exporting the getItems() like so in a helper.js file and importing the file import { getItems } from '../helpers/helpers'; however the code did not work properly and got stuck at Unhandled Rejection (TypeError): Cannot read property 'setState' of undefined

export function getItems() {
    fetch('./data/data_arr.js')
    .then(results => results.json())
    .then(results => this.setState({'items': results}));
}

If anyone can give me pointers as to the error / right way to go about this that would be helpful. Cheers

4 Answers 4

3

Two things you need to know when you want to reuse the data instead of calling fetch again and again

  1. Do fetch call in top most component i.e., parent component and pass down data to all the children, children to children components but, do remember this will be hectic for you. This approach is good when you are building small application which will be like max to max 50 components. But when your application grows big this is not a recommended way of reusing the data across components.

  2. Use Redux state management library for data reusability across components. This acts like a centralised store for your application. This is used mostly in every React app these days. With Redux you can make an action call in parent component and that action will actually fetch the data and pass it to the reducer. So now reducer will set the data in Redux store. Now the data is accessible in every component by getting the data from Redux store using state.get. So you can call redux action like this.props.getItems(); wherever you need the data in the component and the component mapStateToProps will make that data available to your component as props

How to get the data from Redux store?

Define a function mapStateToProps in component and get the data from Redux store using state.get and return it in the function. Pass the mapStateToProps to connect method. You will be connecting your component to Redux HOC component called connect. This connect method accepts actions and Redux state and make them available to the component as props.

Regarding your issue

   Unhandled Rejection (TypeError): Cannot read property 'setState' of undefined

The reason you get this issue because this isn’t available inside the exported getItems function.

What you need to do to fix the issue is pass this to getItems function as a parameter

Note: Here this is a current context

 import { getItems } from '../helpers/helpers';

 componentDidMount(){
     getItems(this);
 }

helpers.js:

 the below function is treated as a normal JavaScript function. This function no wr belongs the component to bind it in component constructor. But in order to play with the state of the component you need to pass the component context this. But this is old way, now a days we all use Redux to avoid these old concepts

 export function getItems(this) {
      fetch('./data/data_arr.js')
.then(results => results.json())
.then(results => this.setState({'items': results}));
      }

This way you can reuse this function and do the setState.

But do remember these solutions will make complex when your application grows big in future. So I would argue you to go with Redux state management library from now to avoid hectic migration in future :)

Excuse me if there are any typo errors because I am answering from my mobile.

Sign up to request clarification or add additional context in comments.

1 Comment

when trying to use the API call method as a function in separate file, I get this: "Unhandled Rejection (TypeError): Cannot read property 'setState' of undefined"
1

You need to pass the context to your helper function. Considering you're still importing the function from your helper, on your componentDidMount, it can be like:

componentDidMount(){
 getItems.call(this);
}

4 Comments

Andre, he didn't want to do the call for every component. Just once and re use :)
Yeah true, your answer's more valid in terms of best code practices. But I still think it can be useful for him to know why his code didn't work. :)
Yep 100% agree, you need to have context ('this') or setState won't work... that being said i'd never pass the context down the line, in this scenario you'd have getItems return the JSON only, and set the state within your component's code
Thank you for your answer, this did make it work with my getItems export function! I am going to try get my head around the Redux solution as well, and see how I go! Cheers
1

So good job with the fetching, there are a lot of paths you can take with sharing data across components, the two main ones you can look into are:

  1. Parent state holds the data, passes it through props
  2. A Store (Mobx, Redux) holds the data, and you inject it into your components as needed

I'm not going to give you the tutorial on how to do them all but I'm assuming the first option will be best for you right now.

If your components are using the same data, they should theoretically have the same parent component in a structure similar to this:

Page Component (Overarching component, lays out the page structure)
 Header
 Component 1
 Component 2
 Footer

So you'll do this call on the mount of Page component and pass this.state.items as a prop to the other components that need it... Stores have a similar process they just save the double handling of props :)

Comments

0

Since you're accessing/modifying state in your getItems() method, you should bind it in your constructor like this:

constructor() {
    super(props);
    this.state = {
      items : []
    };
    this.getItems = this.getItems.bind(this);
}

or you can simply declare it with arrow function so you don't have to bind it:

getItems = () => {
   // some codes here
}

Another thing, the only ones who can access/modify the state of a compoenent is its own methods. If you want to use it outside of itself, import those components inside your component as children then pass your getItems() method as prop.

  render() {
  return (
    <HashRouter>
      <div className="container">
        // some other codes
        <ChildComponent getItems={this.getItems} />
      </div>
      </HashRouter>
    );
  }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.