11

I built an api that returns me the units that I have registered in a DB but when trying to return these are not reviewing

import React, { Component } from 'react';
import axios from 'axios';

const api = {

  units: () => {
    axios.get('http://192.168.0.23/api/public/api/units')
    .then(response => {
      console.log(response);
        return response.data.data
    }).catch((error) => { 
      console.log(error.message)
    });
    return 'Error'
  },

};

export default api;

The response displays the data correctly, the response.data.data if used in a file with a class I can set a state units = [] and with a setState return the units

However I would like to create this file for the api returns, however as I do not use a class so I understand I can not use the state.

Is there any way of without the class returning this data, or keeping in a variable or something of the sort?

Or use setState right here without a class?

I believe that these would be the ways to solve my problem, but if someone else knows otherwise than putting the api call in the same file as the final class may be too.

I tried that way too:

async function getUnits() {   
  return await axios.get('http://192.168.0.23/api/public/api/units');
}

getUnits.then((response) => {
  console.log(response);
    return response.data.data
}).catch((response) => { 
  console.log(response)
});

But the error indicated that getUnits.then is not a function

Even with a normal function did not work:

function units() {
  axios.get('http://192.168.0.23/api/public/api/units')
  .then(response => {
    console.log(response);
      return response.data.data
  }).catch((error) => { 
    console.log(error.message)
  });
  return 'Error'
};
Magas
  • 522
  • 1
  • 4
  • 22
  • are you using redux...? – Piyush Dhamecha Dec 13 '17 at 16:48
  • @PiyushDhamecha Not this project is just with this file to really understand why not to get the return and how to solve – Magas Dec 13 '17 at 16:50
  • 2
    you need to call your api in componentDidMount and set to it state and use it on your jsx – Santosh Shinde Dec 13 '17 at 16:53
  • Its kind of hard to follow your question. If the data is coming back fine from the api then what is the problem? – ShaneG Dec 13 '17 at 16:54
  • Have you tried this `return response.json()` instead of this `return response.data.data` and then another `.then(response=>console.log(response))`, and finally return the axios or make a variable then return the variable you have created. – Jrey Dec 13 '17 at 16:54
  • @SantoshShinde for use componentDidMount I need a Class – Magas Dec 13 '17 at 16:59
  • You are using a class "class App" above by the looks of it – ShaneG Dec 13 '17 at 17:00
  • @ShaneG that I just show the response on console ou setState, and I want to pass this to a var or return in a call from other file – Magas Dec 13 '17 at 17:00
  • @ShaneG but I want that it can be call from any file, with or without class on it – Magas Dec 13 '17 at 17:01
  • @Mate you almost there but you need to figure out your state and props – Santosh Shinde Dec 13 '17 at 17:01
  • @SantoshShinde is right. You can still do what you want using a class. You just need to use state to store your api data and then props to pass it between screens, etc – ShaneG Dec 13 '17 at 17:02
  • @SantoshShinde but how can I use state out of a class, all my tests get errors – Magas Dec 13 '17 at 17:03
  • @mate for that case you need to use props – Santosh Shinde Dec 13 '17 at 17:04
  • @Mate Please see my answer for some explanation about stateful components vs stateless components and how you can pass data from your `api` object to your React components. – quickshiftin Dec 13 '17 at 17:13
  • @Mate I have added snippet for you please check it and let me know if you have any issue – Santosh Shinde Dec 13 '17 at 17:27
  • @SantoshShinde My question is about how to do it out of a class, and from what I could see from the answers, it was the answer that what I want to do is impossible – Magas Dec 13 '17 at 17:30
  • Here `The response displays the data correctly, the response.data.data if used in a file with a class I can set a state units = [] and with a setState return the units` I spoke that I can do it with setState and a class – Magas Dec 13 '17 at 17:32
  • But I want to now how to do it calling in a file without a class – Magas Dec 13 '17 at 17:32
  • 1
    @Mate You will not be able to do it without classes unless you use Redux. Without Redux at least one of your components must manage the state. – quickshiftin Dec 13 '17 at 17:33
  • 2
    I think you need use redux for data flow management for whole app – Santosh Shinde Dec 13 '17 at 17:34
  • Thanks, I'll learn redux then or change my file to use class – Magas Dec 13 '17 at 17:39
  • 1
    I have added some react + redux samples in the answer which will help you to figure out your solution. – Santosh Shinde Dec 14 '17 at 06:18
  • @SantoshShinde Thank you, I will read, but I can not give the answer as certain because I believe that an answer to be considered correct, would be or one that showed me how to perform the return without using a class, or else one proving and explaining in detail the reason of not being able , and there talking about redux and why this is an option to solve the problem – Magas Dec 14 '17 at 16:12
  • Please check this one https://stackoverflow.com/questions/30205145/reactjs-global-helper-functions may be it will help you – Santosh Shinde Dec 14 '17 at 16:27

4 Answers4

5

Use the following code, It will help you.

Normally we would just load the data and then render our app. But React expects some initial state for all that data and needs to update itself once that AJAX request is complete. For this reason, you need to store all that data in the app's state.

Here's how that looks:

      import React, { Component } from 'react';
      import { Platform, StyleSheet, Text, View } from 'react-native';
      import axios from 'axios';


      export default class App extends Component { 

        constructor(props) {
          super(props);

          this.state = {units: []};
        }

        componentDidMount() {
          this.getUnits();
        }

        getUnits() {
          axios.get('http://192.168.0.23/api/public/api/units')
          .then(response => {
            this.setState({ units: response.data.data }));
          }).catch((error) => { 
            console.log(error.message)
          });
        }

        render() {
          const unit = this.state.units.map((item, i) => (
            <div>
              <h1>{ item.example }</h1>
            </div>
          ));

          return (
            <View style={{ flexGrow: 1, alignItems: 'center', justifyContent: 'center' }}>
              <Text>Units: {unit}</Text>
            </View>
          );
        }
      }

And If you want subcomponents to pass that data down to. Here's what that looks like:

          const unit = this.state.units.map((item, i) => (
            <div>
              <h1>{ item.example }</h1>
             <Test unitData={item} />
            </div>
          ));

In different Test component

        class Test extends Component { //use the data in a class component

          constructor(props) {
            super(props);
          }

          render() {
            const unit = this.props.unitData

            return (
              <div className="test">
               <h1>{unit.example}</h1>
              </div>
            );
          }
        }

Please check example to here, which will help you to resolve your issue.

Examples:

  1. react-native-redux-example
  2. basic-react-native-redux-example

Hope this will help you!!

Santosh Shinde
  • 6,045
  • 7
  • 44
  • 68
1

First off in this snippet

async function getUnits() {   
  return await axios.get('http://192.168.0.23/api/public/api/units');
}

getUnits.then((response) => {
  console.log(response);
    return response.data.data
}).catch((response) => { 
  console.log(response)
});

You have made a mistake, you should be calling the getUnits function here eg

getUnits().then(.....)

Regarding whether or not to use a stateful component or a functional component, have a look at the documentation here for some explanation about the differences.

The first page discusses functional components which do not manage their own state. The second page discusses state.

With your situation one of the components will have to make the AJAX call, so one component must be responsible for the state. If you are using Redux the conversation becomes much different. However if you aren't using Redux, you generally will have the state in a high level stateful component with stateless components beneath it. The base component will pass the data (from your AJAX request) to its stateless children by way of props (which are read-only).

So finally we can get to some code! In your api object, let's support a way to pass data back to the caller. Here we are adding onLoad and onError callback parameters which accept functions as arguments. When a successful response arrives, the onLoad callback is called with the data from the API. When an error occurs, the onError callback is called with the error message.

import axios from 'axios';

const api = {
  units: (onLoad, onError) => {
    axios.get('http://192.168.0.23/api/public/api/units')
    .then(response => {
        onLoad(response.data.data);
    }).catch((error) => { 
        onError(error.message);
    });
  },
};

export default api;

Now we'll make your App component stateful (remember, it can have stateless children that it passes data via props). Note we are invoking the API call right before the component will mount, because we call it in the componentWillMount lifecycle method of React. This will not prevent the component from rendering though since your api object is making an asynchronous call, this merely invokes the API request and let's your component render immediately. Once the data arrives, your api object will call the onLoad callback, which is the onApiLoad method of your App component. The App component will then update its state and re-render!

import React, { Component } from 'react';
import { Platform, StyleSheet, Text, View } from 'react-native';
import api from './units';

export default class App extends Component {

  constructor(props) {
    super(props);

    this.state = {units: []};
  }

  componentWillMount() {
    api.units(this.onApiLoad, this.onApiError);
  }

  onApiLoad(units) {
    this.setState({
      units: units
    });
  }

  onApiError(errorMessage) {
    // TODO Something meaningful with error message
  }

  render() {
    const unit = this.state.units.map((item, i) => (
      <div>
        <h1>{ item.example }</h1>
      </div>
    ));

    return (
      <View style={{ flexGrow: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Units: {unit}</Text>
      </View>
    );
  }
}
quickshiftin
  • 66,362
  • 10
  • 68
  • 89
1

If I got your problem then this is what you need to do:

change this:

import React, { Component } from 'react';
import axios from 'axios';

const api = {

units: () => {
  axios.get('http://192.168.0.23/api/public/api/units')
    .then(response => {
      console.log(response);
      return response.data.data
    }).catch((error) => {
    console.log(error.message)
  });
  return 'Error'
},

};

export default api;

to:

import axios from 'axios';

const api = {

units: () => {
  return axios.get('http://192.168.0.23/api/public/api/units')
    .then(response => {
      console.log(response);
      return response.data.data
    }).catch((error) => {
    console.log(error.message)
  });
  return 'Error'
},

};

export default api;

just added a return statement there before axios

then use it in your component like

import api from './api';
....
....
....
componentDidMount() {
  api.units().then(units => this.setState({ units }))
}
....
....
....
Spider
  • 140
  • 6
  • My problem is almost this, but I want to import the api into a file that does not contain a class, so I would not have how to use `componentDidMount()` and `this.setState` – Magas Dec 19 '17 at 14:43
  • Can you explain it a bit more. Sorry but I think I am not getting your problem correctly – Spider Dec 20 '17 at 16:22
  • Can you explain what kind behaviour you want in the file using code. Just show me how you want to use this in the file. – Spider Dec 20 '17 at 16:31
  • I want to do call this in any file I want, if i have a file that just have a const like a form make with formik https://github.com/jaredpalmer/formik and want to call the values from that api to show in a picker in that form – Magas Dec 20 '17 at 19:07
  • The great problem is how to get the values without use states, because if I dont have a class in one of the files i can not have how to use states and because of that I can not call the values from that API – Magas Dec 20 '17 at 19:08
  • And without a class I can not use componentDidMount() too – Magas Dec 20 '17 at 19:10
1

I encountered this issue of async callback in my initial days of programming with javascript, where I found it very difficult to pass results between the files using pure functions. There is something I can suggest you, that you can use anywhere with async programming in javascript and has nothing to do with react.js

import React, { Component } from 'react';
import axios from 'axios';

const api = {

  units: (callback) => {
    axios.get('http://192.168.0.23/api/public/api/units')
    .then(response => {
      console.log(response);
        callback(response.data.data);
    }).catch((error) => { 
      console.log(error.message)
    });
    return 'Error'
  },

};

export default api;

And inside your App component:

import React, { Component } from 'react';
import { Platform, StyleSheet, Text, View } from 'react-native';

import api from './units';

export default class App extends Component { 
  constructor(props) {
    super(props);

    this.state = {
      units: [],
    };
  }
  //Always do async tasks(side effects) in componentDidMount, I will provide you a link to know Do's and Dont's with lifecycle hooks.

  componentDidMount() {
    if(this.state.units.length === 0){
      api.units((data) => this.setState({units: data}));
    }
  }

  render() {
    return (
      <View style={{ flexGrow: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Units: {this.state.units}</Text>
      </View>
    );
  }
}

This is the way of passing a callback functions around when you deal with async operations in Javascript, as you can not return a evaluated result from an async function, because as soon as the result is still being evaluated your function will return something and get out of the scope, but you can use the callback handler of the async operation to finally update your view according to the evaluated result. I am only handling the success callback, you can pass error callback as well to handle the error case.

smishr4
  • 45
  • 1
  • 2
  • 6
  • https://medium.com/@baphemot/understanding-reactjs-component-life-cycle-823a640b3e8d Further details on React Lifecycle hooks(understand Do's and Dont's). – smishr4 Dec 20 '17 at 15:44