I'm building a search field that is fetching from a data base upon users input and I'm struggling a bit. At the moment, it is fetching data in every keystroke, which is not ideal. I have looked at different answers and it seems that the best option is to do this in componentDidUpdate() and get a ref of the input feel to compare this with the current value through a setTimeout().
I have tried this, but I'm still fetching during every keystroke, not sure why? See a sample of the component below:
class ItemsHolder extends Component {
componentDidMount() {
//ensures the page is reloaded at the top when routing
window.scrollTo(0, 0);
this.props.onFetchItems(this.props.search);
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.search !== this.props.search) {
console.log(
this.props.search ===
this.props.searchRef.current.props.value.toUpperCase()
);
setTimeout(() => {
console.log(
this.props.search ===
this.props.searchRef.current.props.value.toUpperCase()
);
if (
this.props.search ===
this.props.searchRef.current.props.value.toUpperCase()
) {
this.props.onFetchItems(this.props.search, this.props.category);
}
}, 500);
}
}
I'm using Redux for state management. Here is the function that is called when fetching items:
export const fetchItemsFromServer = (search) => {
return (dispatch) => {
dispatch(fetchItemsStart());
const query =
search.length === 0 ? '' : `?orderBy="country"&equalTo="${search}"`;
axios
.get('/items.json' + query)
.then((res) => {
const fetchedItems = [];
for (let item in res.data) {
fetchedItems.push({
...res.data[item],
id: item,
});
}
dispatch(fetchItemsSuccess(fetchedItems));
})
.catch((error) => {
dispatch(fetchItemsFail(error));
});
};
};
This is how I'm setting the ref in the search component:
class Search extends Component {
constructor(props) {
super(props);
this.searchInput = React.createRef();
}
componentDidMount() {
this.props.onSetRef(this.searchInput);
}
render() {
return (
<Input
ref={this.searchInput}
toolbar
elementType={this.props.inputC.elementType}
elementConfig={this.props.inputC.elementConfig}
value={this.props.inputC.value}
changed={(event) => this.props.onChangedHandler(event)}
/>
);
}
}
Based on a tutorial I found this should work. For your reference, see the code from this tutorial. I don't see why wouldn't the above work. The only difference is that the tutorial uses hooks.
const Search = React.memo(props => {
const { onLoadIngredients } = props;
const [enteredFilter, setEnteredFilter] = useState('');
const inputRef = useRef();
useEffect(() => {
const timer = setTimeout(() => {
if (enteredFilter === inputRef.current.value) {
const query =
enteredFilter.length === 0
? ''
: `?orderBy="title"&equalTo="${enteredFilter}"`;
fetch(
'https://react-hooks-update.firebaseio.com/ingredients.json' + query
)
.then(response => response.json())
.then(responseData => {
const loadedIngredients = [];
for (const key in responseData) {
loadedIngredients.push({
id: key,
title: responseData[key].title,
amount: responseData[key].amount
});
}
onLoadIngredients(loadedIngredients);
});
}
}, 500);
return () => {
clearTimeout(timer);
};
}, [enteredFilter, onLoadIngredients, inputRef]);
Following recommendation to debounceInput:
import React, { Component } from 'react';
// import classes from './Search.css';
import Input from '../../UI/Input/Input';
// redux
import * as actions from '../../../store/actions/index';
import { connect } from 'react-redux';
class Search extends Component {
componentDidUpdate(prevProps, prevState) {
if (prevProps.search !== this.props.search) {
this.props.onFetchItems(this.props.search, this.props.category);
}
}
debounceInput = (fn, delay) => {
let timerId;
return (...args) => {
clearTimeout(timerId);
timerId = setTimeout(() => fn(...args), delay);
};
};
render() {
return (
<Input
toolbar
elementType={this.props.inputC.elementType}
elementConfig={this.props.inputC.elementConfig}
value={this.props.inputC.value}
changed={(event) =>
this.debounceInput(this.props.onChangedHandler(event), 500)
}
/>
);
}
}
const mapStateToProps = (state) => {
return {
inputC: state.filtersR.inputConfig,
search: state.filtersR.search,
};
};
const mapDispatchToProps = (dispatch) => {
return {
onChangedHandler: (event) => dispatch(actions.inputHandler(event)),
onFetchItems: (search, category) =>
dispatch(actions.fetchItemsFromServer(search, category)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Search);
Here is the final solution after help here:
import React, { Component } from 'react';
// import classes from './Search.css';
import Input from '../../UI/Input/Input';
// redux
import * as actions from '../../../store/actions/index';
import { connect } from 'react-redux';
const debounceInput = (fn, delay) => {
let timerId;
return (...args) => {
clearTimeout(timerId);
timerId = setTimeout(() => fn(...args), delay);
};
};
class Search extends Component {
componentDidUpdate(prevProps, _prevState) {
if (prevProps.search !== this.props.search) {
this.responseHandler();
}
}
responseHandler = debounceInput(() => {
this.props.onFetchItems(this.props.search, this.props.category);
}, 1000);
render() {
return (
<Input
toolbar
elementType={this.props.inputC.elementType}
elementConfig={this.props.inputC.elementConfig}
value={this.props.inputC.value}
changed={(event) => this.props.onChangedHandler(event)}
/>
);
}
}
const mapStateToProps = (state) => {
return {
inputC: state.filtersR.inputConfig,
search: state.filtersR.search,
};
};
const mapDispatchToProps = (dispatch) => {
return {
onChangedHandler: (event) => dispatch(actions.inputHandler(event)),
onFetchItems: (search, category) =>
dispatch(actions.fetchItemsFromServer(search, category)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Search);
You really just need to debounce your input's onChange handler, or better, the function that is actually doing the asynchronous work.
Very simple debouncing higher order function:
const debounce = (fn, delay) => {
let timerId;
return (...args) => {
clearTimeout(timerId);
timerId = setTimeout(() => fn(...args), delay);
}
};
Example Use:
fetchData = debounce(() => fetch(.....).then(....), 500);
componentDidUpdate(.......) {
// input value different, call fetchData
}
<Input
toolbar
elementType={this.props.inputC.elementType}
elementConfig={this.props.inputC.elementConfig}
value={this.props.inputC.value}
changed={this.props.onChangedHandler}
/>
Demo Code
const debounce = (fn, delay) => {
let timerId;
return (...args) => {
clearTimeout(timerId);
timerId = setTimeout(fn, delay, [...args]);
};
};
const fetch = (url, options) => {
console.log("Fetching", url);
return new Promise((resolve) => {
setTimeout(() => {
console.log("Fetch Resolved");
resolve(`response - ${Math.floor(Math.random() * 1000)}`);
}, 2000);
});
};
export default class App extends Component {
state = {
search: "",
response: ""
};
changeHandler = (e) => {
const { value } = e.target;
console.log("search", value);
this.setState({ search: value });
};
fetchData = debounce(() => {
const { search } = this.state;
const query = search.length ? `?orderBy="country"&equalTo="${search}"` : "";
fetch(
"https://react-hooks-update.firebaseio.com/ingredients.json" + query
).then((response) => this.setState({ response }));
}, 500);
componentDidUpdate(prevProps, prevState) {
if (prevState.search !== this.state.search) {
if (this.state.response) {
this.setState({ response: "" });
}
this.fetchData();
}
}
render() {
const { response, search } = this.state;
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<label>
Search
<input type="text" value={search} onChange={this.changeHandler} />
</label>
<div>Debounced Response: {response}</div>
</div>
);
}
}
Related
I have fetch method in useEffect hook:
export const CardDetails = () => {
const [ card, getCardDetails ] = useState();
const { id } = useParams();
useEffect(() => {
fetch(`http://localhost:3001/cards/${id}`)
.then((res) => res.json())
.then((data) => getCardDetails(data))
}, [id])
return (
<DetailsRow data={card} />
)
}
But then inside DetailsRow component this data is not defined, which means that I render this component before data is fetched. How to solve it properly?
Just don't render it when the data is undefined:
export const CardDetails = () => {
const [card, setCard] = useState();
const { id } = useParams();
useEffect(() => {
fetch(`http://localhost:3001/cards/${id}`)
.then((res) => res.json())
.then((data) => setCard(data));
}, [id]);
if (card === undefined) {
return <>Still loading...</>;
}
return <DetailsRow data={card} />;
};
There are 3 ways to not render component if there aren't any data yet.
{data && <Component data={data} />}
Check if(!data) { return null } before render. This method will prevent All component render until there aren't any data.
Use some <Loading /> component and ternar operator inside JSX. In this case you will be able to render all another parts of component which are not needed data -> {data ? <Component data={data} /> : <Loading>}
If you want to display some default data for user instead of a loading spinner while waiting for server data. Here is a code of a react hook which can fetch data before redering.
import { useEffect, useState } from "react"
var receivedData: any = null
type Listener = (state: boolean, data: any) => void
export type Fetcher = () => Promise<any>
type TopFetch = [
loadingStatus: boolean,
data: any,
]
type AddListener = (cb: Listener) => number
type RemoveListener = (id: number) => void
interface ReturnFromTopFetch {
addListener: AddListener,
removeListener: RemoveListener
}
type StartTopFetch = (fetcher: Fetcher) => ReturnFromTopFetch
export const startTopFetch = function (fetcher: Fetcher) {
let receivedData: any = null
let listener: Listener[] = []
function addListener(cb: Listener): number {
if (receivedData) {
cb(false, receivedData)
return 0
}
else {
listener.push(cb)
console.log("listenre:", listener)
return listener.length - 1
}
}
function removeListener(id: number) {
console.log("before remove listener: ", id)
if (id && id >= 0 && id < listener.length) {
listener.splice(id, 1)
}
}
let res = fetcher()
if (typeof res.then === "undefined") {
receivedData = res
}
else {
fetcher().then(
(data: any) => {
receivedData = data
},
).finally(() => {
listener.forEach((cb) => cb(false, receivedData))
})
}
return { addListener, removeListener }
} as StartTopFetch
export const useTopFetch = (listener: ReturnFromTopFetch): TopFetch => {
const [loadingStatus, setLoadingStatus] = useState(true)
useEffect(() => {
const id = listener.addListener((v: boolean, data: any) => {
setLoadingStatus(v)
receivedData = data
})
console.log("add listener")
return () => listener.removeListener(id)
}, [listener])
return [loadingStatus, receivedData]
}
This is what myself needed and couldn't find some simple library so I took some time to code one. it works great and here is a demo:
import { startTopFetch, useTopFetch } from "./topFetch";
// a fakeFetch
const fakeFetch = async () => {
const p = new Promise<object>((resolve, reject) => {
setTimeout(() => {
resolve({ value: "Data from the server" })
}, 1000)
})
return p
}
//Usage: call startTopFetch before your component function and pass a callback function, callback function type: ()=>Promise<any>
const myTopFetch = startTopFetch(fakeFetch)
export const Demo = () => {
const defaultData = { value: "Default Data" }
//In your component , call useTopFetch and pass the return value from startTopFetch.
const [isloading, dataFromServer] = useTopFetch(myTopFetch)
return <>
{isloading ? (
<div>{defaultData.value}</div>
) : (
<div>{dataFromServer.value}</div>
)}
</>
}
Try this:
export const CardDetails = () => {
const [card, setCard] = useState();
const { id } = useParams();
useEffect(() => {
if (!data) {
fetch(`http://localhost:3001/cards/${id}`)
.then((res) => res.json())
.then((data) => setCard(data))
}
}, [id, data]);
return (
<div>
{data && <DetailsRow data={card} />}
{!data && <p>loading...</p>}
</div>
);
};
Given the following React Context Provider. A simple Counter class with 2 methods, stored in the React context.
import { createContext, useContext } from "react";
class Counter {
public count: number = 0;
getCount = () => {
return this.count;
};
incrementCount = () => {
this.count = this.count + 1;
};
}
type CounterContextType = {
counter: Counter;
};
const defaults: CounterContextType = {
counter: new Counter()
};
const CounterContext = createContext<CounterContextType>(defaults);
export const CounterProvider: React.FC = ({ children }) => {
const counter = new Counter();
return (
<CounterContext.Provider
value={{
counter
}}
>
{children}
</CounterContext.Provider>
);
};
export const useCounter = () => {
return useContext(CounterContext);
};
I want to listen to changes in the count property of the Counter instance.
Here is what I have tried:
import { useMemo } from "react";
import { CounterProvider, useCounter } from "./CounterProvider";
const DisplayWithMethod = () => {
const { counter } = useCounter();
return <div>Method: {counter.getCount()}</div>;
};
const DisplayWithProperty = () => {
const { counter } = useCounter();
return <div>Prop: {counter.count}</div>;
};
const DisplayWithMemo = () => {
const { counter } = useCounter();
const val = useMemo(() => counter.count, [counter.count]);
return <div>Memo: {val}</div>;
};
const Button = () => {
const { counter } = useCounter();
return <button onClick={counter.incrementCount}>Increment</button>;
};
export default function App() {
return (
<CounterProvider>
<DisplayWithMethod />
<DisplayWithProperty />
<DisplayWithMemo />
<Button />
</CounterProvider>
);
}
None of these work since the counter instance never changes, so no re renders are triggered. Any idea(s) on how to make this work while keeping a class structure for Counter.
https://codesandbox.io/s/nostalgic-fast-cyflg
The issue is with React is not getting that the count has changed and does not rerender. You can get rid of the issue using useState hook.
You should change type definitions like below and construct counter instances using outputs of useState hook.
import { createContext, useContext, useState } from "react";
type CounterContextType = {
counter: {
count: number;
getCount: () => number;
incrementCount: () => void;
};
};
const defaults: CounterContextType = {
counter: {
count: 0,
getCount: () => 0,
incrementCount: () => undefined
}
};
const CounterContext = createContext<CounterContextType>(defaults);
export const CounterProvider: React.FC = ({ children }) => {
const [count, setCount] = useState<number>(0);
return (
<CounterContext.Provider
value={{
counter: {
count,
getCount: () => count,
incrementCount: () => {
setCount((prevCount) => prevCount + 1);
}
}
}}
>
{children}
</CounterContext.Provider>
);
};
export const useCounter = () => {
return useContext(CounterContext);
};
Code Sandbox
i'm new to react hooks, here i have been converting my project to hooks from classes, i'm getting this kind of message 'Error: Server error
at build_error (actions.js:57)
at eval (actions.js:83)' and 'GET http://127.0.0.1:8000/api/kamera/undefined 404 (Not Found)'
those errors come when i'm changing class to hooks (everything is set correcly using useState and useEffect), any idea ?
class:
initializeCollapses() {
const data = this.props[this.props.action];
let collapseStates = this.state.collapseStates;
if (!data || data.length < 1) {
return;
}
data.map((el) => {
collapseStates["" + el.name + el.identifier] = false;
return;
});
this.setState({
...this.state,
collapseStates: collapseStates,
});
}
componentDidMount() {
this.props.getItems[this.props.action](this.state.actionArgs).then(() => {
this.initializeCollapses();
});
}
Hooks:
const initializeCollapses = () => {
const data = [action];
if (!data || data.length < 1) {
return;
}
data.map((el) => {
collapseStates["" + el.name + el.identifier] = false;
return;
});
setCollapseStates(collapseStates);
};
useEffect(() => {
getItems[action](actionArgs).then(() => {
initializeCollapses();
});
}, []);
initializeCollapses() {
const data = this.props[this.props.action];
let collapseStates = this.state.collapseStates;
if (!data || data.length < 1) {
return;
}
data.map((el) => {
collapseStates["" + el.name + el.identifier] = false;
return;
});
this.setState({
...this.state,
collapseStates: collapseStates,
});
}
componentDidMount() {
this.props.getItems[this.props.action](this.state.actionArgs).then(() => {
this.initializeCollapses();
});
}
const mapDispatchToProps = (dispatch) => {
return {
getItems: {
analysers: (site) => dispatch(getAnalysers(site)),
platforms: (site) => dispatch(getPlatforms(site)),
brokers: (site) => dispatch(getBrokers(site)),
cameras: (site) => dispatch(getCameras(site)),
sites: (site) => dispatch(getSites())
},
};
};
The above class implementation in hooks would roughly be as below
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import getItems from "./store/actions";
or
import { cameras, sites, platform, brokers } from "./store/actions";
const actionArgs = useSelector(state => state.actionArgs); // In place of mapStateToProps
const dispatch = useDispatch();
useEffect(() => {
dispatch(getItems.cameras(actionArgs)) or dispatch(cameras(actionArgs)) //If destructured
}, []);
I have provided an understandable example with whatever data you provided. Refer this for a completely different approach or this one for the same mapDispatchToProps approach.
Good to refer
Example:
import React, {useReducer} from 'react';
const init = 0;
const myReducer = (state, action) => {
switch(action.type){
case 'increment':
return state + 1 // complex actions are kept in seperate files for better organised, clean code
case 'decrement':
return state - 1
case 'reset': // action types as well are kept as selectors
return init
default:
return state
}
};
function ReducerExample(){
const [count, dispatch] = useReducer(myReducer, init)
const add = () => {
dispatch({type: 'increment'})
}
const sub = () => {
dispatch({type: 'decrement'})
}
const reset = () => {
dispatch({type: 'reset'})
}
return (
<div>
<h4>Count: {count}</h4>
<button onClick={add} style={{margin: '10px'}}>Increment</button>
<button onClick={sub}>Decrement</button>
<button onClick={reset} style={{margin: '10px'}}>Reset</button>
</div>
)
}
export default ReducerExample;
I'm using memoize-one on a React component that is basically a table with a rows that can be filtered.
Memoize works great for the filtering but when I want to insert a new row, it won't show up on the table until I either reload the page or use the filter.
If I check the state, the new row's data is in it, so presumably what is happening is that memoize is not allowing the component to re-render even if the state has changed.
Something interesting is that the Delete function works, I am able to delete a row by removing its data from the state and it will re-render to reflect the changes...
Here's the part of the code I consider relevant but if you would like to see more, let me know:
import React, { Component } from "react";
import memoize from "memoize-one";
import moment from "moment";
import {
Alert,
Card,
Accordion,
Button,
Table,
Spinner,
} from "react-bootstrap";
import PropTypes from "prop-types";
import { getRoleMembersDetailed } from "../libs/permissions-manager-client-v1.0";
import RoleMember from "./RoleMember";
import CreateMemberModal from "./CreateMemberModal";
class RoleContainer extends Component {
filter = memoize((roleMembers, searchValue, searchCriterion) => {
const searchBy = searchCriterion || "alias";
return roleMembers.filter((item) => {
if (item[searchBy]) {
if (searchValue === "") {
return true;
}
const value = searchValue.toLowerCase();
if (searchBy !== "timestamp") {
const target = item[searchBy].toLowerCase();
return target.includes(value);
}
// Case for timestamp
const target = moment(Number(item[searchBy]))
.format("MMM DD, YYYY")
.toLowerCase();
return target.includes(value);
}
return false;
});
});
constructor(props) {
super(props);
this.state = {
collapsed: true,
roleAttributes: [],
roleMembers: [],
isLoading: true,
};
}
componentDidMount = async () => {
const roleMembers = Object.values(await this.fetchRoleMembers());
roleMembers.forEach((e) => {
e.alias = e.alias.toLowerCase();
return null;
});
roleMembers.sort((a, b) => {
if (a.alias < b.alias) {
return -1;
}
if (a.alias > b.alias) {
return 1;
}
return 0;
});
// TODO - This logic should be replaced with an API call that describes the roleAttributes.
let roleAttributes = Object.values(roleMembers);
roleAttributes = Object.keys(roleAttributes[0]);
this.setState({
roleMembers,
roleAttributes,
isLoading: false,
});
};
fetchRoleMembers = async () => {
const { roleAttributeName } = this.props;
return getRoleMembersDetailed(roleAttributeName);
};
createRoleMember = (newRoleMembers) => {
const { roleMembers } = this.state;
newRoleMembers.forEach((e) => {
roleMembers.push(e);
});
this.setState(
() => {
roleMembers.sort((a, b) => {
if (a.alias < b.alias) {
return -1;
}
if (a.alias > b.alias) {
return 1;
}
return 0;
});
return { roleMembers };
},
() => {
console.log("sss", this.state);
}
);
};
deleteRoleMember = (alias) => {
this.setState((prevState) => {
const { roleMembers } = prevState;
return {
roleMembers: roleMembers.filter((member) => member.alias !== alias),
};
});
};
render() {
const {
role,
roleAttributeName,
searchValue,
searchCriterion,
userCanEdit,
} = this.props;
const { collapsed, isLoading, roleAttributes, roleMembers } =
this.state;
const filteredRoleMembers = this.filter(
roleMembers,
searchValue,
searchCriterion
);
return (
// continues...
I don't know if it's obvious but there are two functions called filter: this.filter that belongs to memoize and Array.prototype.filter().
I did look around and found these post that says Memoize can be overridden:
If you’ve ran into a UI bug, it is simple to just return false from myComparison to temporarily override the memoization, forcing a refresh on every re-render and returning to the default component behaviour.
But I'm not sure what they mean with "return false from component"
Here's a refactoring of your code to idiomatic React Hooks style (naturally dry-coded).
Note how filtering and sorting the role members is done using useMemo() in a way that doesn't modify state; that's because they can be always recomputed from the stateful data. So long as the useMemo()s' deps array is kept in sync (there're ESLint rules to help with this), this should work with no extra re-renders. :)
Similarly, if you use useCallback (which is a special case of useMemo), you need to keep their deps arrays in sync. If you don't use useCallback, those callbacks may cause re-renders since their identity changes per-render.
import React, { Component } from "react";
import moment from "moment";
import { getRoleMembersDetailed } from "../libs/permissions-manager-client-v1.0";
function filterRoleMembers(
roleMembers,
searchValue,
searchCriterion,
) {
const searchBy = searchCriterion || "alias";
return roleMembers.filter((item) => {
if (item[searchBy]) {
if (searchValue === "") {
return true;
}
const value = searchValue.toLowerCase();
if (searchBy !== "timestamp") {
const target = item[searchBy].toLowerCase();
return target.includes(value);
}
// Case for timestamp
const target = moment(Number(item[searchBy]))
.format("MMM DD, YYYY")
.toLowerCase();
return target.includes(value);
}
return false;
});
}
// TODO: maybe use lodash's `sortBy`?
function compareByAlias(a, b) {
if (a.alias < b.alias) {
return -1;
}
if (a.alias > b.alias) {
return 1;
}
return 0;
}
async function fetchRoleMembers(roleAttributeName) {
return getRoleMembersDetailed(roleAttributeName);
}
async function loadData(roleAttributeName) {
const roleMembers = Object.values(
await fetchRoleMembers(roleAttributeName),
);
roleMembers.forEach((e) => {
e.alias = e.alias.toLowerCase();
});
// TODO - This logic should be replaced with an API call that describes the roleAttributes.
let roleAttributes = Object.values(roleMembers);
roleAttributes = Object.keys(roleAttributes[0]);
return {
roleMembers,
roleAttributes,
};
}
const RoleContainer = ({
role,
roleAttributeName,
searchValue,
searchCriterion,
userCanEdit,
}) => {
const [collapsed, setCollapsed] = React.useState(true);
const [isLoading, setIsLoading] = React.useState(true);
const [roleAttributes, setRoleAttributes] = React.useState([]);
const [roleMembers, setRoleMembers] = React.useState([]);
React.useEffect(() => {
loadData(roleAttributeName).then(
({ roleMembers, roleAttributes }) => {
setRoleAttributes(roleAttributes);
setRoleMembers(roleMembers);
setIsLoading(false);
},
);
}, [roleAttributeName]);
const createRoleMember = React.useCallback(
(newRoleMembers) => {
const updatedRoleMembers = roleMembers.concat(newRoleMembers);
setRoleMembers(updatedRoleMembers);
},
[roleMembers],
);
const deleteRoleMember = React.useCallback(
(alias) => {
const updatedRoleMembers = roleMembers.filter(
(member) => member.alias !== alias,
);
setRoleMembers(updatedRoleMembers);
},
[roleMembers],
);
const filteredRoleMembers = React.useMemo(
() =>
filterRoleMembers(roleMembers, searchValue, searchCriterion),
[roleMembers, searchValue, searchCriterion],
);
const sortedRoleMembers = React.useMemo(
() => [].concat(filteredRoleMembers).sort(compareByAlias),
[filteredRoleMembers],
);
return <>{JSON.stringify(sortedRoleMembers)}</>;
};
i want to return a function that uses useEffect from the usehook and i am getting error "useeffect is called in a function which is neither a react function component or custom hook.
what i am trying to do?
i have addbutton component and when user clicks add button i want to call the function requestDialog.
below is my code within addbutton file
function AddButton () {
const count = useGetCount();
const requestDialog = useRequestDialog(); //using usehook here
const on_add_click = () => {
requestDialog(count); //calling requestDialog here
}
return (
<button onClick={on_add_click}>add</button>
);
}
interface ContextProps {
trigger: (count: number) => void;
}
const popupContext = React.createContext<ContextProps>({
trigger: (availableSiteShares: number) => {},
});
const usePopupContext = () => React.useContext(popupContext);
export const popupContextProvider = ({ children }: any) => {
const [show, setShow] = React.useState(false);
const limit = 0;
const dismiss = () => {
if (show) {
sessionStorage.setItem(somePopupId, 'dismissed');
setShow(false);
}
};
const isDismissed = (dialogId: string) =>
sessionStorage.getItem(dialogId) === 'dismissed';
const context = {
trigger: (count: number) => {
if (!isDismissed(somePopupId) && count <= limit) {
setShow(true);
} else if (count > limit) {
setShow(false);
}
},
};
return (
<popupContext.Provider value={context}>
{children}
{show && (
<Popup onHide={dismiss} />
)}
</popupContext.Provider>
);
};
export function useRequestDialog(enabled: boolean,count: number) {
return function requestDialog() { //here is the error
const { trigger } = usePopupContext();
React.useEffect(() => {
trigger(count);
}
}, [count, trigger]);
}
How to solve the error ""useEffect is called in a function which is neither a react function component or custom hook."
i am not knowing how to use useeffect and the same time use it in the addbutton component.
could someone help me with this. thanks
useEffect method is like, useEffect(() => {}, []), But your usage in requestDialog is wrong. Try changing with following.
function requestDialog() {
const { trigger } = usePopupContext();
React.useEffect(() => {
trigger(count);
}, [count, trigger]);
}