React-Mobx 2020. inject hooks and useObserver - reactjs

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)

Related

How can I dynamically compose/chain multiple 'handleSubmit' methods in React Hook Form?

I have multiple useForm hooks in my form component. I want to create a method to dynamically handle one submit button for an arbitrary number of hooks. The idea is that the partial forms should be validated successively.
For an isolated scenario, this approach works:
import React from 'react'
import { useForm } from 'react-hook-form'
const MyFormComponent = () => {
const form1 = useForm();
const form2 = useForm();
const onSubmitSuccess = () => {
//some success logic
};
const handleMultiple = form1.handleSubmit(form2.handleSubmit(onSubmitSuccess));
return <React.Fragment>
{
...form1Fields
}
{
...form2Fields
}
<button onClick={handleMultiple}>submit</button>
</React.Fragment>
}
Now I want to create a generic 'handleMultiple'. I tried to utilize the redux 'compose' function, but that approach just ignores the validation:
import { compose } from 'redux'
const getHandleMultiple = (submitFunc, ...forms) => {
const handleMultiple = compose(
...forms.map(form => form.handleSubmit),
submitFunc
);
return {
handleMultiple
}
}
Any ideas how to archive this? Different approaches are also appreciatied.
EDIT: I tried to wrap the compose-method in a 'useMemo' hook, as #AhmedI.Elsayed suggested, but that didn't solve the problem:
const useComposedFormSubmit = (submitFunc, ...forms) => {
const handleComposedSubmit = useMemo(() => {
return compose(
...forms.map(form => form.handleSubmit),
submitFunc
)
}, [forms, submitFunc])
return {
handleComposedSubmit
}
}
EDIT 2: Code sandbox:
https://codesandbox.io/s/react-hook-form-compose-handlesubmit-yes5y9
Edit 1: So handleSubmit gets called on your onSubmit and this gets repeated for every handleSubmit we have, this is basically as if we express it mathematically as:
handleSubmit2(handleSubmit1(onSubmit))(x)
but compose?
compose(...forms.map((form) => form.handleSubmit), submitFunc);
This is
handler2(handler1(submitFunc(x)))
and if we composed in opposite order
submitFunc(handler1(handler2(x)))
which is still not what we need. What we need to do is ignore compose because that's not what we need, and implement our own function.
const useComposedFormSubmit = (submitFunc, ...forms) => {
return useMemo(() => {
const fns = [...forms.map(f => f.handleSubmit)];
return fns.reduce((p, c) => {
return c(p);
}, submitFunc);
}, [submitFunc, forms]);
};
No reduce version
const useComposedFormSubmit = (submitFunc, ...forms) => {
return useMemo(() => {
const fns = [...forms.map(f => f.handleSubmit)];
let resultFunc = submitFunc;
for (const fn of fns) {
resultFunc = fn(resultFunc);
}
return resultFunc;
}, [submitFunc, forms]);
};
OLD ANSWER, NOT WORKING BUT EXPLAINS HOW TO EXPRESS THE OP POST IN compose
I think the problem is due to forms being undefined at some point, not sure but you can test this one and I'm pretty sure it should work, this is a hook. Hooks should start with use as documented.
const useMultipleSubmitters = (submitFunc, ...forms) => {
return useMemo(() => {
return compose(...forms.map(f => form.handleSubmit), submitFunc)
}, [forms, submitFunc])
}
and adjust as needed. It's the same as yours but recalculates on rerenders.

Context API values are being reset too late in the useEffect of the hook

I have a FilterContext provider and a hook useFilter in filtersContext.js:
import React, { useState, useEffect, useCallback } from 'react'
const FiltersContext = React.createContext({})
function FiltersProvider({ children }) {
const [filters, setFilters] = useState({})
return (
<FiltersContext.Provider
value={{
filters,
setFilters,
}}
>
{children}
</FiltersContext.Provider>
)
}
function useFilters(setPage) {
const context = React.useContext(FiltersContext)
if (context === undefined) {
throw new Error('useFilters must be used within a FiltersProvider')
}
const {
filters,
setFilters
} = context
useEffect(() => {
return () => {
console.log('reset the filters to an empty object')
setFilters({})
}
}, [setFilters])
{... do some additional stuff with filters if needed... not relevant }
return {
...context,
filtersForQuery: {
...filters
}
}
}
export { FiltersProvider, useFilters }
The App.js utilises the Provider as:
import React from 'react'
import { FiltersProvider } from '../filtersContext'
const App = React.memo(
({ children }) => {
...
...
return (
...
<FiltersProvider>
<RightSide flex={1} flexDirection={'column'}>
<Box flex={1}>
{children}
</Box>
</RightSide>
</FiltersProvider>
...
)
}
)
export default App
that is said, everything within FiltersProvider becomes the context of filters.
Now comes the problem description: I have selected on one page (Page1) the filter, but when I have to switch to another page (Page2), I need to flush the filters. This is done in the useFilters hook in the unmount using return in useEffect.
The problem is in the new page (Page2), during the first render I'm still getting the old values of filters, and than the GraphQL request is sent just after that. Afterwards the unmount of the hook happens and the second render of the new page (Page2) happens with set to empty object filters.
If anyone had a similar problem and had solved it?
first Page1.js:
const Page1 = () => {
....
const { filtersForQuery } = useFilters()
const { loading, error, data } = useQuery(GET_THINGS, {
variables: {
filter: filtersForQuery
}
})
....
}
second Page2.js:
const Page2 = () => {
....
const { filtersForQuery } = useFilters()
console.log('page 2')
const { loading, error, data } = useQuery(GET_THINGS, {
variables: {
filter: filtersForQuery
}
})
....
}
Printout after clicking from page 1 to page 2:
1. filters {isActive: {id: true}}
2. filters {isActive: {id: true}}
3. page 2
4. reset the filters to an empty object
5. 2 reset the filters to an empty object
6. filters {}
7. page 2
As I mentioned in the comment it might be related to the cache which I would assume you are using something like GraphQL Apollo. It has an option to disable cache for queries:
fetchPolicy: "no-cache",
By the way you can also do that reset process within the Page Two component if you want to:
const PageTwo = () => {
const context = useFilters();
useEffect(() => {
context.setFilters({});
}, [context]);
For those in struggle:
import React, { useState, useEffect, useCallback, **useRef** } from 'react'
const FiltersContext = React.createContext({})
function FiltersProvider({ children }) {
const [filters, setFilters] = useState({})
return (
<FiltersContext.Provider
value={{
filters,
setFilters,
}}
>
{children}
</FiltersContext.Provider>
)
}
function useFilters(setPage) {
const isInitialRender = useRef(true)
const context = React.useContext(FiltersContext)
if (context === undefined) {
throw new Error('useFilters must be used within a FiltersProvider')
}
const {
filters,
setFilters
} = context
useEffect(() => {
**isInitialRender.current = false**
return () => {
console.log('reset the filters to an empty object')
setFilters({})
}
}, [setFilters])
{... do some additional stuff with filters if needed... not relevant }
return {
...context,
filtersForQuery: { // <---- here the filtersForQuery is another variable than just filters. This I have omitted in the question. I will modify it.
**...(isInitialRender.current ? {} : filters)**
}
}
}
export { FiltersProvider, useFilters }
What is done here: set the useRef bool varialbe and set it to true, as long as it is true return always an empty object, as the first render happens and/or the setFilters function updates, set the isInitialRender.current to false. such that we return updated (not empty) filter object with the hook.

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?

Why does my UseState hook keeps on failing?

I want to use UseState hook for updating data in my Table component. The data to be used in the Table component is fetched by another function which is imported paginationForDataAdded.
Its look like stackoverflow due to re-rendering.
setAllData(searchResults); will re-render the component and again make api call and repated.
right way to call API.
const [allData, setAllData] = useState([]);
useEffect(function () {
const {
searchResults,
furnishedData,
entitledData
} = paginationForDataAdded({
searchFunction: search,
collectionsData: collections
});
setAllData(searchResults);
});
Assuming paginationForDataAdded is a function that returns a Promise which resolves with an object that looks like the following:
{
searchResults: { resultarray: [...] },
furnishedData: [...],
entitledData: [...]
}
You should do the following your in component:
function App(props) {
const [allData, setAllData] = React.useState([]);
// ...
React.useEffect(() => {
paginationForDataAdded({
searchFunction: search,
collectionsData: collections,
})
.then(
({ searchResults, furnishedData, entitledData }) => {
const nextAllData = searchResults.resultarray || [];
setAllData(nextAllData);
}
)
.catch(/* handle errors appropriately */);
// an empty dependency array so that this hooks runs
// only once when the component renders for the first time
}, [])
return (
<Table
id="pop-table"
data={allData}
tableColumns={[...]}
/>
);
}
However, if paginationForDataAdded is not an asynchronous call, then you should do the following:
function App(props) {
const [allData, setAllData] = React.useState([]);
// ...
React.useEffect(() => {
const {
searchResults,
furnishedData,
entitledData,
} = paginationForDataAdded({
searchFunction: search,
collectionsData: collections
});
const nextAllData = searchResults.resultarray || [];
setAllData(nextAllData)
// an empty dependency array so that this hooks runs
// only once when the component renders for the first time
}, [])
return (
<Table
id="pop-table"
data={allData}
tableColumns={[...]}
/>
);
}
Hope this helps.

How to call useSelector inside callback

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);

Resources