How to effectively use useEffect - reactjs

I'm currently learning how to use hooks in React and I'm currently on useEffect. For my last couple of projects I've been using the same API to pull data from (Opendota) and just doing different ways of calling the data. Any ideas of why this doesn't work? I assume I'm missing something important when it pretains to useEffect
heroes.services
import React from 'react'
const getStatsById = heroId => async () => {
const resp = await fetch("https://api.opendota.com/api/heroStats");
const statsList = await resp.json();
return statsList.find(stats => stats.id === heroId)
};
export {getStatsById};
Herodropdown
import React, { useState, useEffect } from "react";
import {getStatsById} from '../services/heroes.service.js'
const Herodropdown = () => {
useEffect(getStatsById(heroId));
return (
<h2> {heroId}</h2>
)
}
export default Herodropdown

const Herodropdown = () => {
useEffect(getStatsById(heroId));
return (
<h2> {heroId}</h2>
)
}
not defined heroId, the same as getStatsById(undefined)
but this only calls API ... probably you need to use a result of API call:
const Herodropdown = (heroId) => {
const [stats, setStats] = useState(null);
useEffect( () => { setStats( getStatsById(heroId) ) } );
return (
<>
<h2> {heroId}</h2>
stats: {stats}
</>
)
}
... but we don't need to use effect on every render, we should define that it should be fired only on heroId change:
useEffect( () => { setStats( getStatsById(heroId) ) }, [heroId] );
It can be [] for one time/initial call.

Related

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

How to wrap a function that uses hooks inside a useEffect?

I wrote a function to make an API call. Typically, I'd just wrap it in a useEffect and throw it in the same file that needs it, but I'm trying to write my code a little cleaner. So I did the following.
In my component.js file, I have the following:
import { apiCall } from '../../../framework/api.js';
import { useEffect, useState } from 'react';
export const Table = () => {
const [ resp, setResp ] = useState();
useEffect(() => {
console.log(apiCall());
}, []);
return(
<>
{ resp &&
resp.map(([key, value]) => {
console.log("key: " + key);
return(
<SomeComponent />
);
})
}
</>
);
}
in my api.js file, I have the following:
import axios from 'axios';
import { useState } from 'react';
export const apiCall = () => {
const [ resp, setResp ] = useState();
axios.get('https://some.domain/api/get').then((response) => {
setResp(response.data);
});
if(resp) return resp;
}
This always returns an error (Invalid hook call. Hook calls can only be called inside the body of a function component.)
If I rewrite my component.js and include the axios call directly inside useEffect instead of calling the function apiCall() from the external file, it obviously works with no problems.
I think I know it has to do with the fact that I'm using hooks in my apiCall function, and wrapping that call in a useEffect in my component.js. However, if I don't wrap it in a useEffect, it'll just run continuously and I don't want that either.
You have to follow the custom hook naming convention for this to be able to work. You can check out the documentation for that here: https://reactjs.org/docs/hooks-custom.html
Anyway, I believe in this case this should work:
import axios from 'axios';
import { useState } from 'react';
export const useApiCall = () => {
const [ resp, setResp ] = useState();
axios.get('https://some.domain/api/get').then((response) => {
setResp(response.data);
});
if(resp) return resp;
}
And then in component.js, you would call useApiCall()
Usually, we do it like this
export const useApiCall = () => {
const [ resp, setResp ] = useState();
useEffect(() => {
axios.get('https://some.domain/api/get').then((response) => {
setResp(response.data);
});
}, []);
return resp;
}
and then use it like so
export const Table = () => {
const resp = useApiCall();
return(
<>
{ resp &&
resp.map(([key, value]) => {
console.log("key: " + key);
return(
<SomeComponent />
);
})
}
</>
);
}
The prefix "use" in the function name is important, this is how we define a custom hook.
React Hook "useState" is called in function "apiCall" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
You can use following methods.
import { useState } from 'react';
export const ApiCall = () => {
const [state, setState] = useState();
};
or
import { useState } from 'react';
export const useApiCall = () => {
const [state, setState] = useState();
};

how use get method in react js

this is my react code here I fetch the data from the backend using mongo. my data is appearing in the console but not appearing on the web page it's showing `users.map is not a function. but if I try the jsonplaeholder API then its work properly.
import React, { useEffect, useState } from "react";
const Get = () => {
const [users,setUsers] = useState([]);
const getAllUser = async () => {
const response = await fetch("/get");
setUsers(await response.json());
console.log(users);
};
useEffect(() => {
getAllUser();
},[]);
return (
<>
{ users.map((ce) =>
<div key={ce.id}>
<h2>{ce.name}</h2>
<p>{ce.email}</p>
</div>)}
</>
)
}
export default Get;
this is the db data
{"status":"success","results":2,"data":{"users":[{"_id":"6134fcc6eddae0ec522fecd7","name":"ram ","email":"ram#gmail.com","number":9455294552,"__v":0},{"_id":"61364d918a8ab07512094443","name":"rawal","email":"rawal#gmail.com","number":9309304400,"__v":0}]}}
You need to properly set your state with res.data.users as follows.
import React, { useEffect, useState } from "react";
const Get = () => {
const [users, setUsers] = useState([]);
const getAllUser = async () => {
const response = await fetch("/get");
response.json().then((res) => setUsers(res.data.users));
console.log(users);
};
useEffect(() => {
getAllUser();
}, []);
return (
<>
{users.map((ce) => (
<div key={ce.id}>
<h2>{ce.name}</h2>
<p>{ce.email}</p>
</div>
))}
</>
);
};
export default Get;

How does react useEffect work with useState hook?

Can someone explain what am I'm doing wrong?
I have a react functional component, where I use useEffect hook to fetch some data from server and put that data to state value. Right after fetching data, at the same useHook I need to use that state value, but the value is clear for some reason. Take a look at my example, console has an empty string, but on the browser I can see that value.
import "./styles.css";
import React, { useEffect, useState } from "react";
const App = () => {
const [value, setValue] = useState("");
function fetchHello() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Hello World");
}, 1000);
});
}
const handleSetValue = async () => {
const hello = await fetchHello();
setValue(hello);
};
useEffect(() => {
const fetchData = async () => {
await handleSetValue();
console.log(value);
};
fetchData();
}, [value]);
return (
<div className="App">
<h1>{value}</h1>
</div>
);
};
export default App;
Link to codesandbox.
The useEffect hook will run after your component renders, and it will be re-run whenever one of the dependencies passed in the second argument's array changes.
In your effect, you are doing console.log(value) but in the dependency array you didn't pass value as a dependency. Thus, the effect only runs on mount (when value is still "") and never again.
By adding value to the dependency array, the effect will run on mount but also whenever value changes (which in a normal scenario you usually don't want to do, but that depends)
import "./styles.css";
import React, { useEffect, useState } from "react";
const App = () => {
const [value, setValue] = useState("");
function fetchHello() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Hello World");
}, 1000);
});
}
const handleSetValue = async () => {
const hello = await fetchHello();
setValue(hello);
};
useEffect(() => {
const fetchData = async () => {
await handleSetValue();
console.log(value);
};
fetchData();
}, [value]);
return (
<div className="App">
<h1>{value}</h1>
</div>
);
};
export default App;
Not sure exactly what you need to do, but if you need to do something with the returned value from your endpoint you should either do it with the endpoint returned value (instead of the one in the state) or handle the state value outside the hook
import "./styles.css";
import React, { useEffect, useState } from "react";
const App = () => {
const [value, setValue] = useState("");
function fetchHello() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Hello World");
}, 1000);
});
}
const handleSetValue = async () => {
const hello = await fetchHello();
// handle the returned value here
setValue(hello);
};
useEffect(() => {
const fetchData = async () => {
await handleSetValue();
};
fetchData();
}, []);
// Or handle the value stored in the state once is set
if(value) {
// do something
}
return (
<div className="App">
<h1>{value}</h1>
</div>
);
};
export default App;

Correct way to use useEffect() to update when data changes

The useEffect below renders, fetches data, and displays it once (using an empty array for 2nd parameter in useEffect).
I need it to rerun useEffect everytime the user changes data to the database (when user uses axios.post).
What i've tried
using [tickets], but that just causes the useEffect to run infinitly
also using [tickets.length] and [tickets, setTickets]
trying to use props as parameter but didnt find anything useful
import React, { useState, createContext, useEffect } from "react";
import axios from "axios";
export const TicketContext = createContext();
export const TicketProvider = (props) => {
console.log(props);
const [tickets, setTickets] = useState([]);
useEffect(() => {
getTickets();
console.log("1", { tickets });
}, []);
const getTickets = async () => {
const response = await axios.get("http://localhost:4000/tickets/");
setTickets(response.data);
};
return <TicketContext.Provider value={[tickets, setTickets]}>{props.children}
</TicketContext.Provider>;
};
import React from "react";
import { useState, useEffect, useContext } from "react";
import Ticket from "../Ticket";
import { TicketContext } from "../contexts/TicketContext";
import AddBacklog from "../addData/AddBacklog";
const TicketDisplay = (props) => {
const [tickets, setTickets] = useContext(TicketContext);
return (
<div className="display">
<p>Antony Blyakher</p>
<p>Number of Tickets: {tickets.length}</p>
<div className="backlog">
<h1>Backlog</h1>
{tickets.map((currentTicket, i) => (
<div className="ticketBlock">
<Ticket ticket={currentTicket} key={i} />
</div>
))}
</div>
</div>
);
const AddBacklog = (props) => {
const [tickets, setTickets] = useState("");
...
axios.post("http://localhost:4000/tickets/add", newTicket).then((res) => console.log(res.data));
setTickets((currentTickets) => [...currentTickets, { name: name, status: "backlog", id: uuid() }]);
};
You'll need to watch for tickets and return if it has data to not cause infinite loop:
useEffect(() => {
if (tickets.length) return // so, we call just once
getTickets();
console.log("1", { tickets });
}, [tickets]);
const fetchData = () => {
axios.get("http://localhost:7000/api/getData/").then((response) => {
console.log(response.data);
if (response.data.success) {
SetIsLoading(false);
}
setDataSource(response.data.data);
});
};
useEffect(() => {
fetchData();
if (fetchData.length) fetchData();
}, [fetchData]);
by this you can fetch the data in real-time as any change in data occurs.

Resources