React Hook access in an onclick function - reactjs

I need to refresh a component using a button and a HTTP fetch request, so I've a hook that calls the function but I need to fire it from an onClick handler in a button:
const {useEffect, useState, useRef} = React;
const useRefreshArtifacts = (setArtifactsStore) => {
const refresh = () => {
console.log("do some stuff with state");
const [match, setMatch] = useState({});
};
return {refresh};
};
function ArtifactApp(props) {
const {refresh} = useRefreshArtifacts("");
return (<div><button onClick={refresh}>Hello</button></div>);
}
const AppContainer = () => {
return (<div><ArtifactApp /></div>);
}
ReactDOM.render(<AppContainer />, document.getElementById("root"));
https://jsfiddle.net/jre2cwbf/
Which gives the usual Invalid hook call. Hooks can only be called inside of the body of a function component but I can't figure out how to externalise the hook call, I've tried a useCallback but not got any close.
Any help appreciated.

Modify your code in this way:
const {useEffect, useState, useRef} = React;
const useRefreshArtifacts = (setArtifactsStore) => {
const [match, setMatch] = useState("Hello");
const refresh = () => {
console.log("do some stuff with state");
setMatch("refresh")
};
return {match, refresh};
};
function ArtifactApp(props) {
const {match, refresh} = useRefreshArtifacts("");
return (<div><button onClick={refresh}>{match}</button></div>);
}
const AppContainer = () => {
return (<div><ArtifactApp /></div>);
}
ReactDOM.render(<AppContainer />, document.getElementById("root"));
As you can see I have:
move useState outside refresh function;
set match on refresh function;
returned match from custom hook (useRefreshArtifacts) to use it on component.
Here your fiddle modified.

Related

How do I trigger React UseEffect on only the first render?

In am using React and trying to trigger a function only once, when the page initially loads. Currently, the below code triggers the console message twice at page load.
import { useState, useEffect, useRef } from "react";
export default function TestRef(){
const [inputValue, setInputValue] = useState("");
const count = useRef(null);
const myFunc = () => {
console.log('Function Triggered');
}
useEffect(() => {
if(!count.current){
count.current = 1;
myFunc();
}
return () => { count.current = null; }
}, []);
return (
<>
<p>Page content</p>
</>
);
}
I have read up on how React 18 intentionally double-renders elements, which is my reason for using useRef and for returning the cleanup function in the useEffect hook, but it still doesn't seem to work.
hi please make sure you didn't invoke TestRef componenet twice in your page!
for debug and find rerenders you can use react profiler extention on chrome then remove extra rerender by using momo and useMemo and useCallback
Finally got it to work by doing this. This appears to only run myFunc() once, on the initial rendering of the component.
import { useState, useEffect, useRef } from "react";
export default function TestRef(){
const [inputValue, setInputValue] = useState("");
const count = useRef(null);
const myFunc = () => {
console.log('Function Triggered');
}
useEffect(() => {
if(count.current == null){
myFunc();
}
return () => { count.current = 1; }
}, []);
return (
<>
<p>Page content</p>
</>
);
}

React hook "useMemo" with array as dependency

I am new to react (that I use with typeScript) and I am facing an issue with the use of the useMemo hook.
Here is my fetching service:
export default class FetchingService {
datas: Data[] = [];
constructor() {
this.fetch();
}
async fetch(): Promise<Data[]> {
const d = // await an async array from an api, using Array.flat()
this.datas = d;
console.log(this.datas);
return d;
}
}
In a component, I try to watch for change of the datas attribute of my service:
import fetchingService from '../services/fetchingService.ts';
const Home: React.FC = () => {
const ds: Data[];
const [datas, setDatas] = useState(ds);
const fetchDatas = useMemo(() => {
console.log('Render datas', fetchingService.datas?.length)
setDatas(fetchingService.datas);
return fetchingService.datas;
}, [fetchingService.datas]);
return (
<ul>{datas.map(d => {
return (
<li key={d.id}>{d.id}</li>
);
</ul>
);
}
The problem I am facing is that the useMemo hook is not recompouted when the datas attribute changes within my fetchService. I am pretty sure that my FetchingService.fetch() function works because the console.log within the fetch function always display the fetched datas.
The observed behavior is that sometimes datas are well rendered (when fetch ends before rendering ...), but sometimes it isn't.
The expected one is that datas are rendered every time and only on refresh, exept when datas are modified
I also tried to put the length of the data array as a dependency in useMemo, but in both cases it doesn't work and I have a warning in my IDE, telling me it is an unnecessary dependency.
I don't really understand if it is a typescript or a specific react behavior issue. I think the reference of the datas attribute should change at the end of the fetch (or at least its length attribute ...), but tell me if I am wrong.
I do appreciate every help !
in fetchingService, when datas change, probably the dependency cannot be accepted. You can use a custom hook in stead of it.
You can use this source about useMemo: useMemo with an array dependency?
import { useState, useLayoutEffect, useCallback } from "react";
export const useFetchingService = () => {
const [fetchedData, setFetchedData] = useState([]);
const fetch = useCallback(async () => {
const d = await new Promise((res, rej) => {
setTimeout(() => {
res([1, 2, 3]);
}, 5000);
}); // await an async array from an api, using Array.flat()
setFetchedData(d);
}, []);
useLayoutEffect(() => {
fetch();
}, []);
return [fetchedData];
};
useLayoutEffect runs before rendering
using:
const [fetchData] = useFetchingService();
const fetchDatas = useMemo(async () => {
console.log("Render datas", fetchData.length);
setDatas(fetchData);
return fetchData;
}, [fetchData]);
You can also use this directly without 'datas' state.
I hope that this will be solution for you.
So I put together a codesandbox project that uses a context to store the value:
App.tsx
import React, { useState, useEffect, createContext } from "react";
import Home from "./Home";
export const DataContext = createContext({});
export default function App(props) {
const [data, setData] = useState([]);
useEffect(() => {
const get = async () => {
const d = await fetch("https://dummyjson.com/products");
const json = await d.json();
const products = json.products;
console.log(data.slice(0, 3));
setData(products);
return products;
};
get();
}, []);
return (
<div>
Some stuff here
<DataContext.Provider value={{ data, setData }}>
<Home />
</DataContext.Provider>
</div>
);
}
Home.tsx
import React, { FC, useMemo, useState, useEffect, useContext } from "react";
import { DataContext } from "./App";
import { Data, ContextDataType } from "./types";
const Home: FC = () => {
const { data, setData }: ContextDataType = useContext(DataContext);
return (
<>
<ul>
{data.map((d) => {
return (
<li key={d.id}>
{d.title}
<img
src={d.images[0]}
width="100"
height="100"
alt={d.description}
/>
</li>
);
})}
</ul>
</>
);
};
export default Home;
This was my first time using both codesandbox and typescript so I apologize for any mistakes

React nested Hooks with state

An extension of a question I was just answered but:
if you want to nest hooks that call state, is there a sane way of doing it, of should you just not do it?
For example:
const {useEffect, useState, useRef} = React;
const someSubHook = (setArtifactsStore) => {
const [match, setMatch] = useState("Hello");
const subhook = () => {
console.log("do some stuff with state");
setMatch("subhook")
};
return {match, sunhook};
};
const useRefreshArtifacts = (setArtifactsStore) => {
const [match, setMatch] = useState("Hello");
const refresh = () => {
console.log("do some stuff with state");
setMatch("refresh")
let h = someSubHook("sub");
h.subook()
};
return {match, refresh};
};
function ArtifactApp(props) {
const {match, refresh} = useRefreshArtifacts("");
return (<div><button onClick={refresh}>{match}</button></div>);
}
const AppContainer = () => {
return (<div><ArtifactApp /></div>);
}
ReactDOM.render(<AppContainer />, document.getElementById("root"));
https://jsfiddle.net/wu5ej3Lm/
Calling this throws an Invalid hook call. Hooks can only be called inside of the body of a function component.
Basically the codebase I have has a hook that performs some logic, and then has another hook that does some http post requests, both of which use state. I've inherited this so I'm finding my way through it, but I'm not sure if sticking all this as hooks is really best practice if there is a dependency on the state management.
Try to modify code in this way:
const {useEffect, useState, useRef} = React;
const someSubHook = (setArtifactsStore) => {
const [match, setMatch] = useState("Hello");
const subhook = () => {
console.log("do some stuff with state from subhook");
setMatch("subhook")
};
return {match, subhook};
};
const useRefreshArtifacts = (setArtifactsStore) => {
const [match, setMatch] = useState("Hello");
const {match_subhook, subhook} = someSubHook("sub");
const refresh = () => {
console.log("do some stuff with state from refresh");
setMatch("refresh")
subhook()
};
return {match, refresh};
};
function ArtifactApp(props) {
const {match, refresh} = useRefreshArtifacts("");
return (<div><button onClick={refresh}>{match}</button></div>);
}
const AppContainer = () => {
return (<div><ArtifactApp /></div>);
}
ReactDOM.render(<AppContainer />, document.getElementById("root"));
I have:
move let h = someSubHook("sub"); outside refresh function in this way const {match_subhook, subhook} = someSubHook("sub");
called subhook() in refresh function.
Here your fiddle modified.

Passing useState Hook into another function on React

I want to pass in a boolean value in a useState hook that opens and closes a modal on click between two functions. However, I keep getting this error message: Cannot destructure property 'setOpenModal' of 'props' as it is undefined.
Main.js
import React, { useState, useEffect } from "react";
import * as Materials from "../components/Materials"; // <- Material.js
const Main = () => {
const [openModal, setOpenModal] = useState(false); //<- Opens (True) and Closes (False) Modal
const { MaterialContainer } = Materials.MaterialTable(); // <-Calling Function Under MaterialTable
return (
<MaterialContainer
openModal={openModal}
setOpenModal={setOpenModal}
/>
// This is how I am passing in Open/Close useState.
}
Material.js
export const MaterialTable = (props) => {
const { openModal, setOpenModal } = props; // <- Pointed in Error Message.
const openMaterialModal = (item) => {
console.log("Button Clicked");
setOpenModal(true); // <- Where I am passing in a true statement.
};
const MaterialContainer = () => (
<>
<Table>Stuff</Table>
</>
);
return {
MaterialContainer
}
}
Thanks in advance.
The MaterialTable component is entirely malformed from a React perspective, though valid JavaScript. It's just a normal function that defines a couple of constants and then returns nothing. (Well, in the original question it returned nothing. Now it returns an object.)
And when you call that function you indeed don't pass anything to it:
const { MaterialContainer } = Materials.MaterialTable();
So the props will be undefined.
Make MaterialTable itself a React component:
export const MaterialTable = (props) => {
// destructure the props passed to the component
const { openModal, setOpenModal } = props;
// a function I assume you plan to use in the JSX below later?
const openMaterialModal = (item) => {
console.log("Button Clicked");
setOpenModal(true);
};
// the rendering of the component
return (
<>
<Table>Stuff</Table>
</>
);
}
Then just import and use that component, without trying to destructure anything from it or invoke it manually:
import React, { useState, useEffect } from "react";
// import the component
import { MaterialTable } from "../components/Materials";
const Main = () => {
// use the state hook
const [openModal, setOpenModal] = useState(false);
// render the component, passing props
return (
<MaterialTable
openModal={openModal}
setOpenModal={setOpenModal}
/>
);
}

React Hooks + Mobx => Invalid hook call. Hooks can only be called inside of the body of a function component

I have a React Native App,
Here i use mobx ("mobx-react": "^6.1.8") and react hooks.
i get the error:
Invalid hook call. Hooks can only be called inside of the body of a function component
Stores index.js
import { useContext } from "react";
import UserStore from "./UserStore";
import SettingsStore from "./SettingsStore";
const useStore = () => {
return {
UserStore: useContext(UserStore),
SettingsStore: useContext(SettingsStore),
};
};
export default useStore;
helper.js OLD
import React from "react";
import useStores from "../stores";
export const useLoadAsyncProfileDependencies = userID => {
const { ExamsStore, UserStore, CTAStore, AnswersStore } = useStores();
const [user, setUser] = useState({});
const [ctas, setCtas] = useState([]);
const [answers, setAnswers] = useState([]);
useEffect(() => {
if (userID) {
(async () => {
const user = await UserStore.initUser();
UserStore.user = user;
setUser(user);
})();
(async () => {
const ctas = await CTAStore.getAllCTAS(userID);
CTAStore.ctas = ctas;
setCtas(ctas);
})();
(async () => {
const answers = await AnswersStore.getAllAnswers(userID);
UserStore.user.answers = answers.items;
AnswersStore.answers = answers.items;
ExamsStore.initExams(answers.items);
setAnswers(answers.items);
})();
}
}, [userID]);
};
Screen
import React, { useEffect, useState, useRef } from "react";
import {
View,
Dimensions,
SafeAreaView,
ScrollView,
StyleSheet
} from "react-native";
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp
} from "react-native-responsive-screen";
import { observer } from "mobx-react";
import useStores from "../../stores";
import { useLoadAsyncProfileDependencies } from "../../helper/app";
const windowWidth = Dimensions.get("window").width;
export default observer(({ navigation }) => {
const {
UserStore,
ExamsStore,
CTAStore,
InternetConnectionStore
} = useStores();
const scrollViewRef = useRef();
const [currentSlide, setCurrentSlide] = useState(0);
useEffect(() => {
if (InternetConnectionStore.isOffline) {
return;
}
Tracking.trackEvent("opensScreen", { name: "Challenges" });
useLoadAsyncProfileDependencies(UserStore.userID);
}, []);
React.useEffect(() => {
const unsubscribe = navigation.addListener("focus", () => {
CTAStore.popBadget(BadgetNames.ChallengesTab);
});
return unsubscribe;
}, [navigation]);
async function refresh() {
const user = await UserStore.initUser(); //wird das gebarucht?
useLoadAsyncProfileDependencies(UserStore.userID);
if (user) {
InternetConnectionStore.isOffline = false;
}
}
const name = UserStore.name;
return (
<SafeAreaView style={styles.container} forceInset={{ top: "always" }}>
</SafeAreaView>
);
});
so now, when i call the useLoadAsyncProfileDependencies function, i get this error.
The Problem is that i call useStores in helper.js
so when i pass the Stores from the Screen to the helper it is working.
export const loadAsyncProfileDependencies = async ({
ExamsStore,
UserStore,
CTAStore,
AnswersStore
}) => {
const userID = UserStore.userID;
if (userID) {
UserStore.initUser().then(user => {
UserStore.user = user;
});
CTAStore.getAllCTAS(userID).then(ctas => {
console.log("test", ctas);
CTAStore.ctas = ctas;
});
AnswersStore.getAllAnswers(userID).then(answers => {
AnswersStore.answers = answers.items;
ExamsStore.initExams(answers.items);
});
}
};
Is there a better way? instead passing the Stores.
So that i can use this function in functions?
As the error says, you can only use hooks inside the root of a functional component, and your useLoadAsyncProfileDependencies is technically a custom hook so you cant use it inside a class component.
https://reactjs.org/warnings/invalid-hook-call-warning.html
EDIT: Well after showing the code for app.js, as mentioned, hook calls can only be done top level from a function component or the root of a custom hook. You need to rewire your code to use custom hooks.
SEE THIS: https://reactjs.org/docs/hooks-rules.html
You should return the value for _handleAppStateChange so your useEffect's the value as a depdendency in your root component would work properly as intended which is should run only if value has changed. You also need to rewrite that as a custom hook so you can call hooks inside.
doTasksEveryTimeWhenAppWillOpenFromBackgorund and doTasksEveryTimeWhenAppGoesToBackgorund should also be written as a custom hook so you can call useLoadAsyncProfileDependencies inside.
write those hooks in a functional way so you are isolating specific tasks and chain hooks as you wish without violiating the rules of hooks. Something like this:
const useGetMyData = (params) => {
const [data, setData] = useState()
useEffect(() => {
(async () => {
const apiData = await myApiCall(params)
setData(apiData)
})()
}, [params])
return data
}
Then you can call that custom hook as you wish without violation like:
const useShouldGetData = (should, params) => {
if (should) {
return useGetMyData()
}
return null
}
const myApp = () => {
const myData = useShouldGetData(true, {id: 1})
return (
<div>
{JSON.stringify(myData)}
</div>
)
}

Resources