Function call in non relation component in react js - javascript

ProductTable.js
import React, { useEffect, useState } from "react";
function ProductTable() {
useEffect(() => {
fetchData();
}
const fetchData = () => {
axios
.get("http://localhost:4000/api/products/product/viewAllProduct")
.then((res) => {
const getData = res.data.data;
console.log(getData);
setData(getData);
});
};
return ( jsx..)
}
export default ProductTable;
**ProductModals.js
**
``import React, { useEffect, useState } from "react";
function ProductModals(){
const handleSubmit=()=>{
.....
}
return (jsx..)
export default productModals;`
viewProduct.js
import React, { useEffect, useState } from "react";
import ProductTable from "../../Components/Tables/Product/ProductTable";
import ProductModals from "../../Components/Modals/Product/ProductModals";
function viewProduct(){
return(
<productModals/>
<productTable/>
)
}
export default viewProduct;
I need to to get fetchDatafunction from productTable.js component to productModal.js component. both components parent component is viewProduct.js. I tried many ways. but could not work. In productModal.js component has a function for form submit , when form submit done I need to call fetchData function, If anyone know the way please help me

you can do this through react hooks. First, use fetchData into the parent component to pass data into both child components, If needed. Also Once submit the form call fetchData function through it and Update through setData props. So its parent state is also updated.
viewProduct.js
import React, { useEffect, useState } from "react";
import ProductTable from "../../Components/Tables/Product/ProductTable";
import ProductModals from "../../Components/Modals/Product/ProductModals";
useEffect(() => {
fetchData();
}
const fetchData = () => {
axios
.get("http://localhost:4000/api/products/product/viewAllProduct")
.then((res) => {
const getData = res.data.data;
console.log(getData);
setData(getData);
});
};
return ( jsx..)
}
function viewProduct(){
return(
<productModals data={data} setData={setData}/>
<productTable data={data}/>
)
}
export default viewProduct;
productModel.js
Now, Submit your form and call the function recall inside and after getting response update through setData props.
const fetchData = () => {
axios
.get("http://localhost:4000/api/products/product/viewAllProduct")
.then((res) => {
const getData = res.data.data;
console.log(getData);
setData(getData);
});
};
const recall=useCallback(()=>fetchData(),[])

If the scenario is as simple as the example you have provided you can get away with lifting state up, but if you have many nested child components that needs to read/set these states you can use context, as suggested by Rajesh.
Lifting state up
To "lift state up" create a state in the parent component ViewProduct and pass along the relevant variables to the respective components via props:
function ViewProduct(){
const [done, setDone] = useState(false);
return(
<productModals setDone={setDone} />
<productTable done={done} />
)
}
Use the passed in prop setDone in your handleSubmit function to update the state:
function ProductModals({setDone}){
const handleSubmit=()=>{
.....
setDone(true);
}
And in ProductTable make useEffect dependant on done to only fetch when it is true:
function ProductTable({done}) {
useEffect(() => {
if (!done) return;
fetchData();
}, [done]);
const fetchData = () => {
axios
.get(...
Reuse state
If you need to reuse the done state, you can reset it by passing setDone to ProductTable and set it back to false when fetching data:
function ProductTable({done, setDone}) {
useEffect(() => {
if (!done) return;
fetchData();
setDone(false);
}, [done]);
const fetchData = () => {
axios
.get(...

Related

How to prevent useEffect() run twice after running a function in context consumer and prevent useContext() to re-render

I'm learned that React will re-render after state changed e.g. setState from useState(), calling the function or variable from useContext() variable. But now I'm don't understand that why I get the ESLint warning call the context function inside the useCallback() without dependency in the list. If I put the dependency in the list, useCallback() will be re-rendered and useEffect() dependency from useCallback() variable will do again. So how to fix the react-hooks/exhaustive-deps when calling the function inside the useContext() variable?
Auth.js
import React, { createContext, useContext, useEffect, useMemo, useState } from "react";
import * as AuthAPI from "../API/AuthAPI"
import Loading from "../Page/Loading"
const AuthContext = createContext()
export const AuthProvider = ({children}) => {
const [user,setUser] = useState()
const [loadingInitial,setLoadingInitial] = useState(true)
useEffect(()=>{
AuthAPI.getCurrentUser()
.then((user)=>setUser(user))
.catch((error)=>{console.log(error)})
.finally(()=>setLoadingInitial(false))
},[])
const login = async (email,password) => {
const user = await AuthAPI.login({email,password})
setUser(user)
return user
}
const register = async (firstname,lastname,email,password) => {
const user = await AuthAPI.register({firstname,lastname,email,password})
setUser(user)
return user
}
const logout = async () => {
const response = await AuthAPI.logout()
setUser(undefined)
}
const value = useMemo(()=>({
user,
setUser,
login,
register,
logout
}),[user])
return (
<AuthContext.Provider value={value}>
{loadingInitial ? <Loading/> : children}
</AuthContext.Provider>
)
}
export const useAuth = () => {
return useContext(AuthContext)
}
Logout.js
import { useCallback, useEffect, useState } from "react";
import { Navigate, useLocation, useNavigate } from "react-router-dom";
import { useAuth } from "../Hooks/Auth";
import * as AuthAPI from "../API/AuthAPI"
import Loading from "./Loading";
function Logout() {
const auth = useAuth()
const location = useLocation()
const navigate = useNavigate()
const [isLoggedOut,setIsLoggedOut] = useState(false)
const logout = useCallback(async () => {
console.log("Logging out!")
await AuthAPI.logout()
auth.setUser((prevState)=>(undefined))
setIsLoggedOut(true)
},[auth]) // --> re-rendered bacause `auth` context in re-rendered when set `user` state.
useEffect(()=>{
logout()
},[logout]) // --> this also to run again from `logout` callback is being re-rendered.
if (!isLoggedOut) {
return <Loading/>
}
return (
<Navigate to="/login" replace/>
)
}
export default Logout
Any help is appreciated.
How about destructuring your auth context, since you are only using setUser inside useEffect?
const { setUser } = useAuth()
useEffect(() => {
....
}, [setUser])
There is no need for creating a memoized logout callback function if logout isn't used/passed as a callback function. Just apply the logging out logic in the useEffect hook.
Render the Loading component and issue the imperative redirect from the resolved Promise chain of the return AuthAPI.logout Promise.
Example:
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { useAuth } from "../Hooks/Auth";
import * as AuthAPI from "../API/AuthAPI"
import Loading from "./Loading";
function Logout() {
const auth = useAuth();
const navigate = useNavigate();
useEffect(() => {
console.log("Logging out!");
AuthAPI.logout()
.then(() => auth.setUser(undefined))
.finally(() => navigate("/login", { replace: true }));
}, []);
return <Loading />;
}
export default Logout;
Can you try to replace your useEffect code into this:
useEffect(logout, [])

React useEffect and React-Query useQuery issue?

I'm still new to React so forgive me if this is a silly approach to this problem.
My goal: Global error handling using a context provider and a custom hook.
The Problem: I can't remove errors without them immediately being re-added.
I display my errors via this component in the shell...
import React, { useState, useEffect } from 'react'
import Alert from '#mui/material/Alert'
import Collapse from '#mui/material/Collapse'
import { useAlertContext } from '#/context/alert-context/alert-context'
export default function AppAlert () {
const [show, setShow] = useState(false)
const alertContext = useAlertContext()
const handleClose = () => {
alertContext.remove()
setShow(false)
}
useEffect(() => {
if (alertContext.alert) {
setShow(true)
}
}, [alertContext.alert])
return (
<Collapse in={show}>
<Alert severity='error' onClose={handleClose}>
{alertContext.alert}
</Alert>
</Collapse>
)
}
I have a provider setup that also exposes a custom hook...
import React, { useState, createContext, useContext } from 'react'
const AlertContext = createContext()
const AlertProvider = ({ children }) => {
const [alert, setAlert] = useState(null)
const removeAlert = () => setAlert(null)
const addAlert = (message) => setAlert(message)
return (
<AlertContext.Provider value={{
alert,
add: addAlert,
remove: removeAlert
}}
>
{children}
</AlertContext.Provider>
)
}
const useAlertContext = () => {
return useContext(AlertContext)
}
export {
AlertProvider as default,
useAlertContext
}
And finally I have a hook setup to hit an API and call throw errors if it any occur while fetching the data. I'm purposely triggering a 404 by passing a bad API path.
import { useEffect } from 'react'
import { useQuery } from 'react-query'
import ApiV4 from '#/services/api/v4/base'
import { useAlertContext } from '#/context/alert-context/alert-context'
export const useAccess = () => {
const alertContext = useAlertContext()
const route = '/accessx'
const query = useQuery(route, async () => await ApiV4.get(route), {
retry: 0
})
useEffect(() => {
if (query.isError) {
alertContext.add(query.error.toString())
}
}, [alertContext, query.isError, query.error])
return query
}
This code seems to be the issue. Because alertContext.remove() triggers useEffect here and query.error still exists, it immediately re-adds the error to the page on remove. Removing alertContext from the array works, but it is not a real fix and linter yells.
useEffect(() => {
if (query.isError) {
alertContext.add(query.error.toString())
}
}, [alertContext, query.isError, query.error])
This is a perfectly fine approach to the problem. You've also accurately identified the problem. The solution is to create a second hook with access to the methods that will modify the context. AppAlert needs access to the data in the context, and needs to update when AlertContext.alert changes. UseAccess only needs to be able to call AlertContext.add, and that method wont change and trigger a re-render. This can be done with a second Context. You can just expose one Provider and bake the actions provider into the outer context provider.
import React, { useState, createContext, useContext } from 'react'
const AlertContext = createContext()
const AlertContextActions = createContext()
const AlertProvider = ({ children }) => {
const [alert, setAlert] = useState(null)
const removeAlert = () => setAlert(null)
const addAlert = (message) => setAlert(message)
return (
<AlertContext.Provider value={{ alert }}>
<AlertContextActions.Provider value={{ addAlert, removeAlert }}>
{children}
</AlertContextActions.Provider>
</AlertContext.Provider>
)
}
const useAlertContext = () => {
return useContext(AlertContext)
}
export {
AlertProvider as default,
useAlertContext
}
Now, where you need access to the alert you use one hook and where you need access to the actions you use the other.
// in AppAlert
import { useAlertContext, useAlertContextActions } from '#/context/alert-context/alert-context'
...
const { alert } = useAlertContext()
const { removeAlert } = useAlertContextActions()
And finally
// in useAccess
import { useAlertContextActions } from '#/context/alert-context/alert-context'
...
const { addAlert } = useAlertContextActions()
So I found a solution that seems to work for my purposes. I got a hint from this article. https://mortenbarklund.com/blog/react-architecture-provider-pattern/
Note the use of useCallback above. It ensures minimal re-renders of components using this context, as the function is guaranteed to be stable (as its memoized without dependencies).
So with this I tried the following and it solved the problem.
import React, { useState, createContext, useContext, useCallback } from 'react'
const AlertContext = createContext()
const AlertProvider = ({ children }) => {
const [alert, setAlert] = useState(null)
const removeAlert = useCallback(() => setAlert(null), [])
const addAlert = useCallback((message) => setAlert(message), [])
return (
<AlertContext.Provider value={{
alert,
add: addAlert,
remove: removeAlert
}}
>
{children}
</AlertContext.Provider>
)
}
const useAlertContext = () => {
return useContext(AlertContext)
}
export {
AlertProvider as default,
useAlertContext
}
My goal: Global error handling
One problem with the above useEffect approach is that every invocation of useAccess will run their own effects. So if you have useAccess twice on the page, and it fails, you will get two alerts, so it's not really "global".
I would encourage you to look into the global callbacks on the QueryCache in react-query. They are made for this exact use-case: To globally handle errors. Note that to use context, you would need to create the queryClient inside the Application, and make it "stable" with either useRef or useState:
function App() {
const alertContext = useAlertContext()
const [queryClient] = React.useState(() => new QueryClient({
queryCache: new QueryCache({
onError: (error) =>
alertContext.add(error.toString())
}),
}))
return (
<QueryClientProvider client={queryClient}>
<RestOfMyApp />
</QueryClientProvider>
)
}
I also have some examples in my blog.

How to test react functional component async call

I have a functional component. Inside the component, I have called SpecialistsListService service. The service called the API via Axios. I have to test the async function getSpecialistsList and useEffect functions but I don't do that anyone helps me to solve the problem. When I used class component I simply call the method like await wrapper.instance.getSpecialistsList() then check the state but the functional component approach are different I think.
import React, { useState, useEffect } from "react";
import SpecialistsListService from "../../../services/specialists";
import SpecialistsPageView from "./SpecialistsPageView";
import "./index.scss";
export default function SpecialistsPage() {
const [specialistsList, setSpecialistsList] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const specialistsListService = new SpecialistsListService();
useEffect(() => {
getSpecialistsList();
}, []);
async function getSpecialistsList() {
const specialistsListData = await specialistsListService.getSpecialistsList();
setSpecialistsList(specialistsListData);
setIsLoading(false);
}
return (
<SpecialistsPageView isLoading={isLoading} specialists={specialistsList} />
);
}
Splitting your component into custom hooks and component make your life easier to test and more readable by splitting UI and logic.
The custom hooks will look like this
useSpecialistsList.js
import { useState, useEffect } from "react";
const useSpecialistsList = (specialistsListService) => {
const [specialistsList, setSpecialistsList] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
getSpecialistsList();
}, []);
async function getSpecialistsList() {
const specialistsListData = await specialistsListService.getSpecialistsList();
setSpecialistsList(specialistsListData);
setIsLoading(false);
}
return {
isLoading: isLoading,
specialistsList: specialistsList
}
}
export default useSpecialistsList;
The component look like this
import React from "react";
import SpecialistsListService from "../../../services/specialists";
import SpecialistsPageView from "./SpecialistsPageView";
import useSpecialistsList from "./useSpecialistsList";
import "./index.scss";
export default function SpecialistsPage() {
const {isLoading, specialistsList} = useSpecialistsList(new SpecialistsListService());
return (
<SpecialistsPageView isLoading={isLoading} specialists={specialistsList} />
);
}
Now you can test your hooks using "#testing-library/react-hooks"
Test will look like this
import {renderHook} from "#testing-library/react-hooks";
import useSpecialistsList from "./useSpecialistsList";
import SpecialistsListService from "../../../services/specialists";
describe("useSpecialistsList", ()=>{
it('Should return userDetails loading as false', async ()=> {
const {result, waitForNextUpdate} = renderHook(()=> useSpecialistsList(new SpecialistsListService()));
expect(result.current.isLoading).toEqual(true);
await waitForNextUpdate();
expect(result.current.isLoading).toEqual(false);
});
})
Here waitForNextUpdate call the useEffect (Generally update the component)
To read more about testing custom hooks use this like

How to call useState from another Page?

Bascially whenever I deleted an item in my handleDelete function it would route back to the homePage and I wanted to display a message that says your product succesully deleted for about 5 seconds.
In my index.js I first set message to false. and inside my ProductAttribute whenever I click it the set message will be true and will show the message in Index.js/ in my UI.
my handleDelete function
import React, { useState } from "react";
import { Header, Button, Modal } from "semantic-ui-react";
import axios from "axios";
import baseUrl from "../../utils/baseUrl";
import { useRouter } from "next/router";
function ProductAttributes({ description, _id }) {
const [modal, setModal] = useState(false);
const router = useRouter();
async function handleDelete() {
const url = `${baseUrl}/api/product`;
const payload = { params: { _id } };
await axios.delete(url, payload);
router.push("/");
setMessage(true);
setTimeout(function () {
setMessage(false);
}, 5000);
}
while in my Index.js. The setMessage in my useState isn't getting called from ProductAttributes file.
import React, { useEffect, useState } from "react";
import axios from "axios";
import ProductList from "../components/Index/ProductList";
import baseUrl from "../utils/baseUrl";
import { Message, Container } from "semantic-ui-react";
function Home({ products }) {
const [message, setMessage] = useState(false);
return (
<>
<Container>
{message ? (
<Message
deleted
icon="checked"
color="red"
content=" Product Successfully Deleted"
/>
) : (
""
)}
</Container>
<ProductList products={products}></ProductList>
</>
);
}
How can I make this setMessagebe callable in ProductAttributes? am I doing it right with the Parent to Child Relation or should I bring the useState in the child to parent?
You can create an handler in the Home Component like this
const handleSetMessage = (message) => {
setMessage(message)
}
this handler will be responsible of updating the value of message state in the Home component. and this methode you can pass it as props to ProductList component which will also pass it down to ProductAttribute. This will force you to pass props till the lowest level in your APP where you need to call that method.
Or you can take advantage of Context API which will allow you to have access to that method without passing it down as props.
const MessageContext = React.createContext("");
And in the Home component you use that Context like this
function Home () {
const [message, setMessage] = useState('');
const handleSetMessage = () => {
setMessage(true)
}
return <MessageContext.Provider> value={{setMessage: handleSetMessage}}>
// The code which render the component child goes here.
</MessageContext.Provider>
}
After that in your ProductAttribute Component you access to that setMessage function like this
import React, { useContext} from 'react';
const ProductAttribute = (props) => {
const { setMessage } = useContext(MessageContext);
const handleDelete = async () => {
// Here you call the setMessage function which will update state in the `Home` Component
setMessage();
}
return <div>
</div>
}
How can I make this setMessagebe callable in ProductAttributes?
A good practice would encompass you creating a handler function which delegates to the setState function and passing the reference of this function to ProductAttributes as props.
this is an example:
const [counter, setCounter] = useState(0);
const handleIncrementCounter = () => setCounter(counter + 1);
<ChildComponent handleIncrementCounter ={handleIncrementCounter }/>
then in ChildComponent..
function ChildComponent(props) {
return (
<button onClick={props.handleIncrementCounter}/>
);
}
Once you show you a message, wait for 5sec to close the message and redirect back to the home directory. just place the route.push('/') inside setTimeout so it will wait for 5sec to be redirected.
async function handleDelete() {
const url = `${baseUrl}/api/product`;
const payload = { params: { _id } };
await axios.delete(url, payload);
setMessage(true);
setTimeout(function () {
router.push("/");
setMessage(false);
}, 5000);
}

React custom hooks in callback

I'm trying to use my custom hook inside the callback logic like this:
import React, { useEffect, useState } from 'react';
import useDataChange from '../../hooks/useDataChange';
const SomeComponent = () => {
return (
<Table
handleTableChange={data => useDataChange(data)}
/>
);
};
export default SomeComponent;
And my custom hooks (just to simplify) looks like that:
const useDataChange = data => {
console.log(data);
};
export default useDataChange;
In short, custom hook supposed to be fired when data from table is changed (ie. when handleTableChange in Table component is fired). Instead I'm getting:
React Hook "useDataChange" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks
How can I use it when table data is changed?
The key to understanding hooks is to extract pieces of react code out of components. So your first step would be to get it working inside the component
const SomeComponent = () => {
const [data, setData] = useState([])
return (
<Table
handleTableChange={setData}
/>
);
};
Based on your code, I'm not seeing where you'd need a hook or side effect. But let's pretend that you do want to run some simple side effect:
const SomeComponent = () => {
const [data, setData] = useState([])
const [modifiedData, setModifiedData] = useState([])
useEffect(() => {
//here we're just going to save the current data stream into a new state variable for simplicity
setModifiedData(data)
}, [data])
return (
<Table
handleTableChange={setData}
data={modifiedData}
/>
);
};
So now we have some logic that runs a side effect. Now you can extract it to its own hook.
const useModifiedData = (data) => {
const [modifiedData, setModifiedData] = useState(data)
useEffect(() => {
setModifiedData(data)
}, [data])
return modifiedData
}
const SomeComponent = () => {
const [data, setData] = useState([])
const modifiedData = useModifiedData(data)
return (
<Table
handleTableChange={setData}
data={modifiedData}
/>
);
};
Here you have a hook that lives outside the component logic, so it can now go in its own file and be used across your project.
Like it says React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks. React has this limitation so that it can track the state and effects. In your case you can define you custom hook to return a function which does the desired work, instead of directly doing it in your hook.
In this case your custom hook file will look something like this-
const useDataChange = () => data => {
console.log(data);
};
export default useDataChange;
Then in your component you can use it like this -
import React, { useEffect, useState } from 'react';
import useDataChange from '../../hooks/useDataChange';
const SomeComponent = () => {
const callback = useDataChnage();
return (
<Table handleTableChange={callbackdata} />
);
};
export default SomeComponent;

Categories

Resources