React custom hooks in callback - javascript

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;

Related

Function call in non relation component in react js

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(...

Save value from custom hook to state

I am trying to save a value from a custom hook, which is fetching data for the server, to functional component state with useState, because I later need to change this value and after the change it needs to rerender. So desired behaviour is:
Set State variable to value from custom hook
Render stuff with this state variable
Modify state on button click
Rerender with new state
What I tried is:
Set the inital value of useState to my hook:
const [data, setData] = useState<DataType[] | null>(useLoadData(id).data)
but then data is always empty.
Set the state in a useEffect() hook:
useEffect(()=>{
const d = useLoadData(id).data
setData(d)
}, [id])
But this is showing me the Error warning: Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
Doing this:
const [data, setData] = useState<DocumentType[]>([])
const dataFromServer = useLoadData(id).data
useEffect(()=>{
if (dataFromServer){
setData(dataFromServer)
}
}, [dataFromServer])
Leading to: ERROR: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
What would be a proper solution for my use case?
It looks like your custom hook returns a new array every time it is used.
Solution 1: change your hook to return a 'cached' instance of an array.
function useLoadData(id) {
const [data, setData] = useState([]);
useEffect(() => {
loadData(id).then(setData);
}, [id]);
// good
return data;
//bad
//return data.map(...)
//bad
//return data.filter(...)
//etc
}
codesandbox.io link
Solution 2: change your hook to accept setData as a parameter.
function useLoadData(id, setData) {
useEffect(() => {
loadData(id).then(setData);
}, [id]);
}
Here I am telling the hook where to store data so that both custom hook and a button in a component can write to a same place.
codesandbox.io link
Full example:
import React from "react";
import ReactDOM from "react-dom";
import { useState, useEffect } from "react";
// simulates async data loading
function loadData(id) {
return new Promise((resolve) => setTimeout(resolve, 1000, [id, id, id]));
}
// a specialized 'stateless' version of custom hook
function useLoadData(id, setData) {
useEffect(() => {
loadData(id).then(setData);
}, [id]);
}
function App() {
const [data, setData] = useState(null);
useLoadData(123, setData);
return (
<div>
<div>Data: {data == null ? "Loading..." : data.join()}</div>
<div>
<button onClick={() => setData([456, 456, 456])}>Change data</button>
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("container"));

How to pass state/data from one component to another in React.js (riot api specifically)

I am trying to pull information from one component's API call to then use that data in another API call in a separate component. However, I am unsure how to export and use the data from the first API call in the second component.
App.js
import './App.css';
import FetchMatch from './fetch-match/fetch.match';
import FetchPlayer from './fetch-player/fetch.player';
function App() {
return (
<div className="App">
<h1>Hello world</h1>
<FetchPlayer></FetchPlayer>
<FetchMatch></FetchMatch>
</div>
);
}
export default App;
fetch.player then makes the first API call to get a users specific ID which will be used in the second API call too fetch that users match history.
fetch.player.js
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const FetchPlayer = () => {
const [playerData, setPlayerData] = useState([]);
const userName = 'users name';
const userTagLine = '1234';
const apiKey = '???';
useEffect( () => {
axios.get(`https://americas.api.riotgames.com/riot/account/v1/accounts/by-riot-id/${userName}/${userTagLine}?api_key=${apiKey}`)
.then(response => {
console.log(response.data)
setPlayerData([response.data])
})
.catch(error => console.log(error))
}, []);
return (
<div>
{playerData.map( data => (
<div>
<p>{data.puuid}</p>
<p>{data.gameName}#{data.tagLine}</p>
</div>
))}
</div>
)
}
export default FetchPlayer;
not much here but just in case...
fetch.match.js
import React, { useState } from 'react';
// Somehow take in the puuid set in the state of fetch.player to make a second API call below
const FetchMatch = () => {
const [matchData, setMatchData] = useState([]);
return (
<div>
// players match list goes here
</div>
)
}
export default FetchMatch;
I am unsure if I should make a separate function instead which would allow me to create consts to handle both API calls in a single file. Or if there is a way to pass the state from fetch.player as a prop to fetch.match from App.js. I have tried to do the former but it either doesn't work or I am messing up the syntax (most likely this)
If you render both component parallelly in a parent component, they are called sibling components.
Data sharing in sibling components can be done by multiple ways (Redux, Context etc) but the easiest and simplest way (the most basic way without 3rd party API) involves the use of parent as a middle component.
First you create the state in the parent component and provide it as props to the child component which need the data from its sibling (in your case is FetchMatch).
import React from 'react';
import './App.css';
import FetchMatch from './fetch-match/fetch.match';
import FetchPlayer from './fetch-player/fetch.player';
function App() {
const [data,setData] = React.useState();
return (
<div className="App">
<h1>Hello world</h1>
<FetchPlayer></FetchPlayer>
<FetchMatch data={data} ></FetchMatch>
</div>
);
}
export default App;
Provide the function to setData as a props to the child component which will fetch the initial API (in your case is FetchPlayer)
<FetchPlayer onPlayerLoad={(data) => setData(data)} />
Then, in that child component when you finish calling the API and get the result, pass that result to the onPlayerLoad function which will call the setData function with the result as parameters. It will lead to state change and re-rendering of the second FetchMatch component feeding the props data with API results.
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const FetchPlayer = ({onPlayerLoad}) => {
const [playerData, setPlayerData] = useState([]);
const userName = 'users name';
const userTagLine = '1234';
const apiKey = '???';
useEffect( () => {
axios.get(`https://americas.api.riotgames.com/riot/account/v1/accounts/by-riot-id/${userName}/${userTagLine}?api_key=${apiKey}`)
.then(response => {
console.log(response.data)
setPlayerData([response.data])
onPlayerLoad(response.data)
})
.catch(error => console.log(error))
}, []);
return <></>;
Coming to FetchMatch, you will have the data in its second rendering.
import React, { useState } from 'react';
// Somehow take in the puuid set in the state of fetch.player to make a second API call below
const FetchMatch = ({data}) => {
const [matchData, setMatchData] = useState([]);
//console.log(data);
return (
<div>
// players match list goes here
</div>
)
}
export default FetchMatch;
Now, you can do whatever you want with the shared data in second component which in your case is trigger match API. 🎉

React JS useState initial value not updating inside JS fetch API

I am novice to React JS. I have useState and fetchAPI inside contextAPI hooks but the initial state is not updating.
// code
import React,{useState, createContext} from 'react'
export const contextApi = createContext()
export const ContextApiProvider = (props) => {
const [query, setQuery] = useState('chicken')
const [recipes, setRecipes] = useState([])
const api_props = {
APP_ID: '84cf712e',
APP_KEY:'asdcb2b8b842f3e543casjakfa710de4fb343592a64d',
APP_QUERY: query
}
fetch(`https://api.edamam.com/search?q=${api_props.APP_QUERY}&app_id=${api_props.APP_ID}&app_key=${api_props.APP_KEY}`)
.then(res => res.json()).then(data => setRecipes(data.hits))
return (
<contextApi.Provider value={{recipes}}>
{props.children}
</contextApi.Provider>
)
}
First look up the useEffect hook that is where you want to do your data fetching. From there you could set the state using the setState hook that you are running. This might create an endless loop because your are setting state which reruns the component which then trys to set state again.
Hope that helps let me know if you have questions.

Can I use useReducer from outside component

Now I'm trying to use useReducer to created a new way for management state and function but now found the problem is "Hooks can only be called inside of the body of a function component"
Is there any way to solve this problem?
// App Component
import React from "react";
import { product, productDis } from "./ProductReducer";
//{product} is state, {productDis} is dispatch
import { total } from "./TotalReducer";
//{total} is state and i dont need {totalDis}
const App = () => {
return (
<div>
<button onClick={()=>productDis({type:'add',payload:'pen'})}>add</button>
{product} {total}
</div>
);
};
export default App;
// ProductReducer Component
import React, { useReducer } from 'react';
import {totalDis} from './TotalReducer'
//{totalDis} is dispatch and i dont need {total}
export const [product, productDis] = useReducer((state, action) => {
switch (action.type) {
case "add": {
const product_0 = 'pencil'
const product_1 = `${action.payload} and ${product_0}`
totalDis({
type:'total_add',
payload:'250'
})
return product_1;
}
default:
return state;
}
}, []);
// TotalReducer Component
import React, { useReducer } from 'react';
export const [total, totalDis] = useReducer((total, action) => {
switch (action.type) {
case "total_add": {
const vat = action.payload*1.15
return vat;
}
default:
return total;
}
}, 0)
when i click the button on display It should be shown..." pen and pencil 287.5 "
but it show "Hooks can only be called inside of the body of a function component"
there any way to solve this problem? or i should back to nature?
React hooks should be called only inside functional components. Hook state is maintained per component instance. If hooks have to be reused, they can be extracted into custom hooks, which are functions that call built-in hooks and are supposed to be called inside functional components:
export const useTotal = () => {
const [total, totalDis] = useReducer((total, action) => {...}, 0);
...
return [total, totalDis];
};
In case there's a need to maintain common state for multiple components it should be maintained in common parent and be provided to children through props:
const Root = () => (
const [total, totalDispatcher] = useTotal();
return <App {...{total, totalDispatcher}}/>
);
const App = props => {
return (
<div>{props.total}</div>
);
};
Or context API:
const TotalContext = createContext();
const Root = () => (
<TotalContext.Provider value={useTotal()}>
<App/>
</TotalContext.Provider>
);
const App = () => {
const [total] = useContext(TotalContext);
return (
<div>{total}</div>
);
};
With useEnhancedReducer hook introduced here which returns getState function.
You will have something like.
const [state, dispatch, getState] = useEnahancedReducer(reducer, initState)
Because dispatch, getState will never change, they can be used in some hooks without their appearance in the dependence list, they can be stored somewhere else (outside of react) to to be called at anytime, from anywhere.
There is also version of useEnhancedReducer which supports adding middleware, in the same article.
From the docs,
There are three common reasons you might be seeing it:
You might have mismatching versions of React and React DOM.
You might be breaking the Rules of Hooks.
You might have more than one copy of React in the same app.
Deep drive to the docs. I hope, you'll be able to resolve the issue. Especially see:
Breaking the Rules of Hooks:
function Counter() {
// ✅ Good: top-level in a function component
const [count, setCount] = useState(0);
// ...
}
function useWindowWidth() {
// ✅ Good: top-level in a custom Hook
const [width, setWidth] = useState(window.innerWidth);
// ...
}
If you break these rules, you might see this error.
function Bad1() {
function handleClick() {
// 🔴 Bad: inside an event handler (to fix, move it outside!)
const theme = useContext(ThemeContext);
}
// ...
}
function Bad2() {
const style = useMemo(() => {
// 🔴 Bad: inside useMemo (to fix, move it outside!)
const theme = useContext(ThemeContext);
return createStyle(theme);
});
// ...
}
class Bad3 extends React.Component {
render() {
// 🔴 Bad: inside a class component
useEffect(() => {})
// ...
}
}
To conclude, your error seems to be appearing as if you're using reducer inside click handler. Check the example Bad1 to resolve your issue. What I mean here is you shouldn't be doing like this:
onClick={()=>productDis({type:'add',payload:'pen'})}
In the onClick handler, dispatch the action and inside a method use that reducer.

Categories

Resources