How do I use a custom hook inside of useEffect? - reactjs

I'm working with Amazon's Chime SDK Component Library for React. The component library has a number of components and hooks available for use.
One of the component is <LocalVideo />. The problem is that it starts with the video disabled. In order to turn it on you use the hook useLocalVideo().
In the examples I've seen they use a button to execute the hook:
const MyComponent = () => {
const togglevideo = { useLocalVideo };
return(
<>
<LocalVideo />
<button onClick={toggleVideo}>Toggle</button>
</>
);
};
This works fine. But what if I want the LocalVideo component to load enabled? e.g., if I don't want someone to have to click a button?
I've tried several different approaches including:
Add the code directly to the component (this doesn't work, I assume because the hook is called before the render with the LocalVideo component completes).
Adding the code inside useEffect (invalid Hook call errors).
For example:
const MyComponent = () => {
useEffect( () => {
useLocalVideo();
},
);
const MyComponent = () => {
const { tileId, isVideoEnabled, setIsVideoEnabled, toggleVideo } = useLocalVideo();
useEffect( () => {
setIsVideoEnabled(true);
}, []
);
How can I make this hook run after the component renders?

You should call the function toggleVideo inside the useEffect, not the hook useLocalVideo itself:
const MyComponent = () => {
const { togglevideo } = useLocalVideo();
useEffect(() => {
togglevideo();
}, []);
return (
<LocalVideo />
);
};

Related

Adding functions to lit web components in react with typescript

I have a web component i created in lit, which takes in a function as input prop. but the function is not being triggered from the react component.
import React, { FC } from 'react';
import '#webcomponents/widgets'
declare global {
namespace JSX {
interface IntrinsicElements {
'webcomponents-widgets': WidgetProps
}
}
}
interface WidgetProps extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> {
var1: string,
successCallback: Function,
}
const App = () =>{
const onSuccessCallback = () =>{
console.log("add some logic here");
}
return(<webcomponents-widgets var1="test" successCallBack={onSuccessCallback}></webcomponents-widgets>)
}
How can i trigger the function in react component? I have tried this is vue 3 and is working as expected.
Am i missing something?
As pointed out in this answer, React does not handle function props for web components properly at this time.
While it's possible to use a ref to add the function property imperatively, I would suggest the more idiomatic way of doing things in web components is to not take a function as a prop but rather have the web component dispatch an event on "success" and the consumer to write an event handler.
So the implementation of <webcomponents-widgets>, instead of calling
this.successCallBack();
would instead do
const event = new Event('success', {bubbles: true, composed: true});
this.dispatch(event);
Then, in your React component you can add the event listener.
const App = () => {
const widgetRef = useRef();
const onSuccessCallback = () => {
console.log("add some logic here");
}
useEffect(() => {
widgetRef.current?.addEventListener('success', onSuccessCallback);
return () => {
widgetRef.current?.removeEventListener('success', onSuccessCallback);
}
}, []);
return(<webcomponents-widgets var1="test" ref={widgetRef}></webcomponents-widgets>);
}
The #lit-labs/react package let's you wrap the web component, turning it into a React component so you can do this kind of event handling declaratively.
React does not handle Web Components as well as other frameworks (but it is planned to be improved in the future).
What is happening here is that your successCallBack parameter gets converted to a string. You need to setup a ref on your web component and set successCallBack from a useEffect:
const App = () => {
const widgetRef = useRef();
const onSuccessCallback = () =>{
console.log("add some logic here");
}
useEffect(() => {
if (widgetRef.current) {
widgetRef.current.successCallBack = onSuccessCallback;
}
}, []);
return(<webcomponents-widgets var1="test" ref={widgetRef}></webcomponents-widgets>)
}

Unexpected behaviour of setInterval function (interval keeps on decreasing) [duplicate]

Are there ways to simulate componentDidMount in React functional components via hooks?
For the stable version of hooks (React Version 16.8.0+)
For componentDidMount
useEffect(() => {
// Your code here
}, []);
For componentDidUpdate
useEffect(() => {
// Your code here
}, [yourDependency]);
For componentWillUnmount
useEffect(() => {
// componentWillUnmount
return () => {
// Your code here
}
}, [yourDependency]);
So in this situation, you need to pass your dependency into this array. Let's assume you have a state like this
const [count, setCount] = useState(0);
And whenever count increases you want to re-render your function component. Then your useEffect should look like this
useEffect(() => {
// <div>{count}</div>
}, [count]);
This way whenever your count updates your component will re-render. Hopefully this will help a bit.
There is no exact equivalent for componentDidMount in react hooks.
In my experience, react hooks requires a different mindset when developing it and generally speaking you should not compare it to the class methods like componentDidMount.
With that said, there are ways in which you can use hooks to produce a similar effect to componentDidMount.
Solution 1:
useEffect(() => {
console.log("I have been mounted")
}, [])
Solution 2:
const num = 5
useEffect(() => {
console.log("I will only run if my deps change: ", num)
}, [num])
Solution 3 (With function):
useEffect(() => {
const someFunc = () => {
console.log("Function being run after/on mount")
}
someFunc()
}, [])
Solution 4 (useCallback):
const msg = "some message"
const myFunc = useCallback(() => {
console.log(msg)
}, [msg])
useEffect(() => {
myFunc()
}, [myFunc])
Solution 5 (Getting creative):
export default function useDidMountHook(callback) {
const didMount = useRef(null)
useEffect(() => {
if (callback && !didMount.current) {
didMount.current = true
callback()
}
})
}
It is worth noting that solution 5 should only really be used if none of the other solutions work for your use case. If you do decide you need solution 5 then I recommend using this pre-made hook use-did-mount.
Source (With more detail): Using componentDidMount in react hooks
There's no componentDidMount on functional components, but React Hooks provide a way you can emulate the behavior by using the useEffect hook.
Pass an empty array as the second argument to useEffect() to run only the callback on mount only.
Please read the documentation on useEffect.
function ComponentDidMount() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log('componentDidMount');
}, []);
return (
<div>
<p>componentDidMount: {count} times</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
Click Me
</button>
</div>
);
}
ReactDOM.render(
<div>
<ComponentDidMount />
</div>,
document.querySelector("#app")
);
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
useEffect() hook allows us to achieve the functionality of componentDidMount, componentDidUpdate componentWillUnMount functionalities.
Different syntaxes of useEffect() allows to achieve each of the above methods.
i) componentDidMount
useEffect(() => {
//code here
}, []);
ii) componentDidUpdate
useEffect(() => {
//code here
}, [x,y,z]);
//where x,y,z are state variables on whose update, this method should get triggered
iii) componentDidUnmount
useEffect(() => {
//code here
return function() {
//code to be run during unmount phase
}
}, []);
You can check the official react site for more info. Official React Page on Hooks
Although accepted answer works, it is not recommended. When you have more than one state and you use it with useEffect, it will give you warning about adding it to dependency array or not using it at all.
It sometimes causes the problem which might give you unpredictable output. So I suggest that you take a little effort to rewrite your function as class. There are very little changes, and you can have some components as class and some as function. You're not obligated to use only one convention.
Take this for example
function App() {
const [appointments, setAppointments] = useState([]);
const [aptId, setAptId] = useState(1);
useEffect(() => {
fetch('./data.json')
.then(response => response.json())
.then(result => {
const apts = result.map(item => {
item.aptId = aptId;
console.log(aptId);
setAptId(aptId + 1);
return item;
})
setAppointments(apts);
});
}, []);
return(...);
}
and
class App extends Component {
constructor() {
super();
this.state = {
appointments: [],
aptId: 1,
}
}
componentDidMount() {
fetch('./data.json')
.then(response => response.json())
.then(result => {
const apts = result.map(item => {
item.aptId = this.state.aptId;
this.setState({aptId: this.state.aptId + 1});
console.log(this.state.aptId);
return item;
});
this.setState({appointments: apts});
});
}
render(...);
}
This is only for example. so lets not talk about best practices or potential issues with the code. Both of this has same logic but the later only works as expected. You might get componentDidMount functionality with useEffect running for this time, but as your app grows, there are chances that you MAY face some issues. So, rather than rewriting at that phase, it's better to do this at early stage.
Besides, OOP is not that bad, if Procedure-Oriented Programming was enough, we would never have had Object-Oriented Programming. It's painful sometimes, but better (technically. personal issues aside).
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Please visit this official docs. Very easy to understand the latest way.
https://reactjs.org/docs/hooks-effect.html
Info about async functions inside the hook:
Effect callbacks are synchronous to prevent race conditions. Put the async function inside:
useEffect(() => {
async function fetchData() {
// You can await here
const response = await MyAPI.getData(someId);
// ...
}
fetchData();
}, [someId]); // Or [] if effect doesn't need props or state
useLayoutEffect hook is the best alternative to ComponentDidMount in React Hooks.
useLayoutEffect hook executes before Rendering UI and useEffect hook executes after rendering UI. Use it depend on your needs.
Sample Code:
import { useLayoutEffect, useEffect } from "react";
export default function App() {
useEffect(() => {
console.log("useEffect Statements");
}, []);
useLayoutEffect(() => {
console.log("useLayoutEffect Statements");
}, []);
return (
<div>
<h1>Hello Guys</h1>
</div>
);
}
Yes, there is a way to SIMULATE a componentDidMount in a React functional component
DISCLAIMER: The real problem here is that you need to change from "component life cycle mindset" to a "mindset of useEffect"
A React component is still a javascript function, so, if you want something to be executed BEFORE some other thing you must simply need to execute it first from top to bottom, if you think about it a function it's still a funtion like for example:
const myFunction = () => console.log('a')
const mySecondFunction = () => console.log('b)
mySecondFunction()
myFunction()
/* Result:
'b'
'a'
*/
That is really simple isn't it?
const MyComponent = () => {
const someCleverFunction = () => {...}
someCleverFunction() /* there I can execute it BEFORE
the first render (componentWillMount)*/
useEffect(()=> {
someCleverFunction() /* there I can execute it AFTER the first render */
},[]) /*I lie to react saying "hey, there are not external data (dependencies) that needs to be mapped here, trust me, I will leave this in blank.*/
return (
<div>
<h1>Hi!</h1>
</div>
)}
And in this specific case it's true. But what happens if I do something like that:
const MyComponent = () => {
const someCleverFunction = () => {...}
someCleverFunction() /* there I can execute it BEFORE
the first render (componentWillMount)*/
useEffect(()=> {
someCleverFunction() /* there I can execute it AFTER the first render */
},[]) /*I lie to react saying "hey, there are not external data (dependencies) that needs to be maped here, trust me, I will leave this in blank.*/
return (
<div>
<h1>Hi!</h1>
</div>
)}
This "cleverFunction" we are defining it's not the same in every re-render of the component.
This lead to some nasty bugs and, in some cases to unnecessary re-renders of components or infinite re-render loops.
The real problem with that is that a React functional component is a function that "executes itself" several times depending on your state thanks to the useEffect hook (among others).
In short useEffect it's a hook designed specifically to synchronize your data with whatever you are seeing on the screen. If your data changes, your useEffect hook needs to be aware of that, always. That includes your methods, for that it's the array dependencies.
Leaving that undefined leaves you open to hard-to-find bugs.
Because of that it's important to know how this work, and what you can do to get what you want in the "react" way.
const initialState = {
count: 0,
step: 1,
done: false
};
function reducer(state, action) {
const { count, step } = state;
if (action.type === 'doSomething') {
if(state.done === true) return state;
return { ...state, count: state.count + state.step, state.done:true };
} else if (action.type === 'step') {
return { ...state, step: action.step };
} else {
throw new Error();
}
}
const MyComponent = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;
useEffect(() => {
dispatch({ type: 'doSomething' });
}, [dispatch]);
return (
<div>
<h1>Hi!</h1>
</div>
)}
useReducer's dispatch method it's static so it means it will be the same method no matter the amount of times your component is re-rendered. So if you want to execute something just once and you want it rigth after the component is mounted, you can do something like the above example. This is a declarative way of do it right.
Source: The Complete Guide to useEffect - By Dan Abramov
That being said if you like to experiment with things and want to know how to do it "the imperative wat" you can use a useRef() with a counter or a boolean to check if that ref stores a defined reference or not, this is an imperative approach and it's recommended to avoid it if you're not familiar with what happen with react behind curtains.
That is because useRef() is a hook that saves the argument passed to it regardless of the amount of renders (I am keeping it simple because it's not the focus of the problem here, you can read this amazing article about useRef ). So it's the best approach to known when the first render of the component happened.
I leave an example showing 3 different ways of synchronise an "outside" effect (like an external function) with the "inner" component state.
You can run this snippet right here to see the logs and understand when these 3 functions are executed.
const { useRef, useState, useEffect, useCallback } = React
// External functions outside react component (like a data fetch)
function renderOnce(count) {
console.log(`renderOnce: I executed ${count} times because my default state is: undefined by default!`);
}
function renderOnFirstReRender(count) {
console.log(`renderOnUpdate: I executed just ${count} times!`);
}
function renderOnEveryUpdate(count) {
console.log(`renderOnEveryUpdate: I executed ${count ? count + 1 : 1} times!`);
}
const MyComponent = () => {
const [count, setCount] = useState(undefined);
const mounted = useRef(0);
// useCallback is used just to avoid warnings in console.log
const renderOnEveryUpdateCallBack = useCallback(count => {
renderOnEveryUpdate(count);
}, []);
if (mounted.current === 0) {
renderOnce(count);
}
if (mounted.current === 1) renderOnFirstReRender(count);
useEffect(() => {
mounted.current = mounted.current + 1;
renderOnEveryUpdateCallBack(count);
}, [count, renderOnEveryUpdateCallBack]);
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(prevState => (prevState ? prevState + 1 : 1))}>TouchMe</button>
</div>
);
};
class App extends React.Component {
render() {
return (
<div>
<h1>hI!</h1>
</div>
);
}
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<MyComponent/>
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
If you execute it you will see something like this:
You want to use useEffect(), which, depending on how you use the function, can act just like componentDidMount().
Eg. you could use a custom loaded state property which is initially set to false, and switch it to true on render, and only fire the effect when this value changes.
Documentation
the exact equivalent hook for componentDidMount() is
useEffect(()=>{},[]);
hope this helpful :)

How to make react components communicate each others in large component tree

In my react app, I have some complex component tree.
In this component tree, I have a <Footer/> component with buttons. I also have <SomeComponent/> component elsewhere in the tree. This component is actually loaded from some dynamic code and is not always the same (similar to some widget engine, where the container is handled by the app engine, and the content is dynamically loaded). It means the context has no knowledge of what are actually the components.
In order to plug everything else, I have a custom react context that holds some fields and methods, which is exposed trough a custom useMyContext hook.
This is working quite well except one remaining issue :
In my <Footer /> I have a button that should call something inside the <SomeComponent/> component. As an example I may have a 'Refresh' button that should ask the component to get latest data.
Basically I have this react tree:
App
SomeContextProvider
Footer
RefreshButton
Deep/Nested/Component/Structure
SomeComponent
(contains a refresh function)
How can I call the refresh function in my component from the footer ?
I tried to play with forwarding refs and useImperativeHandler hook, which may work, but the deep nesting of component tree leads to a big mess of forwarding refs.
I also tried to extend the context provider, but I didn't found a way to "reverse" the callback (context can react to Refresh button action, but I cannot react to this in sibling branch of the component tree).
How could I handle this ?
PS: if it matters, I'm using react 16.13.1 and typescript 4.5
I think I have the start of a clean solution.
Basically, I can handle my scenario by implementing a subscribe/unsubscribe pattern hold by by app context.
This way I can emit some kind of event from my outer context, and let components in the tree subscribe and handle the events as needed.
Some repro : https://codesandbox.io/s/infallible-chaplygin-79c3gq?file=/src/App.tsx.
Relevant parts below:
Custom react context
type Subscribe = (cb: () => void) => () => void;
type AppContextData = {
subscribe: Subscribe;
onSubmit: () => void;
};
const AppContext = createContext<AppContextData | undefined>(undefined);
const useAppContext = (): AppContextData => {
const context = useContext(AppContext);
if (!context)
throw new Error(`useAppContext must be used within a AppContextProvider`);
return context;
};
Container component
const AppContextProvider: React.FC<PropsWithChildren<{}>> = ({ children }) => {
const subscribtions: (() => void)[] = [];
const subscribe: Subscribe = (cb) => {
subscribtions.push(cb);
return () => {
subscribtions.splice(subscribtions.indexOf(cb), 1);
};
};
const emitSubmit = () => {
subscribtions.forEach((cb) => cb());
};
const appContext: AppContextData = {
subscribe,
onSubmit: emitSubmit
};
return (
<AppContext.Provider value={appContext}>{children}</AppContext.Provider>
);
};
App
export default function App() {
return (
<AppContextProvider>
<div className="App">
<Main />
<Footer />
</div>
</AppContextProvider>
);
}
And finally, subscription and submission trigger:
Component with button
const Footer: React.VFC = () => {
const { onSubmit } = useAppContext();
return <button onClick={onSubmit}>Submit</button>;
};
Component that subscribes (and unsubscribe thanks to react effect)
const Main: React.VFC = () => {
const [myString, setMyString] = useState("initial");
const context = useAppContext();
useEffect(() => {
return context.subscribe(() => setMyString("from context"));
}, [context]);
return <p>{myString}</p>;
};

How to test onClick with Jest that is NOT a callback function in props?

I found lots of ways of using mock functions in jest to spy on callback functions that are passed down to a component but nothing on testing a simple onClick that is defined in the same component.
My Example Page:
const ExamplePage: NextPage = () => {
const router = useRouter();
const onClick = (): Promise<void> => {
axios.post(`/api/track`, {
eventName: Event.TRACK_CLICK,
});
router.push("/new-route");
return Promise.resolve();
};
return (
<Container data-testid="container">
<Title>Example Title</Title>
<CreateButton data-testid="create-button" onClick={onClick}>
Create Partner
</CreateButton>
</Container>
);
};
export default ExamplePage;
My current test where I am attempting to get the onClick from getAttribute:
import { fireEvent, render } from "../../../../test/customRenderer";
import ExamplePage from "../../../pages/example-page";
describe("Example page", () => {
it("has a button to create", () => {
const { getByTestId } = render(<ExamplePage />);
const createButton = getByTestId("create-button");
expect(createButton).toBeInTheDocument();
});
it(" the button's OnClick function should be executed when clicked", () => {
const { getByTestId } = render(<ExamplePage />);
// find the button
const createButton = getByTestId("create-button");
// check the button has onClick
expect(createButton).toHaveAttribute("onClick");
// get the onClick function
const onClick = createButton.getAttribute("onClick");
fireEvent.click(createButton);
// check if the button's onClick function has been executed
expect(onClick).toHaveBeenCalled();
});
});
The above fails since there is no onClick attribute only null. My comments in the test highlight my thought process of trying to reach down into this component for the function on the button and checking if it has been called.
Is there any way to test a onClick that is self contained in a react component?
You need to provide mocked router provider and expect that a certain route is pushed to the routers. You also need extract the RestAPI into a separate module and mock it! You can use Dependency Injection, IOC container or import the Api in the component and mock it using jest. I will leave the RestAPi mocking to you.
Mocking router details here: How to mock useRouter
const useRouter = jest.spyOn(require('next/router'), 'useRouter')
describe("", () => {
it("",() => {
const pushMock = jest.fn();
// Mocking Rest api call depends on how you are going to "inject it" in the component
const restApiMock = jest.jn().mockResolvedValue();
useRouter.mockImplementationOnce(() => ({
push: pushMock,
}))
const rendrResult = render(<ExamplePage />);
//get and click the create button
//expect the "side" effects of clicking the button
expect(restApiMock).toHaveBeenCalled();
expect(pushMock).toHaveBeenCalledWith("/new-route");
});
});

How to use usehook in a function using react and typescript?

i want to use useHook in a function and it says useHook is used within a function which is neither a react function component or custom react hook.
i want to use useOpen hook within a function and i get error.
below is my code,
import useOpen from './Open';
function hide = () => {
const open = useOpen();
open.setOpen(true);
}
function useAnother = () => {
load().then(()=>{
hide();
}
}
what i have tried?
const useHide = () => {
const open = useOpen();
open.setOpen(true);
}
function useAnother = () => {
load().then(()=>{
useHide(); //error here
}
}
Now it throws error react hook cannot be called inside a callback. React hooks must be called within a react function component or custom react hooks.
i am not sure how to fix this. could someone help me fix it. thanks.
According to the Rules of Hooks, you can only call Hooks at the top level: https://reactjs.org/docs/hooks-rules.html
You can solve your problem by calling useHide at the top level and returning a function from useHide that you can call from anywhere. For example:
const useHide = () => {
const hide = () => {
console.log("hiding!!!");
};
return {
hide
};
};
const useAnother = () => {
const { hide } = useHide();
load().then(() => {
hide();
}
};
Sample: https://codesandbox.io/s/stackoverflow-62317282-kielz

Resources