state inside custom hook and the main component - reactjs

Let's say I have a custom hook for calling some API and I have a loading state defined inside this custom hook.
I consume this in my main component. Now my question is can I treat the useState that I did inside the custom hook to be exactly the same, as it would have been added in my main component.
e.g. can I use this "loading" state (both getter & setter) in my main component also for say some other purpose e.g. if i have API calls using some other way as well e.g. using axios?
Thus to summarize, for mental model perspective, can I treat a state variable defined inside the custom hook as exactly the same as one in the component itself?

You can.
If you return both getters and setters, you can use it as if it was a normal "root" useState call.
A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks. (https://reactjs.org/docs/hooks-custom.html#extracting-a-custom-hook)
Example:
const usePageLoading = () => {
const [pageLoading, setPageLoading] = useState(true);
return { pageLoading, setPageLoading };
};
const MyComponent = () => {
const { pageLoading, setPageLoading } = usePageLoading();
// ...
};

Related

Passing React setState function from useState hook into a non React utility function file

Let's say I have a React functional component, FuncComponent, which contains a method like logout. However, I also have a utility method class which performs most of the primary auth login and logout API calls and logic. In my functional component, I mainly want to just call the logout method in the utility file; however, I also want to clear some state along with making the API call. I know I can just do this directly in the React component itself, but I'm a bit curious - can I pass my functional component setState method into the utility function so that all of the log out action is composed into a single function?
I tried something like this, which didn't work:
// React Component A
const [libraryState, setLibraryState] = useState(...);
...
const logoutOfLibrary = () => {
AuthHelper.logoutOfLibrary(setLibraryState, libraryState);
}
// Auth Helper - NOT a React component, just an assortment of utility functions.
const logoutOfLibary(
clearLibaryStateCallback: (libraryState: LibraryState) => void,
libraryState: LibraryState
) => {
// ...logout actions
// creating an object with cleared auth state, like so: { loggedIn: false, userId: undefined, ... }
const clearedStateObject = defaultStateObject;
clearLibraryStateCallback(clearedStateObject)
}

Why or when should I use state within a custom Hook in React?

I am learning about custom Hooks in React. I have taken the following typical example (useFetch) as you can see in https://www.w3schools.com/react/react_customhooks.asp.
import { useState, useEffect } from "react";
const useFetch = (url) => {
const [data, setData] = useState(null); /* <--- Why use this? */
useEffect(() => {
fetch(url).then((res) => res.json()).then((data) => setData(data));
}, [url]);
return [data];
};
export default useFetch;
I'm confused why state should be used inside that Hook, or in general in custom Hooks. I always relate state management to components, not to a hook. Hence perhaps my confusion.
Couldn't it have been done simply by returning a data variable?
Unlike normal functions, custom hooks encapsulates React state. This means that the hook can utilize react's own hooks and perform custom logic. It's quite abstract in that sense.
For your case, you want to return the state of the data, not just the data by itself because the state is tied to a useEffect. That means that fetch will only run and by extension only update data when its dependencies ([url]) are changed.
If it was a normal function just returning the data from the fetch, you would send a request every time the component using the hook re-renders. That's why you use useState coupled with useEffect to make sure it only updates when it should.

How to pass props from parent to grandchild component in react

I have tried pass value from parent to grandchild component, and it works. While I am thinking if there is another simpler or other way of passing props in shorter path.
What I did is quite cumbersome in codesandbox
There may be a common problem in react world called prop drilling by passing data to children only using props.
I would recommend only 2-level passing, if you need pass data deeper then you probably doing something wrong.
Use one of popular state management library (if your project is big) or React context (which is awesome)
Create a folder called /contexts and put contexts there. The structure of files can be like shown below:
First you need to create a context itself
type ClientContextState = {
data: User;
set: (data: User) => void;
logout: () => void;
};
// empty object as a default value
export const UserContext = createContext<UserContextState>({} as UserContextState);
Then create a Provider wrapper component
export const UserProvider = ({ children }: Props) => {
const [data, setData] = useState<User>({});
const sharedState = {
data,
set: setData
logout: () => setData(null)
}
return <UserContext.Provider value={sharedState}>{children}</UserContext.Provider>
});
You may also want to have an alias of useContext hook:
export const useUser = () => {
return useContext(UserContext);
};
After all this whenever you wrap your components or app to <UserProvider>...</UserProvider> you can use our hook to access data and methods form sharedState from any place you want:
export LogoutButton = () => {
const {data, logout} = useUser();
return <Button onClick={() => logout()}>Logout from {data.name}</Button>
}
Whenever you want to pass props or data from Grandparent to child component, always use react-redux. This is useful to maintain the state and access the data from anywhere/any component.
Another way is to use useContext hooks which you can use to pass the props
Following are the steps to use useContext hooks
Creating the context
The built-in factory function createContext(default) creates a context instance:
import { createContext } from 'react';
const Context = createContext('Default Value');
The factory function accepts one optional argument: the default value.
Providing the context
Context.Provider component available on the context instance is used to provide the context to its child components, no matter how deep they are.
To set the value of context use the value prop available on the
<Context.Provider value={value} />:
function Main() {
const value = 'My Context Value';
return (
<Context.Provider value={value}>
<MyComponent />
</Context.Provider>
);
}
Again, what’s important here is that all the components that’d like later to consume the context have to be wrapped inside the provider component.
If you want to change the context value, simply update the value prop.
Consuming the context: Consuming the context can be performed in 2 ways.
The first way, the one I recommend, is to use the useContext(Context) React hook:
import { useContext } from 'react';
function MyComponent() {
const value = useContext(Context);
return <span>{value}</span>;
}
Generally it's helpful to consider whether moving state down the hierarchy would be the simplest route. That means lifting the component instantiation to a place closer to the state being used. In your example, that could mean Component_data is used inside Component and passed to its children there, removing one step in the nested data flow. Even better, would be that Child.a accesses Component_data.A directly.
In a real app with cases where accessing the data directly is less feasible, a solution I lean towards is using Context to set data in the parent that retrieves it, and then I can access it however deeply nested the component might be that needs it.
i.e. in App I would create the Context provider, and in ChildA I access it via useContext hook.
Further reading
https://reactjs.org/docs/context.html
https://overreacted.io/before-you-memo/#solution-1-move-state-down (this post is about an alternative to using useMemo but has an illustrative example of why moving state down is a good thing)

Prevent passing a new function as a prop in a functional component that uses useState

I know that passing a new function every render as a prop to a react component can affect performance (for example passing an arrow function). In a class component I usually solve this problem by passing a method of the class.
But say we have a functional component that uses say useState and I want to create a closure that calls a function returned from useState. This function will be created anew every render, so whatever component it is passed to will be rerendered. Is there a way to avoid passing a new function that refers to some variable, function inside of a functional component?
import React from 'react'
import IDontWantToRerender from './heavyComputations';
export default function IneffectiveFunctionalComponent() {
const [value, setValue] = useState(1);
const valueChanger = () => {
setValue(Math.random());
}
return (
<IDontWantToRerender valueChanger={valueChanger} value={value} />
)
}
You can use the useCallback hook.
const valueChanger = useCallback(
() => {
setValue(Math.random());
},[setValue]); // you need some other dependencies for when it should change
As said in the comments
I wonder, is it really necessary to pass setValue as it will not change?
Yes, if you take a look at the docs, it says
Note
The array of dependencies is not passed as arguments to the callback. Conceptually, though, that’s what they represent: every value referenced inside the callback should also appear in the dependencies array. In the future, a sufficiently advanced compiler could create this array automatically.
We recommend using the exhaustive-deps rule as part of our eslint-plugin-react-hooks package. It warns when dependencies are specified incorrectly and suggests a fix.

Is there a way to make sure child components fire actions after parent components?

I'm having issues when I try to fire an action within the useEffect hook defined at the entry point for my application. Lower in the component tree, I'm using the useEffect hook to make API calls that use a crucial piece of data being retrieved in the entry point I described. It seems that I would not want to move the fetching of that crucial piece of data down and into every child component that needs it. Am I using an anti-pattern here?
The only other option I can think of is not calling the API until that piece of data is retrieved, but for reusability purposes this is not a good way of doing things and provides a lot of potential issues.
The component tree is as follows:
<App> <-- useEffect hook retrieves crucial piece of data
<Child /> <-- useEffect hook calls API with data
</App>
An error occurs because the route gets called without that crucial piece of data, but I have it set up to call the API again once that data is received. This is not a good solution and wastes another API call.
You have to do next.
Create context to provide information about parent initialization to child components.
const ParentInitializeContext = React.createContext(false);
Create a boolean state variable isInitialized in the parent component and set it to true when the parent component will initialize. You also have to provide isInitialized to context:
function App(props) {
const [isInitialized, setIsInitialized] = useState(false);
useEffect(() => {
//here you retreive a crucial piece of data and then call setIsInitialized(true);
}, []);
return (
<ParentInitializeContext.Provider value={isInitialized}>
{props.children}
</ParentInitializeContext.Provider>
);
}
Create own hook to call functions when parent component will initialize:
function useEffectWhenParentInitialized(fn, inputs = []) {
const isParentInitialized = useContext(ParentInitializeContext);
useEffect(() => {
if (isParentInitialized) {
fn();
}
}, [...inputs, isParentInitialized]);
}
Change useEffect hook in child components to useEffectWhenParentInitialized.
You can view a full example here: https://codesandbox.io/s/5volk3o2ol

Resources