I am learning react and right now I have a react app with users and movies, where you can see info about movies whenever you are logged in. If you are logged in as an admin you get access to an adminpage where you can see a list of usercards and register another admin. This cardlist gets updated without needing to refresh.
Since the code that I wrote is not that clean, I wanted to incorporate custom hooks. The problem is that with the new custom hooks everything works fine except for the rendering. Whenever I delete a user or add a new admin, the cardlist does not get updated unless I refresh the page.
I now have a custom hook useUsers but I only use it for my input fields and toast notifcations.
I tried adding users to my useEffect in the hook but that didn't fix my problem.
useEffect(() => { refreshUserList(); }, [users]);
Here is my code.
function useUsers() {
const [users, setUsers] = useState([])
const [showModal, setShowModal] = useState(false)
const notifyUserDeleted = () => toast.success('User deleted!', {
position: "top-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "colored",
});
const [adminUsername, setAdminUsername] = useState("")
const [adminPassword, setAdminPassword] = useState("")
const [showAdminModal, setShowAdminModal] = useState(false)
const notifyAddAdminSuccess = () =>
toast.success('Admin registered!', {
position: "top-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "colored",
})
const refreshUserList = () => {
UserAPI.getUsers()
.then(res => {
setUsers(res.data.users);
})
.catch(err => {
console.error(err);
});
};
useEffect(() => {
refreshUserList();
}, []);
const createAdmin = (adminUsername, adminPassword) => {
const registeredAdmin = {
"username": adminUsername,
"password": adminPassword
};
UserAPI.createAdmin(registeredAdmin)
.then(() => {
setAdminUsername("");
setAdminPassword("");
notifyAddAdminSuccess();
Adminpage.refreshUserList();
})
.catch(err => {
console.log(err);
alert("Failed to register admin!");
});
};
const handleRegisterAdmin = (e) => {
e.preventDefault();
createAdmin(adminUsername, adminPassword);
setShowAdminModal(false);
};
const deleteUser = (id) => {
UserAPI.deleteUser(id)
.then(() => {
notifyUserDeleted()
refreshUserList()
})
.catch(error => {
console.error(error);
});
};
const handleDelete = (e) => {
e.preventDefault();
deleteUser(e.target.value)
setShowModal(false)
}
return {
users, setUsers, showModal, setShowModal, notifyUserDeleted, notifyAddAdminSuccess, showAdminModal, setShowAdminModal,
adminPassword, setAdminPassword, adminUsername, setAdminUsername, refreshUserList, handleRegisterAdmin, handleDelete
}
}
export default useUsers;
function Adminpage() {
const { users, refreshUserList } = useUsers();
return (
<div className="container" style={{ display: "flex" }}>
<UserCardList users={users} refreshUserList={refreshUserList} />
<InputAdmin refreshUserList={refreshUserList} />
</div>
);
}
export default Adminpage;
function UserCardList(props) {
return (
<div className="container">
<h4 style={{ margin: "3% 0 2% 0" }}>User list:</h4>
<Bootstrap.Row>
{
props.users.map(user =>
<UserCard key={user.id} users={user} refreshUserList={props.refreshUserList} />
)
}
</Bootstrap.Row>
</div>
)
}
export default UserCardList;
function UserCard(props) {
const { showModal,
setShowModal,
handleDelete
} = useUsers()
return (
<Bootstrap.Col className="col-lg-4 col-12">
<Bootstrap.Card className='mb-1' style={{ height: "98%", }}>
<Bootstrap.Card.Body>
<Bootstrap.Card.Text><b>User ID: </b>{props.users.id}</Bootstrap.Card.Text>
<Bootstrap.Card.Text><b>Username: </b>{props.users.username}</Bootstrap.Card.Text>
<Bootstrap.Button style={{ backgroundColor: "red", borderColor: "gray" }} onClick={() => setShowModal(true)}><RiDeleteBin5Fill style={{ backgroundColor: "red" }} /></Bootstrap.Button>
</Bootstrap.Card.Body>
</Bootstrap.Card>
<Bootstrap.Modal centered show={showModal} onHide={() => setShowModal(false)}>
<Bootstrap.Modal.Header closeButton>
<Bootstrap.Modal.Title>Confirm Delete</Bootstrap.Modal.Title>
</Bootstrap.Modal.Header>
<Bootstrap.Modal.Body>Are you sure you want to delete this user?</Bootstrap.Modal.Body>
<Bootstrap.Modal.Footer>
<Bootstrap.Button variant="secondary" onClick={() => setShowModal(false)}>
Cancel
</Bootstrap.Button>
<Bootstrap.Button variant="danger" value={props.users.id} onClick={handleDelete}>
Delete
</Bootstrap.Button>
</Bootstrap.Modal.Footer>
</Bootstrap.Modal>
</Bootstrap.Col>
)
}
export default UserCard;
Issue
useEffect(() => { refreshUserList(); }, [users]);
Adding users to the useEffect hook will likely cause a render loop since refreshUserList ultimates updates the users state. Don't unconditionally update any of a hook's dependencies.
React hooks also don't share state. You've two components, Adminpage and UserCard, each using separate instances of a useUsers hook each with their own state. Mutating the state in one instance of useUsers doesn't effect any other instance of useUsers.
Solution
Move the state and logic from the useUsers to a singular React context provider and allow all instances of the useUsers hook to access the single context value.
Example:
export const UsersContext = React.createContext({
adminPassword: "",
adminUsername: "",
handleDelete: () => {},
handleRegisterAdmin: () => {},
notifyAddAdminSuccess: () => {},
notifyUserDeleted: () => {},
setAdminPassword: () => {},
setAdminUsername: () => {},
setShowAdminModal: () => {},
setShowModal: () => {},
setUsers: () => {},
showModal: () => {},
showAdminModal: false,
refreshUserList: () => {},
users: [],
});
export const useUsers = () => React.useContext(UsersContext);
const toastOptions = {
position: "top-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "colored",
};
const UsersProvider = ({ children }) => {
const [users, setUsers] = useState([]);
const [showModal, setShowModal] = useState(false);
const [adminUsername, setAdminUsername] = useState("");
const [adminPassword, setAdminPassword] = useState("");
const [showAdminModal, setShowAdminModal] = useState(false);
const notifyUserDeleted = () =>
toast.success('User deleted!', toastOptions);
const notifyAddAdminSuccess = () =>
toast.success('Admin registered!', toastOptions);
const refreshUserList = () => {
UserAPI.getUsers()
.then(res => {
setUsers(res.data.users);
})
.catch(err => {
console.error(err);
});
};
useEffect(() => {
refreshUserList();
}, []);
const createAdmin = (username, password) => {
const registeredAdmin = { username, password };
UserAPI.createAdmin(registeredAdmin)
.then(() => {
setAdminUsername("");
setAdminPassword("");
notifyAddAdminSuccess();
Adminpage.refreshUserList();
})
.catch(err => {
console.log(err);
alert("Failed to register admin!");
});
};
const handleRegisterAdmin = (e) => {
e.preventDefault();
createAdmin(adminUsername, adminPassword);
setShowAdminModal(false);
};
const deleteUser = (id) => {
UserAPI.deleteUser(id)
.then(() => {
notifyUserDeleted();
refreshUserList();
})
.catch(error => {
console.error(error);
});
};
const handleDelete = (e) => {
e.preventDefault();
deleteUser(e.target.value);
setShowModal(false);
}
const value = {
adminPassword,
adminUsername,
handleDelete,
handleRegisterAdmin,
notifyUserDeleted,
notifyAddAdminSuccess,
refreshUserList,
setUsers,
showModal,
setShowModal,
showAdminModal,
setShowAdminModal,
setAdminPassword,
setAdminUsername,
users,
};
return (
<UsersContext.Provider value={value}>
{children}
</UsersContext.Provider>
);
}
Wrap the app code with the UsersProvider component to provide the users state and callbacks.
<UsersProvider>
...
<Adminpage />
...
</UsersProvider>
Now all the components rendered in UsersProvider's sub-Reactree using the useUsers hook will access and reference the same state and callbacks.
useEffect(() => { refreshUserList(); }, [users])
The 2nd parameter [users] tells useEffect when to fire. So you are actually refreshing the users when the users have changed, which then means never because the users change when they are refreshed.
The trouble you face is very very common in React :) Each hook usage creates it's "own" isolated scope. Meaning that each useUsers() usage produces separate users collection (retrieved from your back-end inside useEffect) and exposes separate set of user-manipulation-specific methods (like handleDelete)
First of all, before answering initial question let's reveal very very dramatic trouble you'll probably face soon
As far as each "useUsers" execution has it's own scope, every time you use this hook, following code will get executed:
useEffect(() => {
refreshUserList(); // UserAPI.getUsers()
}, []);
Now imagine, you call this hook in Adminpage component - users get loaded from the back-end. When users are loaded - you render UserCardList with loaded users. And UserCardList renders UserCard component for each loaded user. And finally each UserCard component calls "useUsers" hook you've created and each of them calls code above (useEffect -> refreshUserList) so each of UserCard component loads users from your back-end one more time :(
You see the trouble, you load 20 users, and for each of these users you load all these users again. if you have 20 users - it would be 21 call on your back-end. If you have 100 users - it would be 101 call on the back-end, etc... The performance of the back-end will definitely degradade very very quickly
To prove this assumption please open network tab in your browser and just reload the page. You'll dfefinitely see endless sequence of load-users requests...
That was about the back-end, but the initial question with not working front-end when users are removed is still opened
The situation is following: you use "useUsers" in Adminpage component to render user list and you use "useUsers" in each UserCard component to call "handleDelete" whenever needed
Each of these components loads "users" independently and when user gets removed via handleDelete in UserCard component - yes, you remove this user "physically" from your storage via
UserAPI.deleteUser
But, on the front-end you execute "refreshUserList":
const deleteUser = (id) => {
UserAPI.deleteUser(id)
.then(() => {
notifyUserDeleted()
refreshUserList()
})
.catch(error => {
console.error(error);
});
};
only in scope of current UserCard component and it refreshes "users" collection only in scope of UserCard component
So parent Adminpage component does not have idea that one of users was removed and user list does not get updated
The most easy "working" solution here is not to call useUsers() hook multiple times and pass handleDelete from Adminpage component to each UserCard component directly (as input parameter):
function Adminpage() {
const { users, refreshUserList, handleDelete } = useUsers();
return (
<div className="container" style={{ display: "flex" }}>
<UserCardList handleDelete={handleDelete} users={users} refreshUserList={refreshUserList} />
<InputAdmin refreshUserList={refreshUserList} />
</div>
);
}
export default Adminpage;
function UserCardList(props) {
return (
<div className="container">
<h4 style={{ margin: "3% 0 2% 0" }}>User list:</h4>
<Bootstrap.Row>
{
props.users.map(user =>
<UserCard key={user.id} handleDelete ={props.handleDelete} users={user} refreshUserList={props.refreshUserList} />
)
}
</Bootstrap.Row>
</div>
)
}
export default UserCardList;
function UserCard(props) {
// do not call "useUsers" here !!! instead accept needed callbacks as input parameters
return (
<Bootstrap.Col className="col-lg-4 col-12">
......... this code remains the same ..........
This technique would fix all back-end / front-end issues you face with minimal effort, but this fix has some disadvantages anyway:
you're able to use "useUsers" hook only once in component tree. And you have to remember this rule and always be careful with it
you have to pass methods through all components in your component tree, like I did it with handleDelete above (Adminpage -> UserCardList -> UserCard). For your component tree it's not too complex, but for larger component hierarchies it could become "a hell". Just imagine that you do it for 5 callbacks through hierarchy of 10 components...
The first trouble (only single hook usage) could be fixed only if you'll connect your "useUsers" usages in some way. Probably, you could utilize some rxjs-like approach with subscribers/notifications inside useUsers, share same "users" collection between them, and whenever something happens inside one of "useUsers" - notify others, etc... Other possible solution here - utilize some application-wide storage, like REDUX and store users there. Anyway, that's definitely much complicated issue then one you already face and that's "better" to skip it if no urgency
To fix second issue (passing callback as input parameter through every component in hierarchy) React's useContext() hook could be handy https://beta.reactjs.org/reference/react/useContext
The solution that Uladzimir presented worked. I had to pass everything from the adminpage into the components like this:
function Adminpage() {
const { users, handleDelete, handleRegisterAdmin,
adminUsername,
setAdminUsername,
adminPassword,
showAdminModal,
setShowAdminModal,
setAdminPassword,
showModal,
setShowModal,
} = useUsers()
return (
<div className="container" style={{ display: "flex" }}>
<UserCardList users={users} handleDelete={handleDelete} showModal={showModal} setShowModal={setShowModal} />
<InputAdmin handleRegisterAdmin={handleRegisterAdmin} adminUsername={adminUsername}
setAdminUsername={setAdminUsername} adminPassword={adminPassword} setAdminPassword={setAdminPassword}
showAdminModal={showAdminModal} setShowAdminModal={setShowAdminModal} />
</div>
);
}
export default Adminpage;
I have code block like this
const onRouteChangeStart = React.useCallback(() => {
if (formState.isDirty) {
if (window.confirm('Confirmation message')) {
return true;
}
NProgress.done();
throw "Abort route change by user's confirmation.";
}
}, [formState.isDirty]);
React.useEffect(() => {
Router.events.on('routeChangeStart', onRouteChangeStart);
return () => {
Router.events.off('routeChangeStart', onRouteChangeStart);
};
}, [onRouteChangeStart]);
It works as I want but I want to add a Custom Confirmation Modal instead of Native Confirmation.
When I added, route changes did not stop. That's why I couldn't wait for the user response.
What can I do? Thank you for your responses.
There is a good sample here where it aborts the current route change and saves it to state, prompts the custom model. If confirmed, it pushes the route again.
https://github.com/vercel/next.js/discussions/32231?sort=new?sort=new#discussioncomment-2033546
import { useRouter } from 'next/router';
import React from 'react';
import Dialog from './Dialog';
export interface UnsavedChangesDialogProps {
shouldConfirmLeave: boolean;
}
export const UnsavedChangesDialog = ({
shouldConfirmLeave,
}: UnsavedChangesDialogProps): React.ReactElement<UnsavedChangesDialogProps> => {
const [shouldShowLeaveConfirmDialog, setShouldShowLeaveConfirmDialog] = React.useState(false);
const [nextRouterPath, setNextRouterPath] = React.useState<string>();
const Router = useRouter();
const onRouteChangeStart = React.useCallback(
(nextPath: string) => {
if (!shouldConfirmLeave) {
return;
}
setShouldShowLeaveConfirmDialog(true);
setNextRouterPath(nextPath);
throw 'cancelRouteChange';
},
[shouldConfirmLeave]
);
const onRejectRouteChange = () => {
setNextRouterPath(null);
setShouldShowLeaveConfirmDialog(false);
};
const onConfirmRouteChange = () => {
setShouldShowLeaveConfirmDialog(false);
// simply remove the listener here so that it doesn't get triggered when we push the new route.
// This assumes that the component will be removed anyway as the route changes
removeListener();
Router.push(nextRouterPath);
};
const removeListener = () => {
Router.events.off('routeChangeStart', onRouteChangeStart);
};
React.useEffect(() => {
Router.events.on('routeChangeStart', onRouteChangeStart);
return removeListener;
}, [onRouteChangeStart]);
return (
<Dialog
title="You have unsaved changes"
description="Leaving this page will discard unsaved changes. Are you sure?"
confirmLabel="Discard changes"
cancelLabel="Go back"
isOpen={shouldShowLeaveConfirmDialog}
onConfirm={onConfirmRouteChange}
onReject={onRejectRouteChange}
/>
);
};
Everything auth-wise is working fine. I even have a loading state setup so that the loader shows until the state is changed, but I still get this flickering on reload. This flickering only happens with Supabase. I was using the Firebase version before and it worked perfectly with my code.
Here is a video for reference: https://imgur.com/a/5hywXj5
Edit: Updated code to current version
export default function Navigation() {
const { user, setUser } = useContext(AuthenticatedUserContext);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const session = supabase.auth.session();
setUser(session?.user ?? null);
const { data: listener } = supabase.auth.onAuthStateChange((_: any, session: any) => {
setUser(session?.user ?? null);
});
setIsLoading(false);
return () => {
listener?.unsubscribe();
};
}, []);
if (isLoading) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator color={Theme.colors.purple} size="large" />
</View>
);
}
return (
<NavigationContainer linking={LinkingConfiguration}>{user ? <AppStack /> : <AuthStack />}</NavigationContainer>
);
}
To recap for others, onAuthStateChange will not execute on first page load so you are triggering it using the getUserAuthStatus async function. However session() function is not async and will immediately return a result of null if there is no user session, or return a session that has been stored in localStorage.
In this case the result of the getUserAuthStatus will always return null. Then onAuthStateChange will trigger with the SIGNED_IN event and a session which will then set the user.
Furthermore the onAuthStateChange function should be registered before you perform the session step so as to capture any events triggered. In the current form an event may be triggered directly after the session() call but before the handler is registered.
So to recap the rendering steps will be:
Step 1
isLoading: true
user: null
Step 2
isLoading: false
user: null
Step 3
isLoading false
user: {...}
So far as I can tell, using session directly without thinking it's async will do the trick.
Ok, Supabase has released some updates since I first asked this question. Here is how I am now able to stop flickering when loading the application.
First, we need to set up our AuthContext for our application. Be sure to wrap your App.tsx with the <AuthContextProvider>.
AuthContext.tsx
import React, { createContext, useContext, useEffect, useState } from 'react';
import { Session, User } from '#supabase/supabase-js';
import { supabase } from '../config/supabase';
export const AuthContext = createContext<{ user: User | null; session: Session | null }>({
user: null,
session: null,
});
export const AuthContextProvider = (props: any) => {
const [userSession, setUserSession] = useState<Session | null>(null);
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setUserSession(session);
setUser(session?.user ?? null);
});
const { data: authListener } = supabase.auth.onAuthStateChange(async (event, session) => {
console.log(`Supabase auth event: ${event}`);
setUserSession(session);
setUser(session?.user ?? null);
});
return () => {
authListener.subscription;
};
}, []);
const value = {
userSession,
user,
};
return <AuthContext.Provider value={value} {...props} />;
};
export const useUser = () => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useUser must be used within a AuthContextProvider.');
}
return context;
};
Now, if you're using React Navigation like me we need to check if we have a valid user to send them to the logged-in home screen. Here's how I do it.
Navigation.tsx
export default function Navigation() {
const { user } = useUser();
return (
<NavigationContainer linking={LinkingConfiguration}>
{user ? <AppStackNavigator /> : <AuthStackNavigator />}
</NavigationContainer>
);
}
Here Below my code I would like to retrieve all data before starting the render of my component, is there any way to do that in react ? I guess it's maybe a simple code line but as I'm new in coding I still don't know all react components behavior. Thanks for your answer.
import { useState, useEffect } from "react";
import axios from "axios";
import Cookies from "js-cookie";
// import material ui
import CircularProgress from "#mui/material/CircularProgress";
import Box from "#mui/material/Box";
// import config file
import { SERVER_URL } from "../../configEnv";
const Products = ({ catList }) => {
// catList is data coming from app.js file in format Array[objects...]
console.log("catList ==>", catList);
const [isLoading, setIsLoading] = useState(true);
const [dataSku, setDataSku] = useState([]);
console.log("datasku ==>", dataSku);
const tab = [];
useEffect(() => {
// Based on the catList tab I fetch additionnal data linked with each object of catList array
catList.slice(0, 2).forEach(async (element) => {
const { data } = await axios.post(`${SERVER_URL}/products`, {
product_skus: element.product_skus,
});
// The result I receive from the call is an array of objects that I push inside the Tab variable
tab.push({ name: element.name, content: data });
setDataSku(tab);
console.log("tab ==>", tab);
setIsLoading(false);
});
}, [catList]);
return isLoading ? (
<Box sx={{ display: "flex" }}>
{console.log("there")}
<CircularProgress />
</Box>
) : (
<div className="products-container">
<div>LEFT BAR</div>
<div>
{dataSku.map((elem) => {
return (
<div>
<h2>{elem.name}</h2>
</div>
);
})}
</div>
</div>
);
};
export default Products; ```
#Jessy use your loading state to fetch data once,
In your useEffect, check for loading,
useEffect(() => {
if(loading) {
catList.slice(0, 2).forEach(async (element) => {
const { data } = await axios.post(`${SERVER_URL}/products`, {
product_skus: element.product_skus,
});
tab.push({ name: element.name, content: data });
setDataSku(tab);
console.log("tab ==>", tab);
setIsLoading(false);
});
}
}, [catList]);`
I finally managed to displayed all results by adding this condition on the isLoading
if (tab.length === catList.length) {
setIsLoading(false);
}
Many thanks guys for your insight :)
My UI was working fine until it was using a class component. Now I am refactoring it to a functional component.
I have to load my UI based on the data I receive from an API handler. My UI will reflect the state of the camera which is present inside a room. Every time the camera is turned on or off from the room, I should receive the new state from the API apiToGetCameraState.
I want the console.log present inside the registerVideoStateUpdateHandlerWrapper to print both on UI load for the first time and also to load every time the video state is changed in the room. However, it doesn't work when the UI is loaded for the first time.
This is how my component looks like:
const Home: React.FunctionComponent<{}> = React.memo(() => {
const [video, setToggleVideo] = React.useState(true);
const registerVideoStateUpdateHandlerWrapper = React.useCallback(() => {
apiToGetCameraState(
(videoState: boolean) => {
// this log does not show up when the UI is loaded for the first time
console.log(
`Video value before updating the state: ${video} and new state is: ${videoState} `
);
setToggleVideo(videoState);
}
);
}, [video]);
React.useEffect(() => {
//this is getting called when the app loads
alert(`Inside use effect for Home component`);
registerVideoStateUpdateHandlerWrapper ();
}, [registerVideoStateUpdateHandlerWrapper ]);
return (
<Grid>
<Camera
isVideoOn={video}
/>
</Grid>
);
});
This was working fine when my code was in class component. This is how the class component looked like.
class Home extends Component {
registerVideoStateUpdateHandlerWrapper = () => {
apiToGetCameraState((videoState) => {
console.log(`ToggleVideo value before updating the state: ${this.state.toggleCamera} and new state is: ${videoState}`);
this.setStateWrapper(videoState.toString());
})
}
setStateWrapper = (toggleCameraUpdated) => {
console.log("Inside setStateWrapper with toggleCameraUpdated:" + toggleCameraUpdated);
this.setState({
toggleCamera: (toggleCameraUpdated === "true" ) ? "on" : "off",
});
}
constructor(props) {
super(props);
this.state = {
toggleCamera: false,
};
}
componentDidMount() {
console.log(`Inside componentDidMount with toggleCamera: ${this.state.toggleCamera}`)
this.registerVideoStateUpdateHandlerWrapper ();
}
render() {
return (
<div>
<Grid>
<Camera isVideoOn={this.state.toggleCamera} />
</Grid>
);
}
}
What all did I try?
I tried removing the useCallback in the registerVideoStateUpdateHandlerWrapper function and also the dependency array from React.useEffect and registerVideoStateUpdateHandlerWrapper. It behaved the same
I tried updating the React.useEffect to have the code of registerVideoStateUpdateHandlerWrapper in it but still no success.
Move registerVideoStateUpdateHandlerWrapper() inside the useEffect() callback like this. If you want to log the previous state when the state changes, you should use a functional update to avoid capturing the previous state through the closure:
const Home = () => {
const [video, setVideo] = useState(false);
useEffect(() => {
console.log('Inside useEffect (componentDidMount)');
const registerVideoStateUpdateHandlerWrapper = () => {
apiToGetCameraState((videoState) => {
setVideo((prevVideo) => {
console.log(`Video value before updating the state: ${prevVideo} and new state is: ${videoState}`);
return videoState;
});
});
};
registerVideoStateUpdateHandlerWrapper();
}, []);
return (
<Grid>
<Camera isVideoOn={video} />
</Grid>
);
};
When you no longer actually need to log the previous state, you should simplify registerVideoStateUpdateHandlerWrapper() to:
const registerVideoStateUpdateHandlerWrapper = () => {
apiToGetCameraState((videoState) => {
setVideo(videoState);
});
};
import React from 'react'
const Home = () => {
const [video, setVideo] = useState(null);
//default video is null, when first load video will change to boolean, when the Camera component will rerender
const registerVideoStateUpdateHandlerWrapper = () => {
apiToGetCameraState((videoState) => {
setVideo(videoState);
});
};
useEffect(() => {
registerVideoStateUpdateHandlerWrapper();
}, []);
return (
<Grid>
<Camera isVideoOn={video} />
</Grid>
);
};
export default Home
componentDidMount() === useEffect()
'useEffect' => import from 'react'
// componentDidMount()
useEffect(() => {
// Implement your code here
}, [])
// componentDidUpdate()
useEffect(() => {
// Implement your code here
}, [ update based on the props, state in here if you mention ])
e.g:
const [loggedIn, setLoggedIn] = useState(false);
useEffect(() => {
// Implement the code here
}, [ loggedIn ]);
the above code will act as equivalent to the componentDidUpdate based on 'loggedIn' state