Export without Render function in React Native - javascript

In my React Native application, i am trying to add a component where i'll perform some config tasks but that component won't render anything. I have made the component already but when i import that on App.tsx the fucntion doesn't get called. How to import this following component properly to App.tsx. The component is given below:
var androidVersion = VersionInfo.appVersion;
var iosVersion = VersionInfo.buildVersion;
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const Config = () => {
console.log('check if get called >>',showVersionCodeStatus);
const prevAmount = usePrevious(iosVersion);
useEffect(() => {
if(prevAmount.iosVersion !== iosVersion) {
// process here
}
if(prevAmount.androidVersion !== androidVersion) {
// process here
}
}, [iosVersion,androidVersion])
// return {showVersionCodeStatus};
}
export default Config;
i'm importing the component in my App.tsx like the following:
import './config';
But it doesn't call the Config function. I have tried the following too:
import Config from './config';
That doesn't seem to work too. What am i doing wrong?

Since Config does not render anything, you should export it as a custom hook with a name such as useConfig. Subsequently you can import and use your custom hook in App.tsx, which will then run the config tasks specified in your useConfig custom hook.
useConfig.ts
var androidVersion = VersionInfo.appVersion;
var iosVersion = VersionInfo.buildVersion;
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const useConfig = () => {
console.log('check if get called >>',showVersionCodeStatus);
const prevAmount = usePrevious(iosVersion);
useEffect(() => {
if(prevAmount.iosVersion !== iosVersion) {
// process here
}
if(prevAmount.androidVersion !== androidVersion) {
// process here
}
}, [iosVersion,androidVersion])
// return {showVersionCodeStatus};
}
export default useConfig;
App.tsx
import useConfig from "./useConfig";
export default function App() {
const config = useConfig();
return (
...
);
}

Related

Importing async function error: Invalid hook call. Hooks can only be called inside of the body of a function component

All I wanna do is be able to call logic from my geolocationApi file into my react-native components whenever I want, NOT LIKE A HOOK but normal async functions, I'm using a custom hook in the geolocationApi file I'm importing though! (custom hooks handles mobx state updates)
I want to call it like this in my functional components (plain and easy):
import geolocationApi from '#utils/geolocationApi.js'
const getCoords = async () =>
{
let result = await geolocationApi().requestLocationPermissions(true);
};
My geolocationApi file where I have a bunch of functions about geolocation I don't want to crowd my components with.
#utils/geolocationApi.js
import _ from 'lodash';
import Geolocation from 'react-native-geolocation-service';
import { useStore } from '#hooks/use-store';
const geolocationApi = () => {
//Custom hook that handles mobx stores
const root = useStore();
const requestLocationPermissions = async (getCityName = false) =>
{
const auth = await Geolocation.requestAuthorization("whenInUse");
if(auth === "granted")
{
root.mapStore.setLocationEnabled(true);
let coords = await getMe(getCityName);
return coords;
}
else
{
root.mapStore.setLocationEnabled(false);
}
};
const getMe = async () =>
{
Geolocation.getCurrentPosition(
async (position) => {
let results = await onSuccess(position.coords);
return results;
},
(error) => {
console.log(error.code, error.message);
},
{ enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 }
);
};
/*const onSuccess = async () => {}*/
};
export default geolocationApi;
This can't be that hard!
If I remove export default geolocationApi and instead add export const geolocationApi at the top I get:
geolocationApi.default.requestLocationPermissions is not a function
You cannot use hooks outside React components. You can pass down the state to your function
import geolocationApi from '#utils/geolocationApi.js'
const getCoords = async (root) =>
{
let result = await geolocationApi(root).requestLocationPermissions(true);
};
Then instead of using useStore()
import _ from 'lodash';
import Geolocation from 'react-native-geolocation-service';
import { useStore } from '#hooks/use-store';
// pass the root from top
const geolocationApi = (root) => {
// your logic
return {
requestLocationPermissions,
getMe
}
}
Then somewhere in your component tree, ( an example with useEffect )
import getCoords from 'path'
const MyComp = () => {
const root = useStore();
useEffect(() => {
getCoords(root)
}, [root])
}
As you said, geolocationApi is a regular function, not a React component/hook. So, it isn't inside the React lifecycle to handle hooks inside of it.
You can use the Dependency Injection concept to fix it.
Make geolocationApi clearly dependent on your store.
const geolocationApi = (store) => {
Then you pass the store instance to it.
const getCoords = async (store) =>
{
let result = await geolocationApi(store).requestLocationPermissions(true);
};
Whoever React component calls the getCoords can pass the store to it.
//...
const root = useStore();
getCoords(root);
//...

React useEffect and React-Query useQuery issue?

I'm still new to React so forgive me if this is a silly approach to this problem.
My goal: Global error handling using a context provider and a custom hook.
The Problem: I can't remove errors without them immediately being re-added.
I display my errors via this component in the shell...
import React, { useState, useEffect } from 'react'
import Alert from '#mui/material/Alert'
import Collapse from '#mui/material/Collapse'
import { useAlertContext } from '#/context/alert-context/alert-context'
export default function AppAlert () {
const [show, setShow] = useState(false)
const alertContext = useAlertContext()
const handleClose = () => {
alertContext.remove()
setShow(false)
}
useEffect(() => {
if (alertContext.alert) {
setShow(true)
}
}, [alertContext.alert])
return (
<Collapse in={show}>
<Alert severity='error' onClose={handleClose}>
{alertContext.alert}
</Alert>
</Collapse>
)
}
I have a provider setup that also exposes a custom hook...
import React, { useState, createContext, useContext } from 'react'
const AlertContext = createContext()
const AlertProvider = ({ children }) => {
const [alert, setAlert] = useState(null)
const removeAlert = () => setAlert(null)
const addAlert = (message) => setAlert(message)
return (
<AlertContext.Provider value={{
alert,
add: addAlert,
remove: removeAlert
}}
>
{children}
</AlertContext.Provider>
)
}
const useAlertContext = () => {
return useContext(AlertContext)
}
export {
AlertProvider as default,
useAlertContext
}
And finally I have a hook setup to hit an API and call throw errors if it any occur while fetching the data. I'm purposely triggering a 404 by passing a bad API path.
import { useEffect } from 'react'
import { useQuery } from 'react-query'
import ApiV4 from '#/services/api/v4/base'
import { useAlertContext } from '#/context/alert-context/alert-context'
export const useAccess = () => {
const alertContext = useAlertContext()
const route = '/accessx'
const query = useQuery(route, async () => await ApiV4.get(route), {
retry: 0
})
useEffect(() => {
if (query.isError) {
alertContext.add(query.error.toString())
}
}, [alertContext, query.isError, query.error])
return query
}
This code seems to be the issue. Because alertContext.remove() triggers useEffect here and query.error still exists, it immediately re-adds the error to the page on remove. Removing alertContext from the array works, but it is not a real fix and linter yells.
useEffect(() => {
if (query.isError) {
alertContext.add(query.error.toString())
}
}, [alertContext, query.isError, query.error])
This is a perfectly fine approach to the problem. You've also accurately identified the problem. The solution is to create a second hook with access to the methods that will modify the context. AppAlert needs access to the data in the context, and needs to update when AlertContext.alert changes. UseAccess only needs to be able to call AlertContext.add, and that method wont change and trigger a re-render. This can be done with a second Context. You can just expose one Provider and bake the actions provider into the outer context provider.
import React, { useState, createContext, useContext } from 'react'
const AlertContext = createContext()
const AlertContextActions = createContext()
const AlertProvider = ({ children }) => {
const [alert, setAlert] = useState(null)
const removeAlert = () => setAlert(null)
const addAlert = (message) => setAlert(message)
return (
<AlertContext.Provider value={{ alert }}>
<AlertContextActions.Provider value={{ addAlert, removeAlert }}>
{children}
</AlertContextActions.Provider>
</AlertContext.Provider>
)
}
const useAlertContext = () => {
return useContext(AlertContext)
}
export {
AlertProvider as default,
useAlertContext
}
Now, where you need access to the alert you use one hook and where you need access to the actions you use the other.
// in AppAlert
import { useAlertContext, useAlertContextActions } from '#/context/alert-context/alert-context'
...
const { alert } = useAlertContext()
const { removeAlert } = useAlertContextActions()
And finally
// in useAccess
import { useAlertContextActions } from '#/context/alert-context/alert-context'
...
const { addAlert } = useAlertContextActions()
So I found a solution that seems to work for my purposes. I got a hint from this article. https://mortenbarklund.com/blog/react-architecture-provider-pattern/
Note the use of useCallback above. It ensures minimal re-renders of components using this context, as the function is guaranteed to be stable (as its memoized without dependencies).
So with this I tried the following and it solved the problem.
import React, { useState, createContext, useContext, useCallback } from 'react'
const AlertContext = createContext()
const AlertProvider = ({ children }) => {
const [alert, setAlert] = useState(null)
const removeAlert = useCallback(() => setAlert(null), [])
const addAlert = useCallback((message) => setAlert(message), [])
return (
<AlertContext.Provider value={{
alert,
add: addAlert,
remove: removeAlert
}}
>
{children}
</AlertContext.Provider>
)
}
const useAlertContext = () => {
return useContext(AlertContext)
}
export {
AlertProvider as default,
useAlertContext
}
My goal: Global error handling
One problem with the above useEffect approach is that every invocation of useAccess will run their own effects. So if you have useAccess twice on the page, and it fails, you will get two alerts, so it's not really "global".
I would encourage you to look into the global callbacks on the QueryCache in react-query. They are made for this exact use-case: To globally handle errors. Note that to use context, you would need to create the queryClient inside the Application, and make it "stable" with either useRef or useState:
function App() {
const alertContext = useAlertContext()
const [queryClient] = React.useState(() => new QueryClient({
queryCache: new QueryCache({
onError: (error) =>
alertContext.add(error.toString())
}),
}))
return (
<QueryClientProvider client={queryClient}>
<RestOfMyApp />
</QueryClientProvider>
)
}
I also have some examples in my blog.

React: Having issues with using contexts with setting the value in the same file as the context declaration

I'm fixing some prop drilling that was going on in my project. My app.js currently queries every 5 seconds for database health, as well as defines contexts for a few variables.
I'm having issues in fetching the setter from the context.
App.js (provider side):
ConnectionContext=React.createContext({})
const App = () => {
const [isConnectedtoDB,setConnectedtoDB]=useState({isConnected:{}});
const setConnected=useContext(ConnectionContext)
useInterval(() => {
const health = (await health.get()).data
if(Date.now() - health.connection.time_last_message > 10000){
setConnected({isConnected:false})
} else{
setConnected({isConnected:health.connection.alive})
},5000)
navbar.js (Consumer side):
const NavBar = () =>{
const {isConnected}=useContext(ConnectionContext)
return (
{isConnected ? &&
<p>I'm alive!</p>
})
I saw online that one solution was to wrap the setting in a UseEffect block but I'm fairly certain you can't nest UseEffect within UseInterval as UseInterval is itself defined by a UseEffect block.
useInterval is exported from another file as follows:
export const useInterval = (callback,delay) => {
const savedCallback = useRef()
useEffect(() => {
function tick() {
savedCallback.current()
}
if (delay !== null) {
let id = setInterval(tick,delay)
return () => clearInterval(id)
}
},[delay]}
}
The error I'm getting is that setConnected is not a function
You are using the context in the parent component which is expected just to provide value to the consumer
The setConnected should be your local state
const [connected, setConnected] = useState({});
You need to feed the state value from the parent(provider) to the child (consumer).
Example:
Parent.js
import React, {createContext, useState} from 'react';
export const ExampleContext = createContext();
function Parent(props){
const [isConnected, setConnected] = useState({})
useInterval(() => {
const health = (await health.get()).data;
if(Date.now() - health.connection.time_last_message > 10000){
setConnected({isConnected:false})
} else{
setConnected({isConnected:health.connection.alive})
},5000)
return (
<ExampleContext.Provider value={{ isConnected }}>
{props.children}
</ExampleContext.Provider>
)
}
export default Parent;
Child.js
import React, {useContext} from 'react';
import {ExampleContext} from './Parent';
function Child(){
const { isConnected } = useContext(ExampleContext);
return(
<div>
{<button> Context in Action: { isConnected } </button>}
</div>
)
}
export default Child;
In your main App.js file you will render child.js as children for parent.js
<Parent>
<Child />
</Parent>

How do I make a client-side only component for GatsbyJS?

How do I create a component for Gatsby that will load on the client-side, not at build time?
I created this one and it renders with gatsby develop but not with the rendered server-side rendering
import React from 'react';
import axios from 'axios';
import adapter from 'axios-jsonp';
export default class Reputation extends React.Component<{}, { reputation?: number }> {
constructor(props) {
super(props);
this.state = {};
}
async componentDidMount() {
const response = await axios({
url: 'https://api.stackexchange.com/2.2/users/23528?&site=stackoverflow',
adapter
});
if (response.status === 200) {
const userDetails = response.data.items[0];
const reputation = userDetails.reputation;
this.setState({
reputation
});
}
}
render() {
return <span>{ this.state.reputation?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") }</span>
}
}
If you don't want the component to be bundled in the main js file at build time, use loadable-components
Install loadable-components and use it as a wrapper for a component that wants to use a client-side only package. docs
import React, { Component } from "react";
import Loadable from "#loadable/component";
const LoadableReputation = Loadable(() =>
import("../components/Reputation")
);
const Parent = () => {
return (
<div>
<LoadableReputation />
</div>
);
};
export default Parent;
before render this component, make sure you have a window, to detect if there is a window object. I would write a hook for that:
function hasWindow() {
const [isWindow, setIsWindow] = React.useState(false);
React.useEffect(() => {
setIsWindow(true);
return ()=> setIsWindow(false);
}, []);
return isWindow;
}
In the parent component check if there is a window object:
function Parent(){
const isWindow = hasWindow();
if(isWindow){
return <Reputation />;
}
return null;
}

React useContext() performance, useContext inside custom hook

I used a structure using React Hooks. It is based on a global Context that contains a combination of reducers (as in the Redux).
Also, I widely use custom hooks to separate logic.
I have a hook that contains asynchronous API requests and it has become quite cumbersome and I have the opportunity to split almost every function of this hook into other hooks, but each of these functions uses a global context (more precisely - dispatch from useReducer()).
So, questions:
Is it ok to use useContext() in every hook who needs it?
How will it affect performance if, for example, I create 10 custom hooks that use useContext() internally and use them in the component.
Example:
providers/Store.js
import React, { createContext, useReducer } from 'react';
export const StoreContext = createContext();
export const StoreProvider = ({ children }) => {
/**
* Store that contains combined reducers.
*/
const store = useReducer(rootReducer, initialState);
return (
<StoreContext.Provider value={store}>{children}</StoreContext.Provider>
);
};
hooks/useStore.js
import { useContext } from 'react';
import { StoreContext } from '../providers';
export const useStore = () => useContext(StoreContext);
hooks/useFoo.js
import { useCallback } from 'react';
import { useStore } from './useStore';
export const useFoo = () => {
const [, dispatch] = useStore();
const doFoo = useCallback(
async params => {
dispatch(actions.request());
try {
const res = await SomeService.getSomething(params);
dispatch(actions.add(res));
dispatch(actions.success());
} catch (error) {
dispatch(actions.failure());
}
},
[dispatch]
);
return { doFoo };
};
hooks/useBar.js
import { useCallback } from 'react';
import { useStore } from './useStore';
export const useBar = () => {
const [, dispatch] = useStore();
const doBar = useCallback(
async params => {
dispatch(actions.request());
try {
const res = await SomeService.getSomething(params);
dispatch(actions.success());
dispatch(actions.add(res));
} catch (error) {
dispatch(actions.failure());
}
},
[dispatch]
);
return { doBar };
};
hooks/useNext.js
...
import { useStore } from './useStore';
export const useNext = () => {
const [, dispatch] = useStore();
...
};
components/SomeComponent.js
const SomeComponent = () => {
// use context
const [store, dispatch] = useStore();
// and here is the context
const { doFoo } = useFoo();
// and here
const { doBar } = useBar();
// and here
useNext();
return (
<>
<Button onClick={doFoo}>Foo</Button>
<Button onClick={doBar}>Bar</Button>
// the flag is also available in another component
{store.isLoading && <Spin />}
</>
)
}
Internally, hooks can reference a state queue owned by component. (Under the hood of React’s hooks system - Eytan Manor
)
useContext is just to reference a global state from the relative Context Provider. There is almost no overhead from useContext as you are concerned.

Categories

Resources