I created a file which would contain helper functions for querying the database. My problem is that I have to get my access token through the Context API but I can't call the useContext hook outside of a functional component. I could place the functions inside one, but I don't need the component, it would be unused. What is the best practice here?
import React, { useContext } from "react";
import axios from "axios";
import { AuthContext } from "../../contexts/auth-context.js";
import { mongoQuery } from "../xhr/QueryMongo";
const { getAccessToken } = useContext(AuthContext);
export async function fetchUserData(userId) {
const sRealmApp = "...";
const CancelTokenLogin = axios.CancelToken;
const sourceLogin = CancelTokenLogin.source();
let token = await getAccessToken("users");
const userQuery = `query {user (query:{_id:"${userId}"}) {name, role, residence }}`;
let queryResult = await mongoQuery(token, sRealmApp, userQuery, sourceLogin);
if (queryResult.data.data !== null && queryResult.data.data.user !== null) {
return queryResult.data.data.user;
} else return false;
//other similar helper functions....
}
(Why I'm creating a new file: I'm refactoring my code because I have a file which has 400 lines of code, but it's not a big project. So I decided to extract code which connects to the database because it's not directly linked to the component.)
You can always provide the context as a parameter for your helper function.
helperFunction.js
fetchUserData(userId, token) {
... rest of code
}
Component.js
Then get the token from your component using useContext
import React, {useContext, useEffect} from 'react';
import context from 'location of context';
import fetchUserData from 'location of helper function';
const Component = () => {
const userId = 123; // Not sure where you are getting this, but for example.
const {getToken} = useContext(context);
useEffect(() => {
fetchUserData(userId, getToken());
}, []);
return (JSX)
};
Although, I don't think this is the best approach. I've done this before and it makes testing a nightmare. Creating a customHook would be a better approach imo.
Related
in my react native app I have started playing with custom hooks, I created a hook to retrive user coordinates, however when my useGeolocation hook is called (inside handleUpdateLocation method) I always get the following warning:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
Actually, all my components are functional components...
Here is my component:
//import * as React from 'react';
import { View, Text, StyleSheet,TouchableOpacity, Platform,PermissionsAndroid, BackHandler, ScrollView, TextInput, ActivityIndicator ,SafeAreaView} from 'react-native';
import React, { useState, useEffect } from 'react'
import { observer } from 'mobx-react'
import useGeolocation from '#hooks/useGeolocation.js';
export default OrderCard14 = observer((props) =>
{
const handleUpdateLocation = async (gender) =>
{
const { coordinates, city } = useGeolocation(true);
};
return(
<View style={{ flex:1,backgroundColor:'white',alignItems:'center',padding:0 }}>
</View>
);
});
And my simplified hook(removed some functions):
//import AsyncStorage from '#react-native-async-storage/async-storage';
import { AsyncStorage ,Platform, Alert, PermissionsAndroid } from 'react-native';
import _ from 'lodash';
import env from '#env/vars';
import http from '#env/axiosin';
import Geolocation from 'react-native-geolocation-service';
import { useStore } from '#hooks/use-store';
const useGeolocation = (getCityName = false) => {
const root = useStore();
const [coordinates, setCoordinates] = useState(false);
const [city, setCity] = useState(false);
const requestLocationPermissions = async () =>
{
console.log('getting new coordinates');
if (Platform.OS === 'ios')
{
const auth = await Geolocation.requestAuthorization("whenInUse");
if(auth === "granted")
{
//root.mapStore.setLocationEnabled(true);
this.locationEnabled = true;
let coords = await getMe(getCityName);
return coords;
/*
const todayId = moment().isoWeekday();
if(todayId != root.userStore.user.lastLoginDayId)
{
getMe();
}
*/
}
else
{
//Alert.alert('PLEASE ENABLE LOCATION');
root.mapStore.setLocationEnabled(false);
//this.locationEnabled = false;
}
}
if (Platform.OS === 'android')
{
let granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION);
if (granted === PermissionsAndroid.RESULTS.GRANTED)
{
// do something if granted...
//this.loactionEnabled = true;
root.mapStore.setLocationEnabled(true);
let coords = await getMe();
return coords;
/*
const todayId = moment().isoWeekday();
if(todayId != root.userStore.user.lastLoginDayId)
{
getMe();
}
*/
}
else
{
//Alert.alert('KULO');
root.mapStore.setLocationEnabled(false);
//this.locationEnabled = false;
}
}
}
useEffect(() => {
requestLocationPermissions();
}, []);
console.log('returning new coordinates with hook: '+coordinates);
return { coordinates, city };
};
export default useGeolocation;
What's exactly the problem?
Invalid hook call. Hooks can only be called inside of the body of a function component.
I think they should add to the list of possible/common reasons that:
You might be conditionally invoking the hook call on any given render, or not invoking it at all.
Hooks not only need to be inside the body of a function component, they need to be at the level of the body of the function component. Always called on every render, always in the same order. You have one that's inside a function:
const handleUpdateLocation = async (gender) =>
{
const { coordinates, city } = useGeolocation(true);
};
Which is invalid. Instead, move the hook call to the component level. You can still use the resulting values inside the function:
const { coordinates, city } = useGeolocation(true);
const handleUpdateLocation = async (gender) =>
{
// here you can still use coordinates and city
};
In general, it's a good rule of thumb to invoke all of the hooks that you'll need as the very first thing you do in any given component.
I have a straightforward react component that looks so in AllWords.js :
import React, { useEffect, useState } from 'react';
import consts from '../../constants/Constants.js';
function AllWords() {
const [words, setWords] = useState([]);
async function fetchData(){
const response= await fetch(consts.FETCH_URL);
const data = await (response.json());
setWords(data);
};
// API: useEffect( () => { . . . return cleanup; },[var_n_whose_change_triggers_useEffect . . .] );
useEffect(() => {fetchData()}, [] );
return (
<>
{
words.map(w=> <div>{w.word}</div>)
}
</>
);
}
export default AllWords;
I would like to refactor the fetchData() method out of the component into another file (basically a separate .js file that holds the fetch call).
What I would like is to have created a file titled FetchAllWords.js under src/actions/ & then import it. & use that.
I have several questions :
do I need to set the state in the FetchAllWords.js and then useSelector to extract the state in AllWords.js?
in FetchAllWords.js do I need to usedispatch to dispatch a method call setting the state? I would like to just setState in FetchAllWords.js and then extract it in AllWords.js. This is what I have so far:
import consts from '../constants/Constants.js';
import { useState } from 'react';
async function FetchAllWords(){
const [words, setWords] = useState([]);
const response= await fetch(consts.FETCH_URL);
const data = await (response.json());
setWords(data);
}
export default FetchAllWords;
I am unsure how to import this and use it in AllWords.js. I am using the following statement :
import wordList from '../../actions/FetchAllWords';
Then I am trying to use wordList as a handle to the file '../../actions/FetchAllWords.js' & attempting to access the async function FetchAllWords so wordList.FetchAllWords();
Firstly , the editor (VSCode) won't let me see the function despite the import call.
Secondly I am getting an error (something like) :
TypeError: _actions_FetchAllWords_js__WEBPACK_IMPORTED_MODULE_3__.default.FetchAllWords is not a function
Any insight or help would be appreciated since rather uneasy with JS & React.
The github repo is : https://github.com/mrarthurwhite/hooks-p5-react-redux
EDIT: As per David's suggestions :
So AllWords.js React component is :
import React, { useEffect, useState } from 'react';
import wordList from '../../services/Fetch.js';
function AllWords() {
const [words, setWords] = useState([]);
function fetchData(){
wordList.fetchAllWords().then(
data => setWords(data)
);
};
// API: useEffect( () => { . . . return cleanup; },[var_n_whose_change_triggers_useEffect . . .] );
useEffect(() => {fetchData()}, [] );
return (
<>
{
words.map(w=> <div>{w.word}</div>)
}
</>
);
}
export default AllWords;
And Fetch.js is :
import consts from '../constants/Constants.js';
class Fetch {
async fetchAllWords(){
const response= await fetch(consts.FETCH_URL);
const data = await (response.json());
return data;
}
}
export default Fetch;
No, don't worry about state in the external file. Just focus on the one thing it should do, perform the AJAX operation. At its simplest it's just a function, something like:
import consts from '../../constants/Constants.js';
const fetchAllWords = async () => {
const response = await fetch(consts.FETCH_URL);
const data = await (response.json());
return data;
}
export default fetchAllWords;
You can even make it a class which contains this function, if you plan on adding other service operations as well. (Fetch specific word? Find word? etc.) The point is that this does just one thing, provide data. Let the React components handle React state.
Within the component you'd just use that to get your data. Something like:
import React, { useEffect, useState } from 'react';
import fetchAllWords from '../../services/FetchAllWords.js';
function AllWords() {
const [words, setWords] = useState([]);
useEffect(() => {
fetchAllWords().then(w => setWords(w));
}, []);
return (
<>
{
words.map(w=> <div>{w.word}</div>)
}
</>
);
}
export default AllWords;
Overall it's a matter of separating concerns. The service performs the AJAX operation and returns the meaningful data, internally concerned with things like JSON deserialization and whatnot. The React component maintains the state and renders the output, internally concerned with updating state after useEffect runs and whatnot.
how can I use UseContext to make this const '' data '' accessible throughout my project? I'm trying to implement but I can't. It doesn't have to be with UseContext, it was just a way that I researched
import api from '../../services/api';
import React, {useContext} from 'react';
export default async function getItems() {
try {
const data = await api.get("/list-results");
return data;
} catch (error) {
return error
}
}
First, create yourself a context:
const MyContext = React.createContext(defaultValue);
Then call your getItems() and store the result in a state and pass it to your context provider:
const [storedData, setStoredData] = useState<MyData | null>(null);
useEffect(() => {
getItems().then((data) => setStoredData(data));
}, []);
if (!storedData) return <div>Loading</div>;
return <MyContext.Provider value={}><MyApp/> </MyContext.Provider>;
Ideally do some more error handling in there.
And finally, get your context using useContext in a component somewhere in MyApp:
const data = useContext(MyContext);
Code is untested and should be seen as pseudo-code.
Could someone please explain what is going wrong in simple terms so I know how to fix this and can deal with it next time I encounter it.
I have looked through all related questions I could find on stackoverflow and haven't been able to fix it, if I have missed one that answers this then please link it.
I have had this error in the past but usually that was just because I had a typo (e.g. a capital instead of a lowercase) or did not import something correctly however that is not the case this time as far as I can tell.
FIRST CODE app.js
SECOND CODE interactions.js
Here is my code
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Navbar from './Navbar'
import Web3 from 'web3';
import { connect } from 'react-redux'
// import Token from '../abis/Token.json'
import {
loadWeb3,
loadAccount,
loadToken,
loadExchange
} from '../store/interactions'
class App extends Component {
componentWillMount() {
this.loadBlockchainData(this.props.dispatch)
}
async loadBlockchainData(dispatch) {
const web3 = loadWeb3(dispatch)
const network = await web3.eth.net.getNetworkType()
const networkId = await web3.eth.net.getId()
const accounts = await loadAccount(web3, dispatch) // <<--
const token = loadToken(web3, networkId, dispatch)
loadExchange(web3, networkId, dispatch)
}
// ......................
function mapStateToProps(state) {
return {
account: accountSelector(state)
}
}
export default connect(mapStateToProps)(App);
import Web3 from 'web3'
import {
web3Loaded,
web3AccountLoaded,
tokenLoaded,
exchangeLoaded
} from './actions'
import Token from '../abis/Token.json'
import Exchange from '../abis/Exchange.json'
export const loadWeb3 = (dispatch) => {
const web3 = new Web3(Web3.givenProvider || 'http://localhost:7545')
dispatch(web3Loaded(web3))
return web3
}
export const loadAccount = async (web3, dispatch) => {
const accounts = await web3.eth.getAccounts()
const account = accounts[0]
dispatch(web3AccountLoaded(account))
return account
}
export const loadToken = async (web3, networkId, dispatch) => {
try {
const token = new web3.eth.Contract(Token.abi, Token.networks[networkId].address) // new 이거 의존성(버전) 문제 이거 조심!!!!!
dispatch(tokenLoaded(token))
return token
} catch (error) {
window.alert('Contract not deployed to the current network. Please select another network with Metamask.')
return null
}
}
export const loadExchange = async (web3, networkId, dispatch) => {
try {
const exchange = new web3.eth.Contract(Exchange.abi, Exchange.networks[networkId].address)
dispatch(exchangeLoaded(exchange))
return exchange
} catch (error) {
window.alert('Contract not deployed to the current network. Please select another network with Metamask.')
return null
}
}
i don'k now why this happening to me
but please let me know this problem if you know this issue
The problem seems to be that you do not define or import the accountSelector function anywhere.
You usually define Redux selector functions in your reducer definition files: they take the current Redux store state as argument (and optionally the connected component props) and return the value to be used in your MapStateToProps object property.
Ex.
export const accountSelector = (state) => state.account
You can read more about selectors on the dedicated Redux resources page
replace this
function mapStateToProps(state) {
return {
account: accountSelector(state)
}
}
with this
function mapStateToProps(state) {
return {
account: state.accountSelector
}
}
you are passing complete state in variable instead of accessing.
for your reference, how to access please go through official documentation for your better understanding Redux
I have an AuthService class that provides all the api calls and handles authentication for those calls, so its a nice modular service. It is not a React component and not used in any render calls. It handles fetch calls mostly. In many other classes now, I use a single global instance of this class, and import it at the top.
I don't think a context is the right approach because it's not an object type or used in renders. I use the instance in componentDidMount() and useEffect().
an example:
//at the bottom, outside curly braces defining AuthService
export const Auth = new AuthService();
a consumer:
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { useState, useEffect } from 'react';
import CommentList from "./CommentList";
import CommentForm from "./CommentForm";
import Comment from "./Comment";
import AuthService from './AuthService';
import { Auth } from './AuthService';
export default function CommentBox(props) {
const [comments, setComments] = useState([]);
// const Auth = new AuthService();
const [formText, setFormText] = useState('');
const [update, setUpdate] = useState(false);
useEffect(() => {
Auth.fetch('/comments/get_comment_for_bill/' + props.id + '/').then((data) => {
if (typeof data[0] !== 'undefined') {
setComments(data);
} else {
setComments([]);
}
setUpdate(false);
});
return () => {
return;
}
}, [props, update]);
return (
<div >
<CommentList comments={comments}/>
<CommentForm id={props.id} formText={formText} setFormText={setFormText} setUpdate={setUpdate}
onChange={e => {
setFormText(e.target.value);
}} />
</div>
);
}
I think the best approach to using singletons in React is by attaching it to the window object. Just make sure you first attach the singleton to an object, which in turn is attached to the window object - to avoid polluting your window namespace. You would do this attaching in your startup script only once:
index.js
var window.app = {}
window.app.authentication = (function() {
function authenticateUser(userId) {
}
return {
authenticateUser: authenticateUser
}
})();
Then in some other module where you want to use it:
Login.js
window.app.authentication.authenticateUser("johndoe");
It's just fine. There's nothing wrong. But why use same instance for everything?
new AuthService()
I would recommend you to export AuthService. Then, whenever you'll need to use that service, define a new instance and use:
const Auth = new AuthService()
// now, use Auth
It's just a personal choice.