How to call useSelector inside callback - reactjs

This is a follow up question to this question:
How to call useDispatch in a callback
I got a React component which needs to receive information from redux in its props. The information is taken using a custom hook.
This is the custom hook:
export function useGetData(selectorFunc)
{
return type =>
{
if(!type)
{
throw new Error("got a wrong type");
}
let myData = selectorFunc(state =>
{
let res = JSON.parse(sessionStorage.getItem(type));
if(!res )
{
res = state.myReducer.myMap.get(type);
}
return res;
});
return myData;
}
}
Based on the answer for the linked question, I tried doing something like this:
function Compo(props)
{
const getDataFunc = useGetData(useSelector);
return <MyComponent dataNeeded = { getDataFunc(NeededType).dataNeeded } />
}
but I get an error because an hook can not be called inside a callback.
How can I fix this issue?

Don't pass the selector, just use it.
Also, according to your logic, you should parse the storage key outside the selector.
export function useDataFunc() {
const myData = useSelector(state => myReducer.myMap);
const getDataFunc = type => {
const resByData = myData.get(type);
try {
// JSON.parse can throw an error!
const res = JSON.parse(sessionStorage.getItem(type));
} catch (e) {
return resByData;
}
return res ? res : resByData;
};
return getDataFunc;
}
function Compo(props) {
const getDataFunc = useDataFunc();
return <MyComponent dataNeeded={getDataFunc(NeededType).dataNeeded} />;
}

I think it should be like,
const myData = useSelector(state => state.myReducer.myMap);

Related

onChange return undefined in ReactJS

I have this component:
import "./styles.css";
import React, { useEffect, useRef, useState } from "react";
import JSONEditor from "jsoneditor";
import "jsoneditor/dist/jsoneditor.css";
const JSONReact = ({ json, mode, onChange }) => {
const ref1 = useRef(null);
const ref2 = useRef(null);
useEffect(() => {
const props = {
onChangeText: (value) => {
console.log(value, "vv");
onChange(value);
},
modes: ["code"]
};
ref1.current = new JSONEditor(ref2.current, props);
if (json) {
ref1.current.set(json);
}
return () => {
ref1.current.destroy();
};
}, []);
useEffect(() => {
if (json) {
ref1?.current.update(json);
}
}, [json]);
return <div ref={ref2} />;
};
export default function App() {
const [state, setState] = useState('{"cars": "22w-08w-23"}');
const onChange = (j) => {
console.log(j);
setState(j);
};
return (
<div className="App">
<JSONReact mode="code" onChange={onChange} json={JSON.parse(state)} />
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
When i type something inside editor i get undefined in console.log(j);, but i don't understand why. Who can help to fix it?
https://codesandbox.io/s/admiring-khorana-pdem1l?file=/src/App.js:909-929
Well it is expected as in the docs it clearly says "This callback does not pass the changed contents", over here https://github.com/josdejong/jsoneditor/blob/master/docs/api.md#:~:text=This%20callback%20does%20not%20pass%20the%20changed%20contents
However, There are two ways to do it, that I found from here
https://github.com/josdejong/jsoneditor/blob/master/docs/api.md#configuration-options
First Way: use getText() from JSONEditor object to get the current value, since it will return you string so just parse it in json and pass inside your callback (onChange) prop.
JavaScript
const props = {
onChange: () => {
onChange(JSON.parse(ref1.current.getText()));
},
modes: ["code"]
};
ref1.current = new JSONEditor(ref2.current, props);
Second way: just use onChangeText and it will give the string in the callback,
const props = {
onChangeText: (value) => {
onChange(JSON.parse(value));
},
modes: ["code"]
};
ref1.current = new JSONEditor(ref2.current, props);
EDIT 1: added JSON validation check before calling onChange Props
const isValidJSON = (jsonString) => {
try {
JSON.parse(jsonString);
} catch (e) {
return false;
}
return true;
};
useEffect(() => {
const props = {
onChangeText: (value) => {
isValidJSON(value) && onChange(JSON.parse(value));
},
modes: ["code"]
};
ref1.current = new JSONEditor(ref2.current, props);
EDIT 2: adding error validation using validate(), here in this case we can remove isValidJSON(value) check from below code since we are already checking for JSON errors using validate().
onChangeText: (value) => {
const errors = ref1.current.validate();
errors.then((err) => {
if (!err.length) {
isValidJSON(value) && onChange(JSON.parse(value));
} else {
console.log(err);
}
});
}
I think it is a normal behaviour of JSONEditor, according to docs:
{function} onChange()
Set a callback function triggered when the contents of the JSONEditor
change. This callback does not pass the changed contents, use get() or
getText() for that. Note that get() can throw an exception in mode
text, code, or preview, when the editor contains invalid JSON. Will
only be triggered on changes made by the user, not in case of
programmatic changes via the functions set, setText, update, or
updateText. See also callback functions onChangeJSON(json) and
onChangeText(jsonString). `

React stale state useCallback

I am using the geolocation API inside my functional component, and my success and error callbacks for geolocation.watchPosition(success, error) have conditional code that is dependent on some state value that is going to change.
I do not want to initiate geolocation.watchPosition on mount inside useEffect.
I tried wrapping the callbacks with useCallback and passing the state value to the dependency array, but the callback still closes over the stale state value even though it is updated. Is there any workaround to this?
Simplified example:
function Map() {
const watchRef = useRef();
const [isBool, setIsBool] = useState(false);
function init() {
watchRef.current = navigator.geolocation.watchPosition(success, error);
}
const success = useCallback((pos) => {
if(isBool) {
// do something...
return;
}
// do something else...
}, [isBool])
function updateStateHandler() {
setIsBool(true);
}
return // jsx...
}
Figured out a solution using refs to break out of the closure.
Solution:
function Map() {
const watchRef = useRef();
const isBoolRef = useRef(); // create a ref
const [isBool, setIsBool] = useState(false);
function init() {
watchRef.current = navigator.geolocation.watchPosition(success, error);
}
// no need for useCallback
const success = (pos) => {
if(isBoolRef.current) {
// do something...
return;
}
// do something else...
}
function updateStateHandler() {
setIsBool(true);
}
useEffect(() => {
isBoolRef.current = isBool; // this will return the updated value inside the callback
}, [isBool])
return // jsx...
}

Memoize return value of custom hook

I have a custom React hook something like this:
export default function useLocations(locationsToMatch) {
const state = useAnotherHookToGetStateFromStore();
const { allStores } = state.locations;
const allLocations = {};
allStores.forEach((store) => {
const { locationId, locationType } = store;
const isLocationPresent = locationsToMatch.indexOf(locationId) !== -1;
if (isLocationPresent && locationType === 'someValue') {
allLocations[locationId] = true;
} else {
allLocations[locationId] = false;
}
});
return allLocations;
}
When I use above hook inside my React component like this:
const locations = useLocations([908, 203, 678]) // pass location ids
I get a max call depth error due to infinite rendering. This is because I have some code inside my component which uses useEffect hook like this:
useEffect(() => { // some code to re-render component on change of locations
}, [locations])
So I tried to wrap my return value in useLocations hook inside a useMemo like this:
export default function useLocations(locationsToMatch) {
const state = useAnotherHookToGetStateFromStore();
const { allStores } = state.locations;
const allLocations = {};
const getStores = () => {
allStores.forEach((store) => {
const { locationId, locationType } = store;
const isLocationPresent = locationsToMatch.indexOf(locationId) !== -1;
if (isLocationPresent && locationType === 'someValue') {
allLocations[locationId] = true;
} else {
allLocations[locationId] = false;
}
});
return allLocations;
};
return useMemo(() => getStores(), [locationsToMatch, state]);
}
But this still causes infinite re-rendering of the consuming component. So how can I return a memoized value from my custom hook useLocations to prevent infinite re-rendering?

No update component react hooks context

The function "AdicionaItem" not reload the map of component "Items.tsx" why?
<Itens.tsx>
const { transacao } = useContext(TransacaoContext);
return (
transacao.itens.map(({descricao}: TransacaoItens) => (<h1>{descricao}</h1>)
);
<TransacaoContext.tsx>
const [transacao, setTransacao] = useState<Transacao>(transacaoInicial);
function AdicionaItem(item: TransacaoItens) {
let novosValores = transacao;
novosValores.itens = [...novosValores.itens, item];
setTransacao(novosValores);
}
<Consulta.tsx>
const { AdicionaItem } = useContext(TransacaoContext);
function Adiciona(){
AdicionaItem({descricao: "Teste"});
};
Your useState hook is outside of your functional component. Try moving it inside of AdicionaItem:
function AdicionaItem(item: TransacaoItens) {
const [transacao, setTransacao] = useState<Transacao>(transacaoInicial);
let novosValores = transacao;
novosValores.itens = [...novosValores.itens, item];
setTransacao(novosValores);
}

React-Mobx 2020. inject hooks and useObserver

When I get the data (view) from useStore, I have to write all the way to this (view: myStore.menu.view) and still wrap it all in useObserver. Is there a way to shorten the code, but still keep the logic the same? I use Mobx and React Hooks.
Thanks in advance!
function useBasketStore() {
const { myStore } = useStore(['exampleStore']);
return useObserver(() => ({
view: myStore.menu?.view,
}));
}
const BasketScreen = () => {
const { view } = useBasketStore();
......
}
I think no way. Only the case if your component wrapped by observer. Then you can just use data:
function useBasketStore() {
const { myStore } = useStore(['exampleStore']);
return {
view: myStore.menu?.view,
};
}
const BasketScreen = () => {
const { view } = useBasketStore();
......
}
export default observer(BasketScreen)

Resources