I have a React component with many chained functions for a specific task. Those functions need access to the component's states. I want to separate some of those functions into a new .js file to keep my component file clean, but doing so, the functions lose access to the states.
This is what I have (works fine):
// SomeComponent.js
import React, { useState, useEffect } from 'react';
export default function SomeComponent() {
const [name, setName] = useState("")
const [accessToken, setAccessToken] = useState("")
useEffect(() => {
console.log(name)
func1()
})
function func1() {
console.log(name)
func2()
}
function func2() {
console.log(name)
func3()
}
function func3() {
setAccessToken("1234")
}
return(
<h1>{name}</h1>
<button onClick={func3}>Click Me</button>
)
}
One idea is to send the states and state-setters as parameters all the way down to func3, but there are like 10 chained functions in my actual code and that's too messy.
Another idea is to make a class in the new file and instantiate the class with the states and state-setters as attributes. Maybe a new React Component?
Is there a cleaner way I can define func1, func2, func3 in a separate file while keeping access to the states? Either sending the states and state-setters to the scope of NewFile.js somehow, or bringing the functions to the scope of my component. I just want them in the same scope.
My only problem with the functions is the amount of space that they take in my component file.
I want something like this:
// SomeComponent.js
import React, { useState, useEffect } from 'react';
import {func1, func2, func3} from './newFile';
export default function SomeComponent() {
const [name, setName] = useState("")
const [accessToken, setAccessToken] = useState("")
useEffect(() => {
console.log(name)
func1()
})
return(
<h1>{name}</h1>
<button onClick={func3}>Click Me</button>
)
}
// NewFile.js
function func1() {
console.log(name)
func2()
}
function func2() {
console.log(name)
func3()
}
function func3() {
setAccessToken("1234")
}
export {func1, func2, func3}
In this sort of situation I sometimes like to use a custom hook to separate functions from the rendered JSX. Here, you could do:
export const useSomeComponent = () => {
const [name, setName] = useState("")
const [accessToken, setAccessToken] = useState("")
useEffect(() => {
console.log(name)
func1()
})
function func1() {
console.log(name)
func2()
}
function func2() {
console.log(name)
func3()
}
function func3() {
setAccessToken("1234")
}
return { name, func3 }; // return only the values used externally
};
import { useSomeComponent } from './useSomeComponent';
export default function SomeComponent() {
const { name, func3 } = useSomeComponent();
// note that if you're returning more than one element, a fragment must surround it
return (<>
<h1>{name}</h1>
<button onClick={func3}>Click Me</button>
</>);
}
It makes a big difference when both the function definitions/hooks and the JSX is long, by focusing on only one thing at a time in a file - the layout/markup, or the logic.
Related
The basic structure of my hook:
const useMyHook = () => {
const func1 = (): boolean => { ... }
const func2 = (type: "type1" | "type2"): boolean => { ... }
const func3 = (): boolean => { ... }
return {
func1, func2, func3
}
}
export default useMyHook
I'm currently using it like this, which works in dev, but breaks my build.
import useMyHook from "../hooks/useMyHook";
const { func1 } = useMyHook()
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const isValid = func1();
if (isValid) {
// do more things
}
}
return (
<form>...</form>
)
When running yarn build, I am faced with this error:
Objects are not valid as a React child (found: object with keys {func1, func2, func3}). If you meant to render a collection of children, use an array instead.
My attempt at solving this was to export my custom hook functions in an array, as the error seemed to suggest.
return [
func1, func2, func3
]
And using it like this:
const [ func1 ] = useMyHook();
const isValid = func1();
TypeScript is not happy with this:
An argument for 'type' was not provided.
But this is confusing, because func1 does not accept a type argument as func2 does.
Questions
Is there some way I work around this build-time error and export the { } of functions? This feels like the cleanest approach.
If there is no way to achieve 1, how can I properly use the exported array of functions without my types being misread?
I'm learning React and have a custom Context accessible with a use method:
// In a Provider file.
export const useUsefulObject(): UsefulObject {
return useContext(...)
}
I want to use UsefulObject in a click callback in another file. For convenience, I have that callback wrapped in a method someLogic.
const PageComponent: React.FC = () => {
return <Button onClick={(e) => someLogic(e)} />;
}
function someLogic(e: Event) {
const usefulObject = useUsefulObject();
usefulObject.foo(e, ...);
}
However, VS Code alerts that calling useUsefulObject in someLogic is a violation of the rules of hooks since it's called outside of a component method.
I can pass the object down (the below code works!) but it feels like it defeats the point of Contexts to avoid all the passing down. Is there a better way than this?
const PageComponent: React.FC = () => {
const usefulObject = useUsefulObject();
return <Button onClick={(e) => someLogic(e, usefulObject)} />;
}
function someLogic(e: Event, usefulObject) {
usefulObject.foo(e, ...);
}
The hooks need to be called while the component is rendering, so your last idea is one of the possible ways to do so. Another option is you can create a custom hook which accesses the context and creates the someLogic function:
const PageComponent: React.FC = () => {
const someLogic = useSomeLogic();
return <Button onClick={(e) => someLogic(e)} />
}
function useSomeLogic() {
const usefulObject = useUsefulObject();
const someLogic = useCallback((e: Event) => {
usefulObject.foo(e, ...);
}, [usefulObject]);
return someLogic;
}
Hello I am trying to render the updated stuff from the database whenever the update is invoked to the database. I found this solution but right now my "add" method is not in the same file as the "fetch" method like in the linked question. I tried the below but it still isn't working:
file 1: (the return method will render the UploadedImages by mapping them)
const [UploadedImages,setUploadedImages] = useState([])
const fetchUserAllPhotos = async () => {
const res = await client.get('/get-photos')
setUploadedImages(res.data.photoList)
}
useEffect(()=>{
fetchUserAllPhotos()
},[])
file 2:
import {fetchUserAllPhotos} from './Gallery/AllPhotos';
const addNewPhoto = async () => {
if (success) {
await fetchUserAllPhotos()
}
}
However inside the file 1's return (render) method it is not giving the updated result whenever a new photos is added (I need to sign out and sign back in in order to see the change). How can I go about solving it?
what if the function is wrapped inside another one?
export default function PhotoGrid(props){
const [UploadedImages,setUploadedImages] = useState([])
const fetchUserAllPhotos = async () => {
const res = await client.get('/get-photos')
setUploadedImages(res.data.photoList)
}
useEffect(()=>{
fetchUserAllPhotos()
},[])
}
you need to add this line at the bottom of file1
export {fetchUserAllPhotos}
In general, every time you want to import function, array, object etc it should look like this:
file1:
const func = () => {
//implementaion of the function
}
export {func}
file2:
import {func} from './file1' //you need to give the path of file1
func() //now you can use the fanction everywhere in file2
note: you can export as many functions as you wants just be careful it important to use the same name in the export and in the import
export {func1, func2, func3, obj1, abj2, arr1, arr2}
if you are exporting function from react component you need to define the function outside the component
const func = () => {
}
export default function MyReactComponent(prop) {
//implementation of your component
}
export {func}
If you need to use prop of your component inside the function you can pass this prop as a function parameter.
I recomend to avoid from exporting function from react component because when your project becomes bigger it will start to be frustrate to find were you impliment each fanction. instead it is a best practice to add a new js file that you implement there all the fetches function, I usually call this file api.js
This file should look something like this (I took this code from my project there I used axios to make the fetches to the server):
import axios from "axios"
const url = 'http://localhost:8080'
const api = {
getQuestions : async () => {
return axios.get(`${url}/question/all`)
},
getQuestionById : async (id) => {
return axios.get(`${url}/question/${id}`)
}
}
export default api;
I have an algorithm that can be used in a lot of places in my React app. For this reason, I need it to be asynchronous so as not to delay components' rendering.
Also, if there is an error in a reduce loop, I need to stop the function and return null. A try/catch format seems appropriate. However, React claims that:
Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
The issue is not because of my algorithm, but because of its async nature. I have written this very simple example that triggers the exact same error:
async function test(name){
try{
if(!name) {
throw new Error
}
const formattedName = name.toUppercase()
return formattedName
}
catch{
return "error"
}
}
export default function App() {
return (
<>
{test("joe")}
</>
);
}
How to fix this?
I don't know what's your use case but probably you just want to have a State in React that holds the name or whatever value you want and then render that value if it's available, something like:
const [name, setName] = useState("")
async function test(name){
try{
if(!name) {
throw new Error
}
const formattedName = name.toUppercase()
setName(formattedName)
}
catch{
return "error"
}
}
export default function App() {
return (
<>
{name}
</>
);
}
EDIT: Suppose that you have that js function in a utils folder or whatever that do all that parsing you mentioned. You can think this as an async call to an API, what you need to then is in your React component (suppose in the first render), the following:
const [data, setData] = useState(...)
useEffect(async () => {
const data = await callToYourParsingFunctionThatIsAsync();
setData(data);
}, [])
And then you render whatever you need from that data variable
import React, { useState } from "react";
export default function App() {
const [name, setName] = useState("");
async function test(innerName) {
if (innerName === name) {
return;
}
try {
if (!innerName) {
throw new Error();
}
const formattedName = innerName;
setName(formattedName);
} catch {
return "error";
}
}
test("Joe");
return <>{"Testing : " + name}</>;
}
You need to use the state for updating the render at a future event in time. A sample code looks like the above.
I'm trying to challenge myself to convert my course project that uses hooks into the same project but without having to use hooks in order to learn more about how to do things with class components. Currently, I need help figuring out how to replicate the useCallback hook within a normal class component. Here is how it is used in the app.
export const useMovieFetch = movieId => {
const [state, setState] = useState({});
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const fetchData = useCallback(async () => {
setError(false);
setLoading(true);
try{
const endpoint = `${API_URL}movie/${movieId}?api_key=${API_KEY}`;
const result = await(await fetch(endpoint)).json();
const creditsEndpoint = `${API_URL}movie/${movieId}/credits?api_key=${API_KEY}`;
const creditsResult = await (await fetch(creditsEndpoint)).json();
const directors = creditsResult.crew.filter(member => member.job === 'Director');
setState({
...result,
actors: creditsResult.cast,
directors
});
}catch(error){
setError(true);
console.log(error);
}
setLoading(false);
}, [movieId])
useEffect(() => {
if(localStorage[movieId]){
// console.log("grabbing from localStorage");
setState(JSON.parse(localStorage[movieId]));
setLoading(false);
}else{
// console.log("Grabbing from API");
fetchData();
}
}, [fetchData, movieId])
useEffect(() => {
localStorage.setItem(movieId, JSON.stringify(state));
}, [movieId, state])
return [state, loading, error]
}
I understand how to replicate other hooks such as useState and useEffect but I'm struggling to find the answer for the alternative to useCallback. Thank you for any effort put into this question.
TL;DR
In your specific example useCallback is used to generate a referentially-maintained property to pass along to another component as a prop. You do that by just creating a bound method (you don't have to worry about dependencies like you do with hooks, because all the dependencies are maintained on your instance as props or state.
class Movie extends Component {
constructor() {
this.state = {
loading:true,
error:false,
}
}
fetchMovie() {
this.setState({error:false,loading:true});
try {
// await fetch
this.setState({
...
})
} catch(error) {
this.setState({error});
}
}
fetchMovieProp = this.fetchMovie.bind(this); //<- this line is essentially "useCallback" for a class component
render() {
return <SomeOtherComponent fetchMovie={this.fetchMovieProp}/>
}
}
A bit more about hooks on functional vs class components
The beautiful thing about useCallback is, to implement it on a class component, just declare an instance property that is a function (bound to the instance) and you're done.
The purpose of useCallback is referential integrity so, basically, your React.memo's and React.PureComponent's will work properly.
const MyComponent = () => {
const myCallback = () => { ... do something };
return <SomeOtherComponent myCallback={myCallback}/> // every time `MyComponent` renders it will pass a new prop called `myCallback` to `SomeOtherComponent`
}
const MyComponent = () => {
const myCallback = useCallback(() => { ... do something },[...dependencies]);
return <SomeOtherComponent myCallback={myCallback}/> // every time `MyComponent` renders it will pass THE SAME callback to `SomeOtherComponent` UNLESS one of the dependencies changed
}
To replicate useCallback in class components you don't have to do anything:
class MyComponent extends Component {
method() { ... do something }
myCallback = this.method.bind(this); <- this is essentially `useCallback`
render() {
return <SomeOtherComponent myCallback={this.myCallback}/> // same referential integrity as `useCallback`
}
}
THE BIG ONE LINER
You'll find that hooks in react are just a mechanism to create instance variables (hint: the "instance" is a Fiber) when all you have is a function.
You can replicate the behavior ofuseCallback by using a memorized function for the given input(eg: movieId)
You can use lodash method
for more in-depth understanding check here