Is it possible to use custom hooks inside getServerSideProps [duplicate] - reactjs

Is there any way for us to use the custom hook in getStaticProps(). The reason for this, is beacuse, we are using the Contentful CMS to fetch the data, from the Delivery API, and it is easier for us to have custom hooks to fetch certain data.
Now when we call, for example useHomePageData in getStaticProps() function, we get the error when trying to use simple object destructing to get the data.
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.
This is the code we have in getStaticProps()
export function getStaticProps() {
const { data, isPending, error } = useFetchPagesProps('phasePage');
const { name, title, slug, sections } = data;
return {
props: {
name,
title,
slug,
sections
}
}
}
I know what this error means, but still does someone know for example any npm library or something like that, so that we can use this custom-hook?
P.S: We are also using useEffect() inside the custom-hook so that can cause the problems I think

getStaticProps technically has nothing to do with React. In essence that is just a function with a special meaning and functionality to the Next.js framework itself. So no, you can't use hooks in there (at least not the ones that use setState useEffect etc.. inside of them.

This is against React Hooks rules and its best practices, and you have to avoid this mentality at all costs, but there are sometimes that you just want to do it wrong ;). in those circumstances you can create a kinda global variable and trick the React to think that you're obeying the rules but clearly you're not :).
the code would be something like this:
//use the hook in one the parents component which you know that would be initialized earlier
//for example maybe _app.js file
let hackTheHooksRules;
function MyApp({ Component, pageProps }) {
hackTheHooksRules = useFetchPagesProps("phasePage");
return <Component {...pageProps} />
}
export default MyApp;
export hackTheHooksRules;
and in the destination where you want to use it, you should do something like this:
import {hackTheHooksRules} from 'path-to-that-file'
export function getStaticProps() {
const { data, isPending, error } = hackTheHooksRules || {};
const { name, title, slug, sections } = data;
return {
props: {
name,
title,
slug,
sections
}
};
}
NOTE: again this is not recommended but sometimes The heart wants what it wants and who are you to blame it 😅
here is the the rules of hooks which you should try to obey them: reactjs.org/docs/hooks-rules.html

custom hooks are NOT any functions that you prefix their name with use. custom hooks are functions that use one of the react hooks. If you check the source code of any library's use functions, you will see they are using one of the react hooks.
Before we execute any react hooks, it first calls resolveDispatcher
function resolveDispatcher() {
var dispatcher = ReactCurrentDispatcher.current;
{
if (dispatcher === null) {
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:\n' + '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' + '2. You might be breaking the Rules of Hooks\n' + '3. You might have more than one copy of React in the same app\n' + 'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.');
}
} // Will result in a null access error if accessed outside render phase. We
// intentionally don't throw our own error because this is in a hot path.
// Also helps ensure this is inlined.
return dispatcher;
}
if your custom hook does not use any of react hooks, it is just a plain function.

Related

Using variables from react hooks to call another hook

I’ve seen a lot of discussion about conditionally calling hooks and why that is wrong, but is there any issue with using a variable returned by one hook to provide an action or data to another?
Example using graphql:
const form = useForm({ initialValues: { name: ‘’ }})
const { data } = useQuery(query, { onCompleted: (data) => form.setValues(data) })
Or another example using react router dom:
const { name } = useParams();
const { data } = useQuery(query, { variables: { name } })
The main reason for choosing to do this is to get rid of a useEffect if I don’t have to use one. The alternative would be to delay the call of the other hook until the query has finished by rendering it in a separate component which again, isn’t always ideal.
Are there any reasons why this is a bad idea or not best practice?
I generally prefer to keep my queries as the first hook called, but otherwise I can’t see why this would be incorrect.
There is nothing wrong with using the output of one hook as the input of another in a general sense.
For certain hooks, this can lead to potentially unintentional (but not invalid) behavior. For example, if you passed the result of a query hook as the default value of a useState, the state value would not be set to the query result value when it eventually loads. (for that you'd have to useEffect + setState). This is an example of potential developer error though, not a React issue.
As far as React is concerned, passing the output of one hook as an input to another is perfectly fine and valid.

How to use separation of concern with react-query (in a clean architecture context)

I'm currently thinking about the perfect architecture for my professionals projects needs.
I read a lot of article about (clean) architecture and I got to the point were I think that I want my UI managed with React totally separated from the application business logic that will be managed by "application manager". The issue is that I want the "application manager" to config and trigger mutations (I think get queries can be used in components without any issue). But since react-query require it to be in React component by using hooks, I don't think it is possible.
I am wrong ?
Does it exist a workaround ?
Maybe you have a library that manage that better ? I'm thinking about RTK Query maybe...
I am a heavy user of RQ for quite some time and since architecture question can never have an objectively correct answer, I can demonstrate what I do personally.
First, I extract all queries and components into API modules by domain, given a simple app with posts, authors and comments, I would have files along these lines with those exports:
// apis/posts.js
export function useGetPosts() {}
export function useGetPost(postId) {}
export function usePutPost() {}
export function usePostPost() {}
export function useDeletePost() {}
// apis/comments.js
export function useGetComments(postId) {}
export function useGetComment(commentId) {}
export function usePutComment() {}
export function usePostComment() {}
export function useDeleteComment() {}
// apis/authors.js
export function useGetAuthors() {}
export function useGetAuthor(authorId) {}
export function usePutAuthor() {}
export function usePostAuthor() {}
export function useDeleteAuthor() {}
Each of those modules would internally handle everything necessary to work as a whole, like useDeleteAuthor would have a mutation and also modify the cache on success, or possibly implement optimistic updates.
Each will have a system of query keys so that the consumer (your components) don't have to know a thing about them.
function MyComponent() {
const posts = useGetPosts()
}
function MyOtherComponent() {
const deletePost = useDeletePost()
}
Try to make the APIs as complete as possible, but also don't forget that mutations can, for example, accept callbacks on call-site:
deletePost.mutate(payload, {
onMutate: () => setState(false)
})
Let's assume you can use this to for example close a confirmation modal before deleting. Something like this doesn't belong to API module, so we just provide it as a local callback to the mutation.
As stated above, there is no correct answer. There is definitely an argument for doing it the other way round and using collocation more, putting queries next to the components where you are using them. But if you want separation, this would be a place to start in my opinion.
As Ben wrote in the comment to your question, RQ is just hooks, so I agree that trying to put it "outside of react" is non-sensical.
You're right, the short answer is react-query is not compatible with clean architecture, and by experience it leads to tight coupling between logic and components
One way that I'm experimenting with is using the queries in components as is, without implementing side effects. Unless it is side effects specifically for that components.
Then inside my logic layer, I would use the QueryObserver and subscribe to changes to whatever key/keys I need.
const observer = new QueryObserver(myQueryClient, {
queryKey: ['key']
})
observer.subscribe(result => console.log(result))
In this example I have my queryClient defined in its own file.
This way I can have my logic seperated from the view layer, but still use the awesome way react-query works.
Note that this way, the logic will only run when a component is mounted that the query function is resolved.
Also the subscibe function can only be called after the inital useQuery is mounted. Else you will get a "Missing queryFn" error. Which is not ideal. Or even close.

How to prevent refresh of list over API after drag & drop with beautiful DND?

I simulated my Context + DND problem in https://codesandbox.io/s/adoring-booth-33vqo . I have other components which will be added to this example and I will use a Context hook to share values across the page.
After the initial render, everything looks fine. The idea of the list is to change the order within itself and when ones changes the order with drag-drop, it throws an "Invalid Hook" error.
So the (first) real question is, what is triggering this error which is linked to the line
const { lang1Library, updateLang1Library } = useContext(LangContext)
;
Thanks in advance for your help.
Geo
It's not a good approach to provide a link for the whole project even if it is small. But I had a quick look and there's at least one thing you're doing wrong:
// DragEndFct.js
export default function DragEndFct(result, libName) {
const { lang1Library, updateLang1Library } = useContext(LangContext);
This is not React component, but it uses a hook - and it is wrong. Hooks have a special meaning in React and should be used properly (Rules of Hooks).
You can't use hooks inside regular functions and expect them to work. That is why you are getting that error.
So, there are many ways you can try to fix this. For instance, DragEndFct is a regular function, you can declare more arguments and pass stuff you get from context:
// you are using it in components right ?
function DragEndFct(result, libName, param3, param4) {}
// so you probably have access to the context there
// and can pass data from the context when you call it.
// something like this
onDragEnd={function (result) {
console.log();
DragEndFct(result, StaticVars.LANG1_LIBRARY_NAME, lang1Library, updateLang1Library);
}}
You could even make DragEndFct to be a React component - it can just return null (which means no UI will be rendered) but in that case you will have hooks and all other stuff there. It really depends on what you need and how you will use it.

Using only object id and lastUpdate for diff on useEffect in React

I have a large and deep object which I render using a React functional component (composed of child components). I also use Redux in the project which interacts with an API. To keep the app fast, I keep a local copy of the object in the component state and dispatch changes when occurred. The objects can also change in different parts of the app.
Each time the object changes in Redux its lastUpdated field is updated with the current time (epoch) by Redux. So instead of doing a deep (and expensive) diff on the whole object it is sufficient to compare the object id (in case the component has to display a different object) and the lastUpdated value (in case the object itself got changed).
This is what my functional component looks like:
interface Item {
id: string
lastUpdated: number
// more fields here
}
interface Props {
item : Item
}
export default function ItemPage(props: Props){
const [displayItem, setDisplayItem] = useState<Item>(props.item)
useEffect(() => {
if (props.item.id !== display.item.id && props.item.lastUpdated !== displayItem.lastUpdated){
setDisplayItem(props.item)
// ... some logic comes here
}
}, [props.item.id, props.item.lastUpdated])
return (
// ... render code here
)
}
This code cause the following error:
React Hook useEffect has missing dependencies: 'item.id' and
'item.lastUpdated'. Either include them or remove the dependency array
react-hooks/exhaustive-deps
I have disable the error with:
// eslint-disable-next-line react-hooks/exhaustive-deps
Questions:
Is it safe to to disable the error as I know the logic of my Redux (and use the more efficient diff)? Is there a better solution?
Is it safe to completely remove the useEffect array as I make the id/lastUpdated diff myself?
Note: I am not looking for the general "do not disable as it will come back to bite you ..." answer.
Looks like you need React.memo with second argument isEqual. Is equal works like shouldComponentUpdate. Read reactjs docs more about React.memo
UPDATE:
I've added codesandbox as example

is it safe to ignore react's warning about calling the useState hook conditionally when only the parameter is conditional?

I am creating a calendar date selection function component for assigning days to schedules in my React app and I wanted to be able to pre-populate calendar with the existing data so that it could be modified by the user.
This is what I have so far:
const initialOptions: { [key: string]: number[] } = {};
for (const option of Object.keys(props.options)) {
const dates = props.options[option].dates;
initialOptions[option] = dates ? dates : [];
}
const [selectedDates, setSelectedDates] = useState(initialOptions);
However, when I try and render the page, I get this:
React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return? react-hooks/rules-of-hooks
After reading through the react rules of hooks, I didn't see anything that indicated that react was depending on the value of the parameter to "associate local state with [my useState() call]". All it really said was...
As long as the order of the Hook calls is the same between renders, React can associate some local state with each of them.
So why is react complaining at me when I am calling useState() in top-level react code that is outside of any conditional statements or functions as per their own rules?
The comments on this question that basically said calls to react hooks need to be before any control structures, even if they are unrelated were what pointed me in the right direction.
The answer provided in the comments was not quite satisfactory though since I needed to process the inital value of selectedDates if an initial value was provided and have that available before I called useState() in order to pass it as a parameter.
Despite being perplexed by this and the somewhat nonsensical nature of this solution (order shouldn't matter with two barely-related pieces of code, right?), I managed to refactor my logic such that it both stopped react from complaining AND allowed me to still conditionally set the selectedDates in my react calendar component.
Here's what I ended up with:
const initialOptions: { [key: string]: number[] } = {};
Object.entries(props.options).forEach(value => {
const [id, options] = value;
if (options.dates) {
initialOptions[id] = options.dates;
}
});
const [selectedDates, setSelectedDates] = useState(initialOptions);
As someone who isn't that familiar with the internals of react, it seems that either:
the react team got something wrong when writing the ESLint plugin for the react hook rules, or
there was a functional limitation in how ESLint works that doesn't allow for a more specific/accurate check, causing the developers to go with a "better safe than sorry" approach by using a less specific check that still caught rule violations as well as edge cases like this one
So overall, my conclusion is, by replacing my for loop with a call to .forEach(), the ESLint
plugin saw my loop as a function rather than a control structure/conditional and allowed my code to pass the test and run without issue.
Now, as a self-described "junior" react developer, i'm pretty sure that tricking ESLint into not giving an error like this is not a good long-term solution. Ideally ESLint rules probably need updating to better check for improper use of conditionals, and/or the react docs should be updated with an example for how to conditionally set a hook's default value in a way that doesn't violate the rules of react hooks.
EDIT: I have created an issue for the react documentation in order to find out what a good long-term solution to this would be and get the documentation and/or ESLint plugins updated if necessary
If you ignore the warning that means that you are setting your expectations wrong on how your Component's code will be executed during renderings.
Just by looking at initialOptions, you can see that the initial value is based on incoming props. In React when the props change your Component gets re-rendered, the initialOptions is re-evaluated BUT it's NOT updated again by useState(initialOptions).
Sure you can say: "but my useState(initialOptions) is not wrapped around any condition!". While that is absolutely true, you didn't inform React that selectedDates needs to be updated between renders. It's value is still the first initial value when the Component was rendered first time.
You need to move the foreach logic into a useEffect with dependency to props.options.
Example based on your code:
const initialOptions: { [key: string]: number[] } = {};
const [selectedDates, setSelectedDates] = useState(initialOptions);
useEffect(() => {
// Here it's okay to have an optional condition!
if (!props.options.length) { return false; }
const newOptions = [];
Object.entries(props.options).forEach(value => {
const [id, options] = value;
if (options.dates) {
newOptions[id] = options.dates;
}
});
setSelectedDates(newOptions);
}, [props.options]);
I've prepared a sandbox example which demonstrates why the rule "Only Call Hooks at the Top Level - Don’t call Hooks inside loops, conditions, or nested functions." must be respected https://codesandbox.io/s/immutable-meadow-xvy50t?file=/src/Component.js <-- click the body of the page repeatedly and notice that the component doesn't update.

Resources