Can I replace context with hooks? - reactjs

Is there a way with new react hooks API to replace a context data fetch?
If you need to load user profile and use it almost everywhere, first you create context and export it:
export const ProfileContext = React.createContext()
Then you import in top component, load data and use provider, like this:
import { ProfileContext } from 'src/shared/ProfileContext'
<ProfileContext.Provider
value={{ profile: profile, reloadProfile: reloadProfile }}
>
<Site />
</ProfileContext.Provider>
Then in some other components you import profile data like this:
import { ProfileContext } from 'src/shared/ProfileContext'
const context = useContext(profile);
But there is a way to export some function with hooks that will have state and share profile with any component that want to get data?

React provides a useContext hook to make use of Context, which has a signature like
const context = useContext(Context);
useContext accepts a context object (the value returned from
React.createContext) and returns the current context value, as given
by the nearest context provider for the given context.
When the provider updates, this Hook will trigger a rerender with the
latest context value.
You can make use of it in your component like
import { ProfileContext } from 'src/shared/ProfileContext'
const Site = () => {
const context = useContext(ProfileContext);
// make use of context values here
}
However if you want to make use of the same context in every component and don't want to import the ProfileContext everywhere you could simply write a custom hook like
import { ProfileContext } from 'src/shared/ProfileContext'
const useProfileContext = () => {
const context = useContext(ProfileContext);
return context;
}
and use it in the components like
const Site = () => {
const context = useProfileContext();
}
However as far a creating a hook which shares data among different component is concerned, Hooks have an instance of the data for them self and don'tshare it unless you make use of Context;

updated:
My previous answer was - You can use custom-hooks with useState for that purpose, but it was wrong because of this fact:
Do two components using the same Hook share state? No. Custom Hooks are a mechanism to reuse stateful logic (such as setting up a subscription and remembering the current value), but every time you use a custom Hook, all state and effects inside of it are fully isolated.
The right answer how to do it with useContext() provided #ShubhamKhatri

Now i use it like this.
Contexts.js - all context export from one place
export { ClickEventContextProvider,ClickEventContext} from '../contexts/ClickEventContext'
export { PopupContextProvider, PopupContext } from '../contexts/PopupContext'
export { ThemeContextProvider, ThemeContext } from '../contexts/ThemeContext'
export { ProfileContextProvider, ProfileContext } from '../contexts/ProfileContext'
export { WindowSizeContextProvider, WindowSizeContext } from '../contexts/WindowSizeContext'
ClickEventContext.js - one of context examples:
import React, { useState, useEffect } from 'react'
export const ClickEventContext = React.createContext(null)
export const ClickEventContextProvider = props => {
const [clickEvent, clickEventSet] = useState(false)
const handleClick = e => clickEventSet(e)
useEffect(() => {
window.addEventListener('click', handleClick)
return () => {
window.removeEventListener('click', handleClick)
}
}, [])
return (
<ClickEventContext.Provider value={{ clickEvent }}>
{props.children}
</ClickEventContext.Provider>
)
}
import and use:
import React, { useContext, useEffect } from 'react'
import { ClickEventContext } from 'shared/Contexts'
export function Modal({ show, children }) {
const { clickEvent } = useContext(ClickEventContext)
useEffect(() => {
console.log(clickEvent.target)
}, [clickEvent])
return <DivModal show={show}>{children}</DivModal>
}

Related

Store hooks in React Context API as dependency injection. Is it a good pattern?

I'm working on shared libraries for multiple projects. And I need to have a flexible way of using hooks not depending on implementation. So I'm using React Context API as a dependency injection pattern.
Simplified example:
const DIContext = React.createContext();
// multiple implementations of common hook useConfig
const useReduxConfig = () => useSelector(getConfig);
const useStaticConfig = () => ({foo: 'bar'})
const useFetchConfig = () => ... //ie fetch config for server
// this will be always static value
const DIValue={
useConfig: useReduxConfig // ca be any of implementation
}
const App = () => {
return <DiContext.Provider value={DIValue}><SharedComponent /></DiContext.Provider>
}
const SharedComponent = () => {
// shared component does not care about the implementation of hook
const {useConfig} = useContext(DiContext)
const config = useConfig();
return <div>{JSON.stringify(config)}</div>
}
What do you think, is it a good pattern ?
This question is opinion-based, here is my view.
I think DI overuses react context, see use cases for context.
I would not pass down some kind of common react custom hooks to descendants component using react context. Make things complicated.
Illustration from a testing perspective:
Yeah, the DI pattern is good for testing, you can create mock objects with the same interface to replace the real object injected. But this doesn't apply to react custom hooks, we'd better not mock custom hooks, most custom hooks will rely on React built-in hooks, such as useEffect, useState, and mocking custom hooks that use these built-in hooks will result in the incorrect lifecycle and state update functionality. When testing, you still need to pass the real custom hooks to the context value rather than create mocks for these hooks. DI doesn't make testing easier as many articles said in this case.
E.g.
index.tsx:
import React, { useContext } from 'react';
import { useEffect } from 'react';
export const DIContext = React.createContext<any>({});
const useConfig = () => ({ foo: 'bar' });
// You should NOT mock useLifecycle, we need to use the real useEffect to perform component lifecycle.
const useLifecycle = () => {
useEffect(() => {
console.log('useLifecycle mount');
}, []);
};
export const DIValue = {
useConfig,
useLifecycle,
};
export const App = () => {
return (
<DIContext.Provider value={DIValue}>
<SharedComponent />
</DIContext.Provider>
);
};
export const SharedComponent = () => {
const { useLifecycle, useConfig } = useContext(DIContext);
const config = useConfig();
console.count('SharedComponent render');
useLifecycle();
useEffect(() => {
console.log('SharedComponent mount');
}, []);
return <div>{JSON.stringify(config)}</div>;
};
index.test.tsx:
import { render } from '#testing-library/react';
import React from 'react';
import { DIContext, DIValue, SharedComponent } from '.';
describe('DI via react context', () => {
test('should pass', () => {
render(
<DIContext.Provider value={DIValue}>
<SharedComponent />
</DIContext.Provider>,
);
});
});
For every component that uses context, you need to provide the same context and the real DIValue, NOT mock DIValue. If I use import rather than context, I don't need the context provider.
Although DI reduced the code for importing custom hooks in each component, the code for the context provider was added to the test code. Of course, you can create a test util function named renderWithDIContext to DRY.
There is another scenario:
If the react custom hook is used by only one component, not a common one, the file location of such a custom hook is usually next to the component file, and the component is imported directly through import, it's quick and easy.
E.g.
SharedComponent.tsx:
import { useA } from './hooks';
import { useContext } from 'react';
const SharedComponent = () => {
const a = useA();
const { useConfig } = useContext(DiContext);
const config = useConfig();
//...
}
The useA hook is only used by one component, so there is no need to import the hook into the context provider file and then put it in the context value to pass down. And, other components don't need this hook.
The most you can do are to put some common hooks in the context value.
This scenario mixes the custom hook used by the component itself with the common hook passed down through the context.
Keep it simple. Just import and use it.
import { useA } from './hooks';
import { useConfig } from '#/common/hooks';
const SharedComponent = () => {
const a = useA();
const config = useConfig();
//...
}
Besides, putting every common hook in context value and passing them down violate interface-segregation principles. The context interface is very large, but each component only wants to know about the hooks that are of interest to them, such shrunken interfaces are also called role interfaces. Of course, you can split the large context into smaller contexts. It will increase code and the need to define these role interfaces. We also need to decide how to compose these context providers with our components. Put all context providers in the root App component(application level) or page component(page-level) or module level. This adds complexity.

Invalid hook call error when showing/hiding component with React Redux

I'm attempting to create a React/Redux component that shows/hides an element when clicked.
I'm using this to trigger the function from another component:
import React from 'react'
//Some other code...
import { useSelector } from 'react-redux'
import onShowHelpClicked from '../help/AddHelpSelector'
<button onClick={onShowHelpClicked}>Help</button>
This this is AddHelpSelector:
import { useState } from 'react'
import { useDispatch } from 'react-redux'
import { helpVisible } from './HelpSlice'
export const AddHelp = () => {
const [isVisible, showHelp] = useState('')
const dispatch = useDispatch()
const onShowHelpClicked = () => {
dispatch(
helpVisible({
isVisible,
})
)
if (isVisible) {
showHelp(false)
} else {
showHelp(true)
}
}
return (
<section>
<h2 style={{ visibility: { isVisible } }}>Help section</h2>
</section>
)
}
export default AddHelp
Finally, this is HelpSlice
import { createSlice } from '#reduxjs/toolkit'
const initialState = [{ isVisible: false }]
const helpSlice = createSlice({
name: 'help',
initialState,
reducers: {
helpVisible(state, action) {
state.push(action.payload)
},
},
})
export const { helpVisible } = helpSlice.actions
export default helpSlice.reducer
I'm fairly certain I'm doing multiple things wrong, as this is my first attempt to do anything with Redux and I'm still struggling to wrap my mind around it after a week of learning.
But specifically, when clicking the help button I get this error.
"Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app"
The linked documentation provides a way to test a component to see if React is importing properly, and it's not. But I'm not really sure what I'm doing wrong.
I think it may be that I'm importing React multiple times, but if I don't then I can't use "useState."
What's the correct way to do this? I'm open to corrections on both my code as well as naming conventions. I'm using boilerplate code as a template as I try to understand this better after getting through the documentation as well as Mosh's 6 hour course which I just finished.
You're importing the < AddHelpSelector /> component here import onShowHelpClicked from '../help/AddHelpSelector', and then you try to use it as a callback handler for the button's onClick, which doesn't really make sense. I assume you actually wanted to only import the onShowHelpClicked function declared inside the < AddHelpSelector /> component (which is not really a valid way of doing it). Since you want to control the visibility using redux state, you could just grab the flag from the redux store inside the < AddHelpSelector /> component using useSelector hook. To set it, you're gonna do that in the component where your button is. For that, you just need to dispatch an action(like you already did), with the updated flag. No need for the local useState. Also, using the flag you could just conditionally render the element.
const App = () => {
const dispatch = useDispatch();
const { isVisible } = useSelector((state) => ({ isVisible: state.isVisible }));
const handleClick = () => {
dispatch(
helpVisible({
!isVisible,
})
)
}
return (<button onClick={handleClick}>Help</button>);
}
export const AddHelp = () => {
const { isVisible } = useSelector((state) => ({ isVisible: state.isVisible }));
return (
<section>
{isVisible && <h2>Help section</h2>}
</section>
)
}
export default AddHelp

Is it possible to use React Hooks in class component by using HOC(Higher Order Component)?

Can I use the functional components in class components? I am going to call a function that is extracted from a functional component in class component. But it is giving errors like the following.
Unhandled Rejection (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
So I tried to call it in the functional component but even in the functional component, I got the same error as when I call it in class component.
Functional component
import React, { useEffect } from 'react';
import { UseWalletProvider, useWallet } from 'use-wallet';
import { providers } from 'ethers';
export function App() {
useEffect(() => {
async function GetBlockId() {
const wallet = useWallet();
console.log(wallet); // =====> This is not displaying.
const { ethereum, connect } = wallet;
const ethersProvider = new providers.Web3Provider(ethereum);
const { blockNumber } = await ethersProvider.getTransaction(hash);
console.log(blockNumber);
};
GetBlockId()
}, []);
return <div>
<h1>{wallet}</h1>
</div>
}
Class component
import React, { Component } from 'react'
import { GetBlockId } from './util'; // =====>> I hope to get result from here.
import { hash } from './hash'
export default class App extends Component {
constructor(props) {
super(props)
}
componentDidMount(): void {
const blockNumber: any = GetBlockId(hash);
console.log(blockNumber);
}
render() {
return (
<div>
<h1>test</h1>
</div>
)
}
}
util.tsx
import React, { useEffect } from 'react';
import { UseWalletProvider, useWallet } from 'use-wallet';
import { providers } from 'ethers';
// import { Container } from './styles';
export function GetBlockId() {
useEffect(() => {
async function GetBlockId() {
const wallet = useWallet();
const { ethereum, connect } = wallet;
const ethersProvider = new providers.Web3Provider(ethereum);
const { blockNumber } = await ethersProvider.getTransaction(hash);
return blockNumber;
};
GetBlockId()
}, []);
}
So finally I hope to use "use-wallet" package in the class component. Is that possible? If yes, how to use useWallet hook in the class component?
React hooks are only compatible with React function components, they can't be used in class components at all. The issue with your first attempt is that you are trying to call a React hook in a callback, which breaks one of the Rules of Hooks.
Rules of Hooks
Only Call Hooks at the Top Level
Don’t call Hooks inside loops, conditions, or nested functions.
Instead, always use Hooks at the top level of your React function,
before any early returns. By following this rule, you ensure that
Hooks are called in the same order each time a component renders.
That’s what allows React to correctly preserve the state of Hooks
between multiple useState and useEffect calls. (If you’re curious,
we’ll explain this in depth below.)
Only Call Hooks from React Functions
Don’t call Hooks from regular JavaScript functions. Instead, you can:
✅ Call Hooks from React function components.
✅ Call Hooks from custom Hooks (we’ll learn about them on the next page).
By following this rule, you ensure that all stateful logic in a
component is clearly visible from its source code.
You code is calling useWallet in a callback function passed to the useEffect hook. Note that this isn't the same thing as a custom Hook calling another hook.
Move the useWallet hook call out into the function component body. This will close over the wallet value in the render scope and will be available/accessible in the useEffect hook callback. I'm assuming you still only want/need the useEffect hook to run once when the component mounts, so I'm leaving that aspect alone.
import React, { useEffect } from 'react';
import { UseWalletProvider, useWallet } from 'use-wallet';
import { providers } from 'ethers';
export function App() {
const wallet = useWallet();
useEffect(() => {
console.log(wallet);
const { ethereum, connect } = wallet;
async function GetBlockId() {
const ethersProvider = new providers.Web3Provider(ethereum);
const { blockNumber } = await ethersProvider.getTransaction(hash);
console.log(blockNumber);
};
GetBlockId();
}, []);
return (
<div>
<h1>{wallet}</h1>
</div>
);
}
Update
To use the useWallet hook with a class component I suggest creating a Higher Order Component that can use it and pass the wallet value as a prop.
Example:
const withWallet = Component => props => {
const wallet = useWallet();
return <Component {...props} wallet={wallet} />;
};
Decorate the class component and access via this.props.wallet
class App extends Component {
constructor(props) {
super(props)
}
componentDidMount(): void {
const { ethereum, connect } = this.props.wallet;
...
}
render() {
return (
...
);
}
}
export default withWallet(App);
You can't call react hook inside a class component.
According to ReactJS Doc you can combine the functionality.
You can’t use Hooks inside a class component, but you can definitely mix classes and function components with Hooks in a single tree. Whether a component is a class or a function that uses Hooks is an implementation detail of that component. In the longer term, we expect Hooks to be the primary way people write React components.
GetBlockId Is Not a React Functional Component. There is no return method; hence it will throw an error saying that you can't use a hook in a non Functional Component. Change this function to a functional component (via returning a JSX component) and it should work.
NOTE that your getBlockId function is recursive and will fail.
According to the docs. In your class component (parent component). You will want to use the UseWalletProvider and in your functional component use the hook.
Here is an example (untested), hopefully that will get you on your way.
import React, {useState} from 'react';
import { useWallet, UseWalletProvider } from 'use-wallet';
import { ethers } from 'ethers';
import { hash } from './hash'
function App() {
const wallet = useWallet()
const blockNumber = wallet.getBlockNumber()
const [blockId, setBlockId] = useState('')
useEffect(()=>{
const getBlockId = async() => {
const { ethereum, connect } = wallet;
const ethersProvider = new ethers.providers.Web3Provider(ethereum);
return await ethersProvider.getTransaction(hash);
}
setBlockId(getBlockId());
},[]);
//Note that now blockId contains the blockId that you get.
//You can use it with child components etc.
return (
<div>
<p>{blockId}</p>
</div>
);
}
function Index() {
return (
<UseWalletProvider
chainId={1}
connectors={{
// This is how connectors get configured
portis: { dAppId: 'my-dapp-id-123-xyz' },
}}
>
<App />
</UseWalletProvider>
);
}
try the following code, you'd better not using useXXX inside an function which in a functional component,
export function App() {
const wallet = useWallet();
const getBlockId = useCallback(() => {
console.log(wallet);
const { ethereum, connect } = wallet;
const ethersProvider = new providers.Web3Provider(ethereum);
const { blockNumber } = await ethersProvider.getTransaction(hash);
console.log(blockNumber);
}, [wallet]);
useEffect(() => {
getBlockId()
}, []);
return <div>
<h1>{wallet}</h1>
</div>
}

Can I use the React Context API inside a Context API or do I have to merge them?

I am just curios about if it would be possible to use the Context API inside a Context API. Like for example I would have a Context API for an AppState and want to use that in another Context API which handles a WebSocket connection?
Inspired by Joseph's answer I am thinking about just using those both context api's in a custom hook together.
useMultipleContexts(){
const contextOne = useContext(ContextOne);
const contextTwo = useContext(ContextTwo);
/**
* Do something with both contexts
* in a custom hook that can be used
* multiple times with the same state
*/
}
This is a good scenario to use hooks instead of context.
// custom hook
function useAppState() {
//add handlers here
return appState;
}
function WebSocket() {
const appState = useAppState();
// do something (i.e reconnect) every time appState changes
useEffect(() => { /* do something */, [appState])
}
function App() {
return <WebSocket />
}
Let me explain how to use two different Contexts at the same time.
First step:
You need to create two different context
const AppContext = React.createContext(null);
const SocketContext = React.createContext(null);
Second step:
You need to implement your custom hook.
const UseSharedLogic = () => {
// your common logic
}
Then share it using the context API.
<AppContext.Provider value={state}>
<SocketContext.Provider value={UseSharedLogic}>
<App />
</DispatchContext.Provider>
</StateContext.Provider>
Third step:
You need to consume these contexts at the component that you need to use them inside it.
const state = React.useContext(AppContext);
const socket = React.useContext(SocketContext);
Here you can use both contexts together and you use one value from one context in another one.
Let's assume that socket context has a function called connect and it depends on value from the app context, you can do something like this.
socket.connect(state.anyValue);
I would create a new functional component that would wrap the components
Say you had two components written as follows.
import React from 'react';
const ContextA = React.createContext({});
export default ContextA;
import React from 'react';
const ContextB = React.createContext({});
export default ContextB;
I generally avoid the above pattern because people have to guess what you're trying to put in the context. Instead I write a functional component that provides the context as follows
import { createContext, useContext } from 'react'
import ContextA from './contexta'
import ContextB from './contextb'
// The ! is needed in order to allow undefined to be set as the initial value.
const MyContext = createContext<IMyContextInfo>(undefined!);
export default ({children}) => {
const { somethingFromA } = useContext(ContextA);
const { somethingFromB }= useContext(ContextB);
return (
<MyContext.Provider value={{ a: somethingFromA, b: somethingFromB }}>
{children}
</MyContext.Provider>
);
}

Proptypes for custom react hooks

With react hooks coming, should we use prop-types for React custom hooks e.g,
import React from 'react';
import PropTypes from 'prop-types';
const useTitle = title => {
React.useEffect(() => {
document.title = title;
}, [title]);
}
useTitle.propTypes = {
title: PropTypes.string.isRequired,
};
export default useTitle;
Is the above a good approach to validate the param(s) passed to a custom react hooks or should there be a different way for validation the props/params passed to custom hook which is basically just a simple function.
No. React doesn't validate custom hooks or in-built hooks props.
Look here for updateFunctionComponent, It validates prop types using checkPropTypes for a react component and then render it with hooks i.e. check out renderWithHooks.
Now if you check here in renderWithHooks method, It updates the dispatcher and calls your functional component which in turn calls your custom hook, since it's just another function call inside your functional component.
Your custom hook will call in-built hooks. You can check the implementation here . Based on type of dispatcher it will call built-in hooks.
If you search checkPropTypes in the whole file you won't find the validation logic or prop-types/checkPropTypes dependency which is used to validate the prop types.
Here is some nice article about how react hooks works
I'm using PropTypes.checkPropTypes for useSelector hook. And it works for me.
const useTitle = title => {
React.useEffect(() => {
document.title = withPropsValidation(title);
}, [title]);
}
const withPropsValidation = props => {
PropTypes.checkPropTypes(propTypes, props, 'prop', '')
return props
}
const propTypes = {
title: PropTypes.string.isRequired,
}
https://github.com/facebook/prop-types#proptypescheckproptypes
In my opinion, using some kind of type mechanism would be better like TypeScript but if you don't you should still use propTypes and additionally you can check this course out from kentcdodds to be sure about propTypes usage https://egghead.io/courses/simplify-react-apps-with-react-hooks
The OP's approach has an issue in that the line const useTitle = title => { ... }; effectively makes title be the name of the parameter commonly named props. The useTitle.propTypes assignment, then, is not accurate.
The OP's approach, however, is viable (with modification). Below is an implementation showing how to use PropTypes with a custom hook:
MyHook.jsx:
import { useEffect } from 'react';
import PropTypes from 'prop-types';
const useMyHook= ({ callback, ref }) => {
...do something...
};
useMyHook.propTypes = {
callback: PropTypes.func.isRequired,
ref: PropTypes.element.isRequired
};
export { useMyHook };
MyComponent.jsx:
import React, { useRef } from 'react';
import { useMyHook} from '.../MyHook.jsx';
const MyComponent = props => {
const myRef = useRef(null);
const myCallback = () => console.log('Hello');
useMyHook({ ref: myRef, callback, myCallback });
return <div ref={myRef}>Stuff...</div>;
};
Typescript is the best way for validation and check props
import React from 'react';
const useTitle = ({title}:{title?:string})=> {
React.useEffect(() => {
document.title = title;
}, [title]);
}
export default useTitle;
Or
import React from 'react';
type customPropType= {
title?:string
}
const useTitle = ({title}:customPropType)=> {
React.useEffect(() => {
document.title = title;
}, [title]);
}
export default useTitle;

Resources