I am trying to to set a hook on click. When that hook is set, it enters a url and then when it is set, it is supposed to run a handleSubmit function to update the urls and display it to screen. My problem is that the function run at the same time. I have tried to use the useEffect method, by placing the handleSubmit function in there, but it keeps giving errors about the event object. I have tried the async/await function on the onClick method but have read that it doesn't work on hooks. I have read the promises docs but they are confusing right now. Can anyone point me in the right direction?
const Peers = ({ peerData, symbol, handleSubmit }) => {
const [peerSymbol, setPeerSymbol] = useState('');
let today = new Date().toISOString().slice(0, 10)
const urls = [
`https://finnhub.io/api/v1/company-news?symbol=${peerSymbol}&from=2021-03-01&to=${today}&token=`,
`https://finnhub.io/api/v1/stock/peers?symbol=${peerSymbol}&token=`,
`https://finnhub.io/api/v1/stock/profile2?symbol=${peerSymbol}&token=`,
`https://finnhub.io/api/v1/stock/financials-reported?symbol=${peerSymbol}&token=`,
`http://api.marketstack.com/v1/tickers/${peerSymbol}/eod/latest?access_key`
]
useEffect(() => {
let e = e
return (e) => handleSubmit(e, urls);
}, [peerSymbol])
return (
<div className="peers bg-light">
<h2>Peers</h2>
{peerData.filter(peer => {
return peer !== symbol.toUpperCase();
}).map(element => {
return <span
key={element}
onClick={async (e) => { setPeerSymbol(element); handleSubmit(e, urls) }}>{element}</span>
})}
</div>
);
}
Add a function outside the component's body as getUrls and call it with the element and date:
const getUrls = (peerSymbol, today) => ([
`https://finnhub.io/api/v1/company-news?symbol=${peerSymbol}&from=2021-03-01&to=${today}&token=budo2rv48v6spq9og4p0`,
`https://finnhub.io/api/v1/stock/peers?symbol=${peerSymbol}&token=budo2rv48v6spq9og4p0`,
`https://finnhub.io/api/v1/stock/profile2?symbol=${peerSymbol}&token=budo2rv48v6spq9og4p0`,
`https://finnhub.io/api/v1/stock/financials-reported?symbol=${peerSymbol}&token=budo2rv48v6spq9og4p0`,
`http://api.marketstack.com/v1/tickers/${peerSymbol}/eod/latest?access_key=72d118ca9db1873033447561590e2794`
]);
const Peers = ({ peerData, symbol, handleSubmit }) => {
const [peerSymbol, setPeerSymbol] = useState('');
const today = new Date().toISOString().slice(0, 10)
return (
<div className="peers bg-light">
<h2>Peers</h2>
{peerData.filter(peer => {
return peer !== symbol.toUpperCase();
}).map(element => {
return <span
key={element}
onClick={async (e) => { setPeerSymbol(element); handleSubmit(e, getUrls(element, today)) }}>{element}</span>
})}
</div>
);
}
this way you don't have to rely on the component's state to update before calling handleSubmit and you can remove useState if it's no longer needed.
Related
Getting an error of Unhandled Runtime Error while setting a state in an axios get call.
Even tested with a button click and confirmed that function is not called more that once. I don't know what is the problem with it.
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
const [quizzesTaken, setQuizzesTaken] = useState([])
const quizzesTakenByUser = async () => {
try {
const res = await axios.get('/api/quizzes/quizzestaken')
setQuizzesTaken(res.data) // Getting error on this line.
} catch (error) {
log(error)
}
}
<button onClick={() => quizzesTakenByUser()}>Get Quizzes</button>
useEffect(() => {
if (gradeText !== 'none') {
getGradeDescription()
}
}, [gradeText])
This is how I render the components base on quizzesTaken
{quizzesTaken.map(q => {
return (
<QuizTakenItem
subject={
q.subject +
' ' +
q.class_name
}
correct={q.correct_ans}
total={q.total_questions}
/>
)
})}
The problem is solved after I removed the code setting the independent state of the child component but now it is a more annoying problem to the brain, why it was like that is still a question?
const [width, setWidth] = useState("10%")
const {correct, total} = props;
setWidth(((correct/total) * 100) + "%")
And thank you all for your help.
Did you try this way, using then() :
const [quizzesTaken, setQuizzesTaken] = useState([])
const quizzesTakenByUser = () => {
axios.get('/api/quizzes/quizzestaken')
.then(res => setQuizzesTaken(res.data)
.catch(error => console.log(error)
}
<button onClick={() => quizzesTakenByUser()}>Get Quizzes</button>
Did you tried like this?
<button onClick={quizzesTakenByUser}>Get Quizzes</button>
I have a function that filters through some state and renders out the result for a search request.
const handleSearch = (value: string) => {
const searchResultData = users.filter((userId) => user.id.startsWith(value));
setSearchResult(searchResultData);
};
I am trying to work with lodash.throttle library to cause a delay before the request is sent. So we don't have a request go out every time a user types.
const handleSearch = useCallback(throttle((value: string) => {
const searchResultData = users.filter((userId) => user.id.startsWith(value));
setSearchResult(searchResultData);
}, 2500), []);
This works in delaying input as expected but for some reason, the user.filter method doesn't run, and so the state isn't updated with the search result. I believe the problem might be from the useCallback hook, but the throttle function is dependent on it to run. Any ideas on how I can work around this problem?
If your throttled/debounced handler uses props or state, like this:
const { fetcherFunctionFromProps } = props;
const eventHandler = async () => {
const resp = await fetcherFunctionFromProps();
};
const debouncedEventHandler = useMemo(
() => throttle(eventHandler, 300)
), [fetcherFunctionFromProps]);
And it doesn't work,
you can refactor it to the following:
const { fetcherFunctionFromProps } = props;
const eventHandler = async (fetcher) => {
const resp = await fetcher();
};
const debouncedEventHandler = useMemo(() => throttle(eventHandler, 300), []);
...
<Component onClick={() => debouncedEventHandler(fetcherFunctionFromProps)}>
I have created a form and I have noticed that when I submit data, they are not writing in the db (with error 400). So I have investigated and I have noticed that one api call that I make in useEffect is done about 5 time during the submit. (I have tried to comment this part and It works!)
I have a first part of form, in which with a select I make a choose, this value is used to make an api call (and there is the problem) to give back some data to use in the form.
return (
<AvForm model={isNew ? {} : userClientAuthorityEntity} onSubmit={saveEntity}>
<AvInput
id="client-application"
data-cy="application"
type="select"
className="form-control"
name="application"
onChange={handleChangeApp} // there i save the value applicationApp
required
value={applicationApp}
>
<option value="" key="0">
Select
</option>
{applicationListAPP ?
applicationListAPP.map(value => {
return (
<option value={value.appCod} key={value.appCod}>
{value.appDescription}
</option>
);
})
: null}
</AvInput>
</AvGroup>
<ShowRoleApp applicationRole={applicationApp} /> // so there I pass the value to make the api call
)
const ShowRoleApp = ({ applicationRole }) => {
const [profili, setProfili] = useState([]);
const [isLoading, setIsLoading] = useState(false);
if (!applicationRole) {
return <div />;
}
// I think that it the problem, because it recall GetProfili
useEffect(() => {
async function init() {
await GetProfili(applicationRole)
.then((res) => {
console.log('res ', res);
setProfili(res);
setIsLoading(true);
})
.catch((err) => console.log('err ', err));
}
init();
}, []);
return isLoading ? (
RenderProfili(profili, applicationRole)
) : (
<div className='d-flex justify-content-center'>
<div className='spinner-border text-primary' role='status'>
<span className='visually-hidden'></span>
</div>
</div>
);
};
const GetProfili = async (appCod) => {
const chiamata = 'myApi' + appCod.toString();
const res = await fetch(chiamata);
const result = res.clone().json();
return result;
};
const RenderProfili = (profili, applicationRole) => {
const ruoliOperatore = profili ? profili.filter(it => it.appCod.toString() === applicationRole.toString()) : null;
return (
<AvGroup>
<Label for="sce-profiloutentepa-pucCod">Profile (*)</Label>
// other code for the form...
So in your opinion how can i do to call the GetProfili without recall every time when I submit the form?
Thank you
You could define GetProfili as a custom hook an manage the useEffect call in it.
It will return the isLoading and profili instances.
Try to change your code like this.
GetProfili:
const GetProfili = (appCod) => {
const [isLoading, setIsLoading] = useState(true)
const [profili, setProfili] = useState([])
const loadProfili = async () => {
const chiamata = 'myApi' + appCod.toString();
const res = await fetch(chiamata);
setProfili(res.json())
setIsLoading(false)
}
useEffect(() => {
loadProfili()
}, [])
return { isLoading, profili };
};
ShowRoleApp:
const ShowRoleApp = ({ applicationRole }) => {
if (!applicationRole) {
return <div />;
}
const { isLoading, profili } = GetProfili(applicationRole)
return isLoading ? (
RenderProfili(profili, applicationRole)
) : (
<div className='d-flex justify-content-center'>
<div className='spinner-border text-primary' role='status'>
<span className='visually-hidden'></span>
</div>
</div>
);
};
I didn't really understand the question but I can say something that might help. The useEffect() hook gets called on every rerender of the component so if it updates 5 times its because some states inside the component get updated 5 times. Also states are updated in child components update the parent.
I'm working on a project that has to do with playlist, so what I want to execute is whenever one of the songs on the playlist is clicked the image that is attached to the song should be viewed.
So, I have my code this way...
const Component = () => {
const value = useContext(DataContext);
const [data, setData] = useState(null);
const [currentData, setCurrentData] = useState(null);
useEffect(() => {
const url =
"https://52-90-82-235.maverickmaven.com/geotourdata/json.cfm?h=-107,37,s,en,3A771765";
const currentValue = value;
axios({
method: "get",
url,
responseType: "stream",
}).then((response) => {
let features = response.data.features.filter((elem) => {
return elem.type === "Feature";
});
setData(features);
const currentDatafile = data?.filter((data) => {
return data?.assets[0].audio === value;
});
setCurrentData(currentDatafile);
});
}, [setCurrentData]);
};
So, what this code does is that it returns the array that has the picture, but the problem is that it only filters once and repeatedly returns the same value even if I click on another song, and I need it to filter every time I clicked on the songs(i.e the function is executed).
I tried filtering and mapping at the same time, but it didn't work. or maybe I didn't write the syntax well enough.
Please I need help.
Move these lines to new useEffect hook. Will trigger after you set data
useEffect(() => {
const currentDatafile = data?.filter((item) => {
return item.assets[0].audio === value;
});
setCurrentData(currentDatafile)},[data])
You shouldn't re-fetch the data from the remote source every time. I've wrapped that in a custom hook instead, here (and a custom fetcher function to make testing/mocking easier).
Then, you shouldn't hold the selected object in the state unless you need to modify it internally (in which case you should copy it into a state atom anyway); instead, just hold the ID.
function fetchTourData() {
return fetch('https://52-90-82-235.maverickmaven.com/geotourdata/json.cfm?h=-107,37,s,en,3A771765')
.then(response => response.json())
.then(data => data.features.filter((elem) => elem.type === 'Feature'));
}
function useTourData() {
const [data, setData] = React.useState(null);
React.useEffect(() => {
fetchTourData().then(setData);
}, [setData]);
return data;
}
const Component = () => {
const tourData = useTourData();
const [selectedId, setSelectedId] = React.useState(null);
const selectedTour = (tourData || []).find(t => t.id === selectedId);
if (tourData === null) {
return <div>Loading</div>
}
return (
<div>
<div>
Selected: {JSON.stringify(selectedTour || "nothing")}
</div>
<ul>
{tourData.map(t => <li key={t.id}><a href="#" onClick={() => setSelectedId(t.id)}>{t.name}</a></li>)}
</ul>
</div>
);
};
ReactDOM.render(<Component />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
When state is in a hook it can become stale and leak memory:
function App() {
const [greeting, setGreeting] = useState("hello");
const cb = useCallback(() => {
alert("greeting is " + greeting);
}, []);
return (
<div className="App">
<button onClick={() => cb()}>Click me</button>
<p>
Click the button above, and now update the greeting by clicking the one
below:
</p>
<button onClick={() => setGreeting("bye")}>
Update greeting
</button>
<p>Greeting is: {greeting}</p>
<p>
Now click the first button again and see that the callback still has the
old state.
</p>
</div>
);
}
Demo: https://codesandbox.io/s/react-hook-stale-datamem-leak-demo-9pchk
The problem with that is that we will run into infinite loops in a typical scenario to fetch some data if we follow Facebook's advice to list all dependencies always, as well as ensure we don't have stale data or memory leaks (as the example showed above):
const [state, setState] = useState({
number: 0
});
const fetchRandomNumber = useCallback(async () => {
if (state.number !== 5) {
const res = await fetch('randomNumber');
setState(v => ({ ...v, number: res.number }));
}
}, [setState, state.number]);
useEffect(() => {
fetchRandomNumber();
}, [fetchRandomNumber]);
Since Facebook say we should list fetchRandomNumber as a dependency (react-hooks/exhaustive-deps ESLint rule) we have to use useCallback to maintain a reference, but it regenerates on every call since it both depends on state.number and also updates it.
This is a contrived example but I've run into this many times when fetching data. Is there a workaround for this or is Facebook wrong in this situation?
Use the functional form of the state setter:
const fetchData = useCallback(async () => {
const res = await fetch(`url?page=${page}`);
setData((data) => ([...data, ...res.data]));
setPage((page) => page + 1);
}, [setData, setPage]);
Now you don't need data and page as your deps
You can also use a ref to run the effect only on mount :
const mounted = useRef(false);
useEffect(() => {
if(!mounted.current) {
fetchSomething();
mounted.current = true;
}
return () => { mounted.current = false }
}, [fetchSomething]);
And
const fetchSomething = useCallback(async () => {
...
}, [setData, setPage, data, page]);
fetchSomething is not a dependency here. You don't want to retrigger the effect, you only cause it once when the component mounts. Thats what useEffect(() => ..., []) is for.