React - Update custom hook's ref in a component - javascript

I have implemented the following custom hook:
export default function useCamera() {
const [cameraRatio, setCameraRatio] = useState(Camera.Constants.DesiredRatio);
const [cameraType, setCameraType] = useState(Camera.Constants.Type.back);
const [flashMode, setFlashMode] = useState(
Camera.Constants.FlashMode.off
);
const camRef = useRef(null); <----- Interesting part
const cameraTypeRef = useRef(cameraType);
const isTakingPhoto = useRef(false);
const takePhoto = () => {
if(!camRef.current) return; <----- Interesting part
...
}
const flip = () => {}
const toggleFlash = () => {}
...
return {
camRef,
cameraRatio,
cameraType,
flashMode,
takePhoto,
flip,
toggleFlashMode,
...
}
}
So, in my Camera component, I just do:
function Camera({ ... }) {
const {
camRef, <---- Interesting part
takePhoto,
...
} = useCamera();
return <NativeCamera ref={camRef} onTakePhoto={takePhoto} ... />
}
As you can see, I am passing the camRef to "NativeCamera". This ref is defined in my custom hook "useCamera", and received in my "Camera" component, which just pass the ref to the child.
But this doesn't work. Any workaround?

Related

React recoil, overlay with callback and try finally

I want to render overlay on the long running operations.
Consider I have the following code
let spinnerState = useRecoilValue(overlayState);
return <BrowserRouter>
<Spin indicator={<LoadingOutlined />} spinning={spinnerState.shown} tip={spinnerState.content}>.........</BrowserRouter>
What I do in different components
const [, setOverlayState] = useRecoilState(overlayState);
const onButtonWithLongRunningOpClick = async () => {
Modal.destroyAll();
setOverlayState({
shown: true,
content: text
});
try {
await myApi.post({something});
} finally {
setOverlayState(overlayStateDefault);
}
}
How can I refactor this to use such construction that I have in this onbuttonclick callback? I tried to move it to the separate function, but you cannot use hooks outside of react component. It's frustrating for me to write these try ... finally every time. What I basically want is something like
await withOverlay(async () => await myApi.post({something}), 'Text to show during overlay');
Solution
Write a custom hook that includes both UI and API. This pattern is widely used in a large app but I couldn't find the name yet.
// lib/buttonUi.js
const useOverlay = () => {
const [loading, setLoading] = useState(false);
return {loading, setLoading, spinnerShow: loading };
}
export const useButton = () => {
const overlay = useOverlay();
const someOp = async () => {
overlay.setLoading(true);
await doSomeOp();
/* ... */
overlay.setLoading(false);
}
return {someOp, ...overlay}
}
// components/ButtonComponent.jsx
import { useButton } from 'lib/buttonUi';
const ButtonComponent = () => {
const {spinnerShow, someOp} = useButton();
return <button onClick={someOp}>
<Spinner show={spinnerShow} />
</button>
}
export default ButtonComponent;
create a custom hook that handles the logic for showing and hiding the overlay.
import { useRecoilState } from 'recoil';
const useOverlay = () => {
const [, setOverlayState] = useRecoilState(overlayState);
const withOverlay = async (fn: () => Promise<void>, content: string) => {
setOverlayState({ shown: true, content });
try {
await fn();
} finally {
setOverlayState(overlayStateDefault);
}
};
return withOverlay;
};
You can then use the useOverlay hook in your components
import { useOverlay } from './useOverlay';
const Component = () => {
const withOverlay = useOverlay();
const onButtonWithLongRunningOpClick = async () => {
await withOverlay(async () => await myApi.post({ something }), 'Text to show during overlay');
};
return <button onClick={onButtonWithLongRunningOpClick}>Click me</button>;
};

Using React Context with AsyncStorage using hooks

My goal is to use custom hooks created from Context to pass and modify stored values
The final goal is to use something like useFeedContext() to get or modify the context values
What I am actually getting is either the functions that I call are undefined or some other problem ( I tried multiple approaches)
I tried following this video basics of react context in conjunction with this thread How to change Context value while using React Hook of useContext but I am clearly getting something wrong.
Here is what I tried :
return part of App.js
<FeedProvider mf={/* what do i put here */}>
<Navigation>
<HomeScreen />
<ParsedFeed />
<FavScreen />
</Navigation>
</FeedProvider>
Main provider logic
import React, { useState, useEffect, useContext, useCallback } from "react";
import AsyncStorage from "#react-native-async-storage/async-storage";
const FeedContext = React.createContext();
const defaultFeed = [];
const getData = async (keyName) => {
try {
const jsonValue = await AsyncStorage.getItem(keyName);
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch (e) {
console.log(e);
}
};
const storeData = async (value, keyName) => {
console.log(value, keyName);
try {
const jsonValue = JSON.stringify(value);
await AsyncStorage.setItem(keyName, jsonValue);
} catch (e) {
console.log(e);
}
};
export const FeedProvider = ({ children, mf }) => {
const [mainFeed, setMainFeed] = useState(mf || defaultFeed);
const [feedLoaded, setFeedLoaded] = useState(false);
let load = async () => {
let temp = await AsyncStorage.getItem("mainUserFeed");
temp != null
? getData("mainUserFeed").then((loadedFeed) => setMainFeed(loadedFeed))
: setMainFeed(defaultFeed);
setFeedLoaded(true);
};
useEffect(() => {
load();
}, []);
useCallback(async () => {
if (!feedLoaded) {
return await load();
}
}, [mainFeed]);
const setFeed = (obj) => {
setMainFeed(obj);
storeData(mainFeed, "mainUserFeed");
};
return (
<FeedContext.Provider value={{ getFeed: mainFeed, setFeed }}>
{children}
</FeedContext.Provider>
);
};
//export const FeedConsumer = FeedContext.Consumer;
export default FeedContext;
The custom hook
import { useContext } from "react";
import FeedContext from "./feedProviderContext";
export default function useFeedContext() {
const context = useContext(FeedContext);
return context;
}
What I would hope for is the ability to call the useFeedContext hook anywhere in the app after import like:
let myhook = useFeedContext()
console.log(myhook.getFeed) /// returns the context of the mainFeed from the provider
myhook.setFeed([{test:1},{test:2}]) /// would update the mainFeed from the provider so that mainFeed is set to the passed array with two objects.
I hope this all makes sense, I have spend way longer that I am comfortable to admit so any help is much appreciated.
If you want to keep using your useFeedContext function, I suggest to move it into the your 'Provider Logic' or I'd call it as 'FeedContext.tsx'
FeedContext.tsx
const FeedContext = createContext({});
export const useFeedContext = () => {
return useContext(FeedContext);
}
export const AuthProvider = ({children}) => {
const [mainFeed, setMainFeed] = useState(mf || defaultFeed);
...
return (
<FeedContext.Provider value={{mainFeed, setMainFeed}}>
{children}
</FeedContext.Provider>
);
};
YourScreen.tsx
const YourScreen = () => {
const {mainFeed, setMainFeed} = useFeedContext();
useEffect(() => {
// You have to wait until mainFeed is defined, because it's asynchronous.
if (!mainFeed || !mainFeed.length) {
return;
}
// Do something here
...
}, [mainFeed]);
...
return (
...
);
};
export default YourScreen;

React Native HOC component

I am trying to do a withCache HoC component but having some problems...
Thats the HoC:
// HOC for cached images
const withCache = (Component) => {
const Wrapped = (props) => {
console.log(props);
const [uri, setUri] = useState(null);
useEffect(() => {
(async () => {
const { uri } = props;
const name = shorthash.unique(uri);
const path = `${FileSystem.cacheDirectory}${name}`;
const image = await FileSystem.getInfoAsync(path);
if (image.exists) {
console.log("Read image from cache");
setUri(image.uri);
return;
} else {
console.log("Downloading image to cache");
const newImage = await FileSystem.downloadAsync(uri, path);
setUri(newImage.uri);
}
})();
}, []);
return <Component {...props} uri={uri} />;
};
Wrapped.propTypes = Component.propTypes;
return Wrapped;
};
export default withCache;
The thing is that "Component" is a custom Image component with specific propTypes and defaultProps.
How do I use this component? I have tried:
const CachedImage = withCache(<MyCustomImage uri={"https://..."} height={100} ripple />)
...
return (<CachedImage />)
but not working :( What I want is to pass a boolean prop to my custom image component named "cached", and if true return the custom image component wrapped in the HOC
In order to use the HOC, you would create the instance outside of the functional component like
const CachedImage = withCache(MyCustomImage)
and use it like
const MyComp = () => {
...
return (<CachedImage uri={"https://..."} height={100} ripple />)
}
Final implementation of the HoC, in case someone finds it useful in the future.
import React, { useState, useEffect } from "react";
import shorthash from "shorthash";
import * as FileSystem from "expo-file-system";
// HOC for cached images
const withCache = (Component) => {
const Wrapped = (props) => {
console.log(props);
const [uri, setUri] = useState(null);
useEffect(() => {
(async () => {
const { uri } = props;
const name = shorthash.unique(uri);
const path = `${FileSystem.cacheDirectory}${name}`;
const image = await FileSystem.getInfoAsync(path);
if (image.exists) {
console.log("Read image from cache");
setUri(image.uri);
return;
} else {
console.log("Downloading image to cache");
const newImage = await FileSystem.downloadAsync(uri, path);
setUri(newImage.uri);
}
})();
}, []);
// Needs to have the final uri before render the image
return uri && <Component {...props} uri={uri} />;
};
Wrapped.propTypes = Component.propTypes;
return Wrapped;
};
export default withCache;

How to prevent a state update on a react onClick function (outside useEffect)?

When I use useEffect I can prevent the state update of an unmounted component by nullifying a variable like this
useEffect(() => {
const alive = {state: true}
//...
if (!alive.state) return
//...
return () => (alive.state = false)
}
But how to do this when I'm on a function called in a button click (and outside useEffect)?
For example, this code doesn't work
export const MyComp = () => {
const alive = { state: true}
useEffect(() => {
return () => (alive.state = false)
}
const onClickThat = async () => {
const response = await letsbehere5seconds()
if (!alive.state) return
setSomeState('hey')
// warning, because alive.state is true here,
// ... not the same variable that the useEffect one
}
}
or this one
export const MyComp = () => {
const alive = {}
useEffect(() => {
alive.state = true
return () => (alive.state = false)
}
const onClickThat = async () => {
const response = await letsbehere5seconds()
if (!alive.state) return // alive.state is undefined so it returns
setSomeState('hey')
}
}
When a component re-renders, it will garbage collect the variables of the current context, unless they are state-full. If you want to persist a value across renders, but don't want to trigger a re-renders when you update it, use the useRef hook.
https://reactjs.org/docs/hooks-reference.html#useref
export const MyComp = () => {
const alive = useRef(false)
useEffect(() => {
alive.current = true
return () => (alive.current = false)
}
const onClickThat = async () => {
const response = await letsbehere5seconds()
if (!alive.current) return
setSomeState('hey')
}
}

ReactJS - getting data from DB and returning promise results from .then back to return function

TLDR; Basically I'm trying to figure out how to get data from my api into my React app on load so my app has it available right away. I'm totally fine with redoing or reworking any or all of my code below if need be. I just need to figure out how to do this. I think it needs to go into the context so that all my components have access to it.
I have created a ReactJS app with hard-coded dummy data and it works great. Now we need to convert it to pulling in real dynamic data from the database and am having trouble with understanding how promises work with state and useEffect. It gets so confusing. I am creating the contextValue object in my final .then call and I need that at the end of my file in the return. I tried saving it to a state variable, but that doesn't seem to be working. I am getting an error TypeError: _useContext is null in a different file.
Here is the entire context file with any irrelevant code redacted:
import PropTypes from 'prop-types';
export const ScheduleContext = createContext();
export const ScheduleProvider2 = (props) => {
const allChemicals = [...];
const allBOMs = [...];
const makeRandomStr = (length) => {...};
const getRandomChemicals = () => {};
const getRandomBOMs = () => {..};
const getRandomIntInclusive = (min, max) => {...};
const randomDate = (start, end, startHour, endHour) => {...};
const quotes = [...];
const getRandomComments = () => {..};
const getBatchNumbers = () => {...};
const [context, setContext] = useState(null);
const [orders, setOrders] = useState(null);
const [filteredOrders, setFilteredOrders] = useState(null);
const [pendingOrderIDs, setPendingOrderIDs] = useState(null);
const [activeOrder, setActiveOrder] = useState(0);
const [columns, setColumns] = useState([]);
const [showDetails, setShowDetails] = useState(false);
const [title, setTitle] = useState('Title');
const [lineType, setLineType] = useState('Type');
const chemicals = useState(allChemicals);
const BOMs = useState(allBOMs);
useEffect(() => {
const fetchPendingOrders = () => {
const ordersURL = 'http://localhost:3001/orders';
return fetch(ordersURL, {
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
headers: {
'Content-Type': 'application/json'
},
referrerPolicy: 'no-referrer' // no-referrer, *client
});
};
fetchPendingOrders()
.then((result) => {
return result.json();
})
.then((data) => {
const tempOrders = data.map((el, index) => {
return {
id: index,
.....
};
});
setOrders(tempOrders);
setFilteredOrders(tempOrders);
const pendingOrderIDVals = tempOrders.map(function(val) {
return val.id;
});
setPendingOrderIDs(pendingOrderIDVals);
const contextValue = {
orders,
setOrders,
filteredOrders,
setFilteredOrders,
pendingOrderIDs,
setPendingOrderIDs,
columns,
setColumns,
showDetails,
setShowDetails,
activeOrder,
setActiveOrder,
title,
setTitle,
lineType,
setLineType,
chemicals,
BOMs
};
setContext(contextValue);
console.log(contextValue);
})
.catch((e) => {
console.log(e);
});
}, []);
return (
<ScheduleContext.Provider value={context}>
{props.children}
</ScheduleContext.Provider>
);
};
That error TypeError: _useContext is null is happening in a functional component in a file that reads in the context file. These are the two relevant lines(This is the beginning of the SchedulePage.js where the error is happening):
import { ScheduleContext } from '../../schedule-context-new';
const Schedule = () => {
const {
showDetails,
orders,
setOrders,
activeOrder,
columns,
setColumns,
title,
pendingOrderIDs,
filteredOrders,
setTitle,
lineType,
setLineType
} = useContext(ScheduleContext);
....
I'm also using the ScheduleProvider in my app.js if that makes a difference:
import { ScheduleProvider2 } from './schedule-context-new';
import Schedule from './components/home/SchedulePage';
import './App.scss';
const App = () => {
return (
<ScheduleProvider2>
<div className={'App'}>
<Schedule />
</div>
</ScheduleProvider2>
);
};
Update:
Per the example link, I tried changing the end of my context file to this and now it is complaining about Error: ScheduleProvider2(...): Nothing was returned from render.
(async function() {
const context = await fetchPendingOrders();
return (
<ScheduleContext.Provider value={context}>
{props.children}
</ScheduleContext.Provider>
);
})();
};
Update 2 - I've figured out how to create a basic barebones replica of my app that actually produces the same error. Once again for some reason, the useEffect code wasn't getting called until I commented out useEffect, I don't know why.
https://codesandbox.io/s/tender-cookies-jzn5v
You should waite for API result
const Schedule = () => {
const {
orders,
setOrders,
filteredOrders,
setFilteredOrders,
pendingOrderIDs,
setPendingOrderIDs
} = useContext(ScheduleContext) ||[];
Or use useEffect
const data = useContext(ScheduleContext);
useEffect(() => {
// Do you calculation here when ScheduleContext is ready
}, [data])
codesandbox

Categories

Resources