When I use the react-admin component <List> <Edit> the AUTH_ERROR is called in my authProvider.
But when I'm using the dataProvider in a custom component, the AUTH_ERROR is not called.
As stated in the documentation, if any API calls return any error, the authProvider will catch it with type AUTH_ERROR.
I'm using the default dataProvider like in the tutorial :
const API_URL = process.env.REACT_APP_API_URL;
const convertDataProviderRequestToHTTP = (type, resource, params) => {
let url = '';
const token = localStorage.getItem('token');
const options = {
headers: new Headers({
'Accept': 'application/ld+json',
'Content-Type': 'application/ld+json',
'Authorization': `Bearer ${token}`,
}),
};
switch (type) {
case 'GET_GENERAL': {
url = `${API_URL}/${resource}?${stringify(params)}`;
break;
}
case GET_ONE:
url = `${API_URL}/${resource}/${params.id}`;
break;
case UPDATE:
url = `${API_URL}/${resource}/${params.id}`;
options.method = 'PUT';
options.body = JSON.stringify(params.data);
break;
case DELETE:
url = `${API_URL}/${resource}/${params.id}`;
options.method = 'DELETE';
break;
default:
throw new Error(`Unsupported fetch action type ${type}`);
}
return {url, options};
};
const convertHTTPResponseToDataProvider = (response, type, resource, params) => {
const {json} = response;
switch (type) {
case GET_LIST:
case GET_MANY_REFERENCE:
let data = json['hydra:member'];
return {
data: data,
total: json['hydra:totalItems'],
};
case CREATE:
return {data: json};
default:
return {data: json};
}
};
export default (type, resource, params) => {
const {fetchJson} = fetchUtils;
const {url, options} = convertDataProviderRequestToHTTP(type, resource, params);
return fetchJson(url, options)
.then (response => {console.log(response) ; return response})
.then(response => convertHTTPResponseToDataProvider(response, type, resource, params));
};
And in my custom component here's how I call the dataProvider :
class MyClass extends PureComponent {
constructor(props) {
super(props);
this.state = {
data: null,
};
}
componentDidMount() {
dataProvider('GET_GENERAL', 'myRessource', {
"perPage": 1,
"page": 1,
"oneField.id": 123,
"twoField.id": 132,
"threeField.id": 145,
})
.then(response => response.data)
.then((response) => {
this.setState({data: response});
})
};
render() {
const {data} = this.state;
return <div>{data.field}</div>
}
}
const enhance = compose(
withStyles(styles),
connect(mapStateToProps, {customAction}),
translate
);
export default enhance(MyClass);
And of course my authProvider is configured like that :
// ...
if (type === AUTH_ERROR) {
const {status} = params;
if (status === 401 || status === 403) {
console.log('here');
return Promise.reject();
}
console.log('there');
return Promise.resolve();
}
// ...
In my example, my API returns HTTP 401, and in the console I never get 'here' or 'there', so I can't perform custom action on AUTH_ERROR.
Any ideas what am I doing wrong ?
I Figure out how to handle the FETCH_ERROR.
The doc said : React-admin components don’t call the dataProvider function directly. They use withDataProvider. So here's the solution with my example below :
class MyClass extends PureComponent {
constructor(props) {
super(props);
this.state = {
data: null,
};
}
componentDidMount() {
const { dataProvider } = this.props;
dataProvider('GET_GENERAL', 'myRessource', {
"perPage": 1,
"page": 1,
"oneField.id": 123,
"twoField.id": 132,
"threeField.id": 145,
})
.then(response => response.data)
.then((response) => {
this.setState({data: response});
})
};
render() {
const {data} = this.state;
return <div>{data.field}</div>
}
}
const enhance = compose(
withStyles(styles),
withDataProvider,
connect(mapStateToProps, {customAction}),
translate
);
export default enhance(MyClass);
Related
I need help with the async nature of Async storage and axios api. Here's the functionality that I am trying to achieve ->
send request to two separate api to get some data.
display that data on the screen with some additional text
api request are authenticated so a token is passed as Authentication Header
I have attached the current implementation, I am having the a number of errors in this
Errors:
Login_token not set in state after fetching from Async Storage.
Data not set in state after api call
both resulting in either failed api calls or undefined state errors on render
This is my code.
import React, { FunctionComponent, useEffect, useCallback, useState} from 'react';
import { StyleSheet, View} from 'react-native';
// chat
import { GiftedChat } from 'react-native-gifted-chat';
// navigation
import { RootStackParamList } from '../../navigators/RootStack';
import { StackScreenProps } from '#react-navigation/stack';
export type Props = StackScreenProps<RootStackParamList, "Chat">;
// api
import { Convo_details, Send_Msg, List_Msg, Expert_Public_Profile } from '../../api/UserApi';
import Spinner from 'react-native-loading-spinner-overlay';
import AsyncStorage from '#react-native-async-storage/async-storage';
import uuid from 'react-native-uuid';
const Chat: FunctionComponent<Props> = ({ navigation, route, ...props }) => {
// console.log(props.route.params);
const [login_token, setlogin_token] = useState('')
const [conversation_id, setconversation_id] = useState('')
const [conversation_details, setconversation_details] = useState({})
const [currentuser, setcurrentuser] = useState({})
const [loading, setLoading] = useState(false);
const [expertuid, setexpertuid] = useState('')
const [ExpertProfile, setExpertProfile] = useState({})
const [messages, setMessages] = useState([]);
useEffect(() => {
getlogintoken()
console.log("####################################","getlogintoken");
}, [])
/* conversationid */
useEffect(() => {
if (route.params != null) {
setconversation_id(route.params[0])
}
console.log("####################################","conversation id");
}, [])
/* expert uid */
useEffect(() => {
if (route.params != null) {
setexpertuid(route.params[1])
}
console.log("####################################","expert uid");
}, [])
/* expert public profile */
useEffect(() => {
getexpertpublicprofile()
getConvo_details()
console.log("####################################","convo_details");
}, [])
useEffect(() => {
// get current user
AsyncStorage.getItem("currentuser").then(res => {
if (res != null) setcurrentuser(res)
else alert("Current user not found")
})
console.log("####################################","current user");
}, [])
// set welcome msg
useEffect(() => {
if (Object.keys(conversation_details).length != 0 && Object.keys(ExpertProfile).length != 0)
setwelcomemsg()
}, [])
const onSend = useCallback(async (messages = []) => {
// console.log(messages[0].text);
setMessages(previousMessages => GiftedChat.append(previousMessages, messages))
const data = {
conversation_id: "f98d6851-a713-4f58-9118-77a779ff175f",//conversation_id,
message_type: "TEXT",
body: messages[0].text
}
const res: any = await Send_Msg(data, login_token)
.catch(error => {
alert(`Send_Msg -> ${error}`)
console.log(error);
return
})
if (res.status == 200) {
console.log(res.data);
} else console.log(res);
}, [])
const getexpertpublicprofile = async () => {
setLoading(true)
const res: any = await Expert_Public_Profile(expertuid, login_token)
.catch(error => {
setLoading(false)
console.log("Expert public profile ->");
alert(`Expert public profile ->${error.message}`)
console.log(error);
return
})
setLoading(false)
if (res.status === 200) setExpertProfile(res.data)
else {
alert(`get expert public profile${res.data.message}`)
console.log("getexpertpublicprofile -->");
console.log(res.data);
}
}
const getlogintoken = () => {
AsyncStorage.getItem("login_token").then(res => {
if (res != null) {
setLoading(false)
setlogin_token(res)
}
else alert("No login token found")
})
}
const getConvo_details = async () => {
setLoading(true)
const res: any = await Convo_details(conversation_id, login_token)
.catch(error => {
setLoading(false)
alert(`Convo_details-->${error.message}`)
console.log("Convo_details -->");
console.log(error);
return
})
setLoading(false)
if (res.status === 200) setconversation_details(res.data)
else {
alert(`get convo details-> ${res.data.message}`)
console.log("getConvo_details -->");
console.log(res.data);
}
}
const setwelcomemsg = () => {
try {
let user = JSON.parse(currentuser)
let messages = [
{
_id: uuid.v4().toString(),
conversation_id: conversation_details.conversation_id,
created_at: new Date(),
from: conversation_details.recipient.user_uid,
type: "TEXT",
text: `About Me - ${ExpertProfile.bio}`,
user: {
_id: conversation_details.recipient.user_uid,
}
},
{
_id: uuid.v4().toString(),
conversation_id: conversation_details.conversation_id,
created_at: new Date(),
from: conversation_details.recipient.user_uid,
type: "TEXT",
text: `My name is ${conversation_details.recipient.name}`,
user: {
_id: conversation_details.recipient.user_uid,
}
},
{
_id: uuid.v4().toString(),
conversation_id: conversation_details.conversation_id,
created_at: new Date(),
from: conversation_details.recipient.user_uid,
type: "TEXT",
text: `Hi ${user.full_name}`,
user: {
_id: conversation_details.recipient.user_uid,
}
}]
setMessages(previousMessages => GiftedChat.append(previousMessages, messages))
} catch (error) {
console.log("try -> set welcome msg");
console.log(error);
return
}
}
return (
<View style={styles.maincontainer}>
<Spinner
visible={loading}
textContent={'Loading...'}
textStyle={{ color: '#FFF' }}
/>
<GiftedChat
messages={messages}
onSend={messages => onSend(messages)}
user={{
_id: currentuser.user_uid,
}}
isTyping={false}
scrollToBottom={true}
showAvatarForEveryMessage={true}
renderAvatar={() => null}
/>
</View>
);
}
export default Chat;
const styles = StyleSheet.create({
maincontainer: {
flex: 1,
},
});
When axios returns, it usually give the response as res.data, so in your case, try either res.data or res.data.yourToken (I'm not sure how it's your object).
Gurav,
As far as your code above, The api call's will trigger even before you get currentuser or loginToken. You have to handle the api call after getting the currentuser and loginToken. This can be gracefully handled with async, await.
example code:
useEffect(() => {
getData()
}, [])
useEffect(() => {
if(login_token && currentuser) {
//The api call goes here after you get the logintoken andcurrentuser.
// The above condition is just an example but will vary based on your requirements
}
}, [login_token, currentuser])
const getData = async () => {
await getlogintoken()
await getcurrentuser()
}
const getlogintoken = async () => {
await AsyncStorage.getItem("login_token").then(res => {
if (res != null) {
setLoading(false)
setlogin_token(res)
}
else alert("No login token found")
})
}
const getcurrentuser = async () => {
await AsyncStorage.getItem("currentuser").then(res => {
if (res != null) setcurrentuser(res)
else alert("Current user not found")
})
}
I have searched Google for a clear answer on this but cant find one. Below is my code.
In a previous screen I have stored the token to SecureStore
I'm now trying to access it from a different screen.
(async () => {
const token = await SecureStore.getItemAsync('token');
return token;
})();
export default class App extends React.Component {
constructor(){
super();
this.state = {
data: [],
loaded: true,
error: null,
token: token
}
}
Can anyone advise me how to get the value from SecureStore to my state inside the class?
Entire Code
import React, { Component} from 'react';
import { Text, Button, ScrollView } from 'react-native';
import { globalStyles } from '../styles/global';
import * as SecureStore from 'expo-secure-store';
(async () => {
const token = await SecureStore.getItemAsync('token');
//console.log('token output 1 ' + token);
return token;
})();
export default class App extends React.Component {
constructor() {
super();
this.state = {
data: [],
loaded: true,
error: null,
token: ""
};
}
baseURL = 'https://www.example.co.uk/api/auth';
getData = (ev)=>{
this.setState({loaded:false, error: null});
let url = this.baseURL + '/list';
let h = new Headers();
console.log('token output 2 = ' + this.state.token);
h.append('Authorization', 'Bearer tokenToBePutHere');
h.append('Content-Type', 'application/json');
h.append('X-Requested-With', 'XMLHttpRequest');
let req = new Request(url, {
headers: h,
method: 'GET'
});
fetch(req)
.then(response=>response.json())
.then(this.showData)
.catch(this.badStuff)
}
showData = (data)=>{
this.setState({loaded:true, data:data});
}
badStuff = (err) => {
this.setState({loaded: true, error: err.message});
}
componentDidMount() {
(async () => {
const token = await SecureStore.getItemAsync('token');
this.setState({ token });
})();
this.getData();
}
render() {
return (
<ScrollView style={globalStyles.container}>
{ !this.state.loaded && (
<Text>LOADING</Text>
)}
<Text>Your Lists Are:</Text>
{/*} <Button title="Get Data"
onPress={this.getData} /> */}
{ this.state.error && (
<Text style={styles.err}>{this.state.error}</Text>
)}
{ this.state.data && this.state.data.length > 0 && (
this.state.data.map( data => (
<Text key={data.id}>
{ data.lists.name }
</Text>
))
)}
</ScrollView>
);
}
}
I have added all of my code for this screen.
token Output 1 works and outputs the correct token
token Output 2 does not work and returns nothing.
I need to use the token in the api call but cant get it to pass there.
You can use the lifecycle method componentDidMount and callbacks like this:
export default class App extends React.Component {
constructor() {
super();
this.state = {
data: [],
loaded: true,
error: null,
token: ""
};
}
componentDidMount() {
SecureStore.getItemAsync("token").then(token => {
this.setState({ token });
});
}
// ...
}
Same solution using async/await:
class App extends React.Component {
constructor() {
super();
this.state = {
data: [],
loaded: true,
error: null,
token: '',
};
}
componentDidMount() {
(async () => {
const token = await SecureStore.getItemAsync('token');
this.setState({ token });
})();
}
// ...
}
Addressing updated question
If you need only need the token for the fetch request you don't even need to store the token in the state. You can use it directly after retrieving it from SecureStore:
componentDidMount() {
(async () => {
const token = await SecureStore.getItemAsync('token');
// Your fetch code
this.setState({loaded:false, error: null});
let url = this.baseURL + '/list';
let h = new Headers();
h.append('Authorization', `Bearer ${token}`);
h.append('Content-Type', 'application/json');
h.append('X-Requested-With', 'XMLHttpRequest');
let req = new Request(url, {
headers: h,
method: 'GET'
});
fetch(req)
.then(response=>response.json())
.then(() => this.setState({loaded:true, data:data}))
.catch(() => this.badStuff())
})();
}
Problem:
I have create react-native application.And there I am using AsyncStorage with axios in like this to handle my API calls.This is how that looks like.
import axios from "axios";
import { AsyncStorage } from "react-native";
// TODO: Replace this with actual JWT token from Keycloak
axios.defaults.headers.post["Content-Type"] = "application/json";
// Create axios instance for api calls
var instance = null;
export const setAuth = async () => {
const user = await AsyncStorage.getItem("jwt");
AsyncStorage.getItem("jwt").then(token => {
instance = axios.create({
baseURL: "",
timeout: 150000,
headers: {
Authorization: "Bearer " + token,
"Content-Type": "application/json"
}
});
instance.interceptors.response.use(
function(response) {
return response;
},
async function(error) {
if (error.response.status) {
return error;
}
}
);
});
};
export const Get = (route, data) => {
instance || setAuth();
return instance.get(
route,
data == null ? { data: {} } : { data: JSON.stringify(data) }
);
};
export const Post = (route, data) => {
instance || setAuth();
return instance.post(route, JSON.stringify(data));
};
export const Put = (route, data) => {
debugger;
instance || setAuth();
return instance.put(route, JSON.stringify(data));
};
export const AddAdmin = (route, data) => {};
Becuase of the asynchronus property of AsyncStorage it is not creating the axios instanceinstance = axios.create({.The problem is with after this line .So can someone help me with this.I do not have any idea to find out what is wrong with this. Thank you.
You can give a try to this.
export const setAuth = async () => {
const token = await AsyncStorage.getItem('jwt');
instance = axios.create({
baseURL: '',
timeout: 150000,
headers: {
Authorization: 'Bearer ' + token,
'Content-Type': 'application/json'
}
});
// remaining Code
};
export const Get = (route, data) => {
function getData(){
return instance.get(
route,
data == null ? { data: {} } : { data: JSON.stringify(data) }
)
}
if(instance) return getData()
return setAuth().then(getData)
}
I have an app authenticating fine and returning the access_token and refresh_token. I store them with AsyncStorage and save/get the access_token with redux. This is the very first app I am building and I am struggling with how and where to use the refresh_token.
This is the axios call in the component loginForm.js
axios({
url: `${base}/oauth/token`,
method: 'POST',
data: formData,
headers: {
Accept: 'application/json',
'Content-Type': 'multipart/form-data',
}
})
.then(response => {
setStatus({ succeeded: true });
// console.log(response.data);
deviceStorage.saveKey("userToken", response.data.access_token);
deviceStorage.saveKey("refreshToken", response.data.refresh_token);
Actions.main();
})
.catch(error => {
if (error.response) {
console.log(error);
}
});
This is the service deviceStorage.js
import { AsyncStorage } from 'react-native';
const deviceStorage = {
async saveItem(key, value) {
try {
await AsyncStorage.setItem(key, value);
} catch (error) {
console.log('AsyncStorage Error: ' + error.message);
}
}
};
export default deviceStorage;
This is the token action file
import { AsyncStorage } from 'react-native';
import {
GET_TOKEN,
SAVE_TOKEN,
REMOVE_TOKEN,
LOADING_TOKEN,
ERROR_TOKEN
} from '../types';
export const getToken = token => ({
type: GET_TOKEN,
token,
});
export const saveToken = token => ({
type: SAVE_TOKEN,
token
});
export const removeToken = () => ({
type: REMOVE_TOKEN,
});
export const loading = bool => ({
type: LOADING_TOKEN,
isLoading: bool,
});
export const error = tokenError => ({
type: ERROR_TOKEN,
tokenError,
});
export const getUserToken = () => dispatch =>
AsyncStorage.getItem('userToken')
.then((data) => {
dispatch(loading(false));
dispatch(getToken(data));
})
.catch((err) => {
dispatch(loading(false));
dispatch(error(err.message || 'ERROR'));
});
export const saveUserToken = (data) => dispatch =>
AsyncStorage.setItem('userToken', data)
.then(() => {
dispatch(loading(false));
dispatch(saveToken('token saved'));
})
.catch((err) => {
dispatch(loading(false));
dispatch(error(err.message || 'ERROR'));
});
export const removeUserToken = () => dispatch =>
AsyncStorage.removeItem('userToken')
.then((data) => {
dispatch(loading(false));
dispatch(removeToken(data));
})
.catch((err) => {
dispatch(loading(false));
dispatch(error(err.message || 'ERROR'));
});
This is the token reducer file
import {
GET_TOKEN,
SAVE_TOKEN,
REMOVE_TOKEN,
LOADING_TOKEN,
ERROR_TOKEN
} from '../actions/types';
const INITIAL_STATE = {
token: {},
loading: true,
error: null
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_TOKEN:
return {
...state,
token: action.token
};
case SAVE_TOKEN:
return {
...state,
token: action.token
};
case REMOVE_TOKEN:
return {
...state,
token: action.token
};
case LOADING_TOKEN:
return {
...state,
loading: action.isLoading
};
case ERROR_TOKEN:
return {
...state,
error: action.error
};
default:
return state;
}
};
And this is the authentication file
import React from 'react';
import {
StatusBar,
StyleSheet,
View,
} from 'react-native';
import { connect } from 'react-redux';
import { Actions } from 'react-native-router-flux';
import { Spinner } from '../common';
import { getUserToken } from '../../actions';
class AuthLoadingScreen extends React.Component {
componentDidMount() {
this.bootstrapAsync();
}
bootstrapAsync = () => {
this.props.getUserToken().then(() => {
if (this.props.token.token !== null) {
Actions.main();
} else {
Actions.auth();
}
})
.catch(error => {
this.setState({ error });
});
};
render() {
return (
<View style={styles.container}>
<Spinner />
<StatusBar barStyle="default" />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
},
});
const mapStateToProps = state => ({
token: state.token,
});
const mapDispatchToProps = dispatch => ({
getUserToken: () => dispatch(getUserToken()),
});
export default connect(mapStateToProps, mapDispatchToProps)(AuthLoadingScreen);
I believe I need to create an action and reducer to get the refresh_token (is that correct?) but I do not know what to do with it and where to call it (perhaps in the authentication file?).
Any help with this possibly with code examples related to my code would be massively appreciated. Thanks
Below are the steps
Do Login , get accessToken , refreshToken from response and save it to AsyncStorage.
Make common function for API calling
async function makeRequest(method, url, params, type) {
const token = await AsyncStorage.getItem('access_token');
let options = {
method: method,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token,
},
};
if (!token) {
delete options['Authorization'];
}
if (['GET', 'OPTIONS'].includes(method)) {
url += (url.indexOf('?') === -1 ? '?' : '&') + queryParams(params);
} else {
Object.assign(options, {body: JSON.stringify(params)});
}
const response = fetch(ENV.API_URL+url, options);
return response;
}
Make one method in redux for getAceessTokenFromRefreshToken.
Use this method when session is expired
How do you know session is expired?
From each API calling if you get response like (440 response code) in
async componentWillReceiveProps(nextProps) {
if (nextProps.followResponse && nextProps.followResponse != this.props.followResponse) {
if (nextProps.followResponse.status) {
if (nextProps.followResponse.status == 440) {
// call here get acceesstokenfrom refresh token method and save again accesstoken in asyncstorage and continue calling to API
}
}
}
}
I am having troubles with getting the state in my HomeComponent.js . Every time I try to print it, it return "undefined" .
I've tried different ways to call onPress in my Home component (e.g. onPress={this.printState()}, but none work)
This is my HomeComponent.js
//import statements
const mapStateToProps = state => {
return {
jobTitles: state.jobTitles
}
}
const mapDispatchToProps = dispatch => ({
fetchJobTitles: () => dispatch(fetchJobTitles())
});
class Home extends Component {
constructor(props) {
super(props);
this.state = {
jobInputValue: '',
addressInputValue: ''
};
}
componentDidMount() {
this.props.fetchJobTitles();
}
printState = () => {
console.log('State is: ' +
JSON.stringify(this.state.jobTitles));
}
render() {
return (
<ImageBackground style={styles.bkgImage} source={require('../assets/homepage_background.jpg')}>
//JSX goes here
<Button
title="CAUTĂ"
type="outline"
underlayColor={colors.red}
titleStyle={styles.buttonTitleStyle}
color={colors.red}
style={styles.buttonStyle}
onPress={this.printState}
/>
</ImageBackground>
);
}
}
//some styles
export default connect(mapStateToProps, mapDispatchToProps)(Home);
This is my reducer (jobTitles.js):
import * as ActionTypes from '../ActionTypes';
export const jobTitles = (state = { errMess: null,
jobTitles:[]}, action) => {
switch (action.type) {
case ActionTypes.GET_JOB_TITLES:
return {...state, errMess: null, jobTitles: action.payload};
case ActionTypes.JOB_TITLES_FAILED:
return {...state, errMess: action.payload};
default:
return state;
}
};
And this is my Action Creator:
import * as ActionTypes from './ActionTypes';
import { baseUrl } from '../shared/baseUrl';
export const fetchJobTitles = () => (dispatch) => {
return fetch(baseUrl + 'api/jobs/job_keywords')
.then(response => {
if (response.ok) {
return response;
} else {
var error = new Error('Error ' + response.status + ': ' +
response.statusText);
error.response = response;
throw error;
}
},
error => {
var errmess = new Error(error.message);
throw errmess;
})
.then(response => response.json())
.then(jobTitles => dispatch(addJobTitles(jobTitles)))
.catch(error => dispatch(jobTitlesFailed(error.message)));
};
export const jobTitlesFailed = (errmess) => ({
type: ActionTypes.JOB_TITLES_FAILED,
payload: errmess
});
export const addJobTitles = (jobTitles) => ({
type: ActionTypes.GET_JOB_TITLES,
payload: jobTitles
});
This is how the response from the API looks like:
"jobTitles": Object {
"results": Array [
"Engineer",
"Software",
"Software Architect",
"Software Consultant",
"Solution Architect",
"System Architect"
]
}
I expected the console.log() statement from the print() function in the HomeComponent.js to print the JSON response from the API, but instead it returns "undefined". Any ideas why?
Any help will be greatly appreaciated!
In your code :
this.state = {
jobInputValue: '',
addressInputValue: ''
};
What you try to print :
this.state.jobTitles
Of course it's undefined ! Either log this.props.jobTitles or set state jobTitles to print what you want.
You should use this.props.jobTitles
The mapStateToProps puts data from the redux state into the props of the component. this.state only holds the local state of the component. So jobInputValue and addressInputValue in this case. Everything from mapStateToProps and mapDispatchToProps will end up in the props. (As the name of the function indicates)