I have made two simple straight forward component is React, used a open source API to test API integration. React is showing this weird behavior of infinite console logs. I don't understand the issue. I'm using the fetch function for making API calls and functional component.
App component:
function App() {
const [characters, setCharac] = useState([])
const URL = "https://swapi.dev/api/";
fetch(URL + "people").then(response => response.json().then(data => {
setCharac(data.results)
console.log('Test');
}))
return (
<div className="App">
{characters.map(charac => {
return <Character {...charac} />
})}
</div>
);
}
Character component:
const Character = (props) => {
console.log(props);
return (
<div key={props.name}>
<h1>{props.name}</h1>
<p>{props.height}</p>
</div>
);
}
console.log('Test'); in App component and console.log(props); in Character component are being executed infinitely.
This is the render method
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Your components are rendering multiple times because your state is changed every time you fetch data (because of setState).
Try creating a function fetchData(). Make this function async as well to wait for data to be retrieved.
const fetchData = async () => {
const result = await fetch(URL + "people").then(response => response.json().then(data => {
setCharac(data.results)
console.log('Test');
return data;
}));
return result;
}
and then use it inside useEffect (Read more about useEffects: React hooks: What/Why `useEffect`?)
useEffect(() => {
fetchData();
}, []);
Note the usage of [] in useEffect. The data will be fetched only once when you load the component.
Try wrapping it in a useEffect
e.g.
useEffect(()=>{
fetch(URL + "people").then(response => response.json().then(data => {
setCharac(data.results)
console.log('Test');
}))
},[])
otherwise every time the state is set it is firing off the fetch again because a re-render is being triggered.
Because you fetch some data, update the state, which causes a re-render, which does another fetch, updates the state, which causes another render...etc.
Call your fetch function from inside a useEffect with an empty dependency array so that it only gets called once when the component is initially rendered.
Note 1: you can't immediately log the state after setting it as setting the state is an async process. You can, however, use another useEffect to watch for changes in the state, and log its updated value.
Note 2: I've used async/await in this example as the syntax is a little cleaner.
// Fetch the data and set the state
async function getData(endpoint) {
const json = await fetch(`${endpoint}/people`);
const data = await response.json();
setCharac(data.results);
}
// Call `getData` when the component initially renders
useEffect(() => {
const endpoint = 'https://swapi.dev/api';
getData(endpoint);
}, []);
// Watch for a change in the character state, and log it
useEffect(() => console.log(characters), [characters]);
You can do something like this:
import React, { useState, useEffect, useCallback } from "react";
const Character = (props) => {
console.log(props);
return (
<div key={props.name}>
<h1>{props.name}</h1>
<p>{props.height}</p>
</div>
);
};
export default function App() {
const [characters, setCharac] = useState([]);
const makeFetch = useCallback(() => {
const URL = "https://swapi.dev/api/";
fetch(URL + "people").then((response) =>
response.json().then((data) => {
setCharac(data.results);
console.log("Test");
})
);
}, []);
useEffect(() => {
makeFetch();
}, []);
return (
<div className="App">
{characters.map((charac) => {
return <Character {...charac} />;
})}
</div>
);
}
Related
I have a useEffect hook that is not updating after changing the dependency.
The dependency is a state variable passed through props. Included below is what the code looks like:
const MyComponent = ({resource}) => {
// runs after changing resource state
console.log(resource)
useEffect(() => {
setLoading(true);
// doesnt run after changing resource state
console.log(resource)
setVariable(setDefaultValue());
}, [resource]);
}
MyComponent is being rendered for one of two states 'option1' or 'option2' the component renders differently depending on the state. I call the component like so:
const [resource, setResource] = useState('option1');
const handleChange = (e) => {
setResource(e.target.value);
};
return (
<MyComponent resource={resource} />
)
I don't understand why the useEffect isn't running after resource state is changed. The console.log on the outside of the useEffect show the change state, but the console.log inside of the useffect isn't run after changing state.
Oh, you can change code the following above try:
//Parent Component
const [resource, setResource] = useState('option1');
const [variable,setVariable] = useState();
const [loading,setLoading] = useState(false);
useEffech(()=>{
let fetch_api = true;
setLoading(true);
fetch(URL,options).then(res=>res.json())
.then(res=>{
if(fetch_api){
setVariable(setDefaultValue());
setLoading(false);
}
});
return ()=>{
//remove memory
fetch_api = false;
}
},[resource]);
const handleChange = (e) => {
setResource(e.target.value);
};
return (
<MyComponent variable={variable} />
)
//Child Component
const MyComponent=({variable})=>{
return <>
</>
}
I have my state and I want to display the component if the value is true but in the console I receive the error message Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state my code
import React, { useState} from "react";
import { useToasts } from "react-toast-notifications";
const Index = () => {
const [test, setTest]= useState(true);
const { addToast } = useToasts();
function RenderToast() {
return (
<div>
{ addToast('message') }
</div>
)}
return (
<div>
{test && <RenderToast /> }
</div>
)
}
You cannot set state during a render. And I'm guessing that addToast internally sets some state.
And looking at the docs for that library, you don't explicitly render the toasts. You just call addToast and then the <ToastProvider/> farther up in the tree shows them.
So to make this simple example works where a toast is shown on mount, you should use an effect to add the toast after the first render, and make sure your component is wrapped by <ToastProvider>
const Index = () => {
const { addToast } = useToasts();
useEffect(() => {
addToast('message')
}, [])
return <>Some Content here</>
}
// Example app that includes the toast provider
const MyApp = () => {
<ToastProvider>
<Index />
</ToastProvider>
}
how i can display the toast based on a variable for exemple display toast after receive error on backend?
You simply call addToast where you are handling your server communication.
For example:
const Index = () => {
const { addToast } = useToasts();
useEffect(() => {
fetchDataFromApi()
.then(data => ...)
.catch(error => addToast(`error: ${error}`))
}, [])
//...
}
my question is if it is possible to trigger useEffect with a variable from outside the component.
In my case i have this main component that has the useEffect responsible to update info every time the variable "refresh" changes.
function Main() {
const [data, setData] = useState([])
const [refresh, setRefresh] = useState(false)
useEffect(async () => {
await axios.get(`${process.env.REACT_APP_SERVER_URL}/api/data/`)
.then(res => {
setData(res.data);
})
}, [refresh]);
And then i have a function that i can invoke inside the component or child that triggers the useEffect, updating the data.
const refreshData = () => setRefresh(!refresh);
So far so good, it works as i wanted but now i needed to export this function to use in a component not related to this one, but i know that you cannot export a function declared inside a component.
So my idea was to create this same function outside the component, like so:
let refreshOutside = false;
export const refreshMainFromOutside = () => {
refreshOutside = !refreshOutside;
}
So now i can add the variable "refreshOutside" to the useEffect, like so:
useEffect(async () => {
await axios.get(`${process.env.REACT_APP_SERVER_URL}/api/data/`)
.then(res => {
setData(res.data);
})
}, [refresh, refreshOutside]);
But if i import it in other component and invoke the method it does not trigger the useEffect, i am kinda new to react but i think its because the component is not re-rendering.
Is there any solution that might work on my case?
Thanks in advance.
I suggest you put your hooks inside another file for example useComponent.js and export your refreshData as const inside it, then use that hook inside any component you wish:
const useComponent = () => {
const [data, setData] = useState([])
const [refresh, setRefresh] = useState(false)
useEffect(async () => {
await axios.get(`${process.env.REACT_APP_SERVER_URL}/api/data/`)
.then(res => {
setData(res.data);
})
}, [refresh, refreshOutside]);
const refreshData = () => setRefresh(!refresh);
export { refreshData }
}
export default useComponent
import the hook inside any component then destructure functions and use them:
import useComponent from '../hooks/useComponent'
const MyComponent = () => {
const { refreshData } = useComponent()
return <button onClick={refreshData}>Click to refresh!</button>
}
export default MyComponent
As mentioned in the comment, you can simply define a separate function to fetch the data (and memoize it with the useCallback() hook), then you can use that function wherever you want in your Main component and in any Child component to whom you pass it as prop.
Maybe an example would make it easier to understand:
const Main = () => {
const [data, setData] = React.useState([]);
const updateData = React.useCallback((startIndex) => {
/*
axios.get(`${process.env.REACT_APP_SERVER_URL}/api/data/`)
.then(result => {
setData(result.data);
})
*/
axios.get('https://jsonplaceholder.typicode.com/posts/')
.then((result) => {
console.log('fetching data...');
setData(result.data.slice(startIndex, startIndex + 5));
console.log('... data updated');
});
}, [setData]);
React.useEffect(() => {
updateData(0); // fetch & update `data` when the Main component mounts
}, [])
return (
<div>
<h1>Main</h1>
{
data.length > 0
? <ul>{data.map(item => <li>{item.title}</li>)}</ul>
: <p>'There are no data'</p>
}
<button
onClick={() => updateData(5)} // fetch & update `data` on request, on button click
>Refresh from Main</button>
<Child handleClick={updateData} />
</div>
)
}
const Child = ({handleClick}) => {
return (
<React.Fragment>
<h1>Child</h1>
<button
onClick={() => handleClick(10)} // fetch & update `data` on request, on button click
>Refresh from Child</button>
</React.Fragment>
)
}
ReactDOM.render(<Main />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.js"></script>
<script crossorigin src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<div id="app"></div>
P.S. in the above example I used a parameter (startIndex) for the updateData() function just to keep the data state short and make it change when different buttons are clicked (because the API I used always returns the same data). In a real case use you are unlikely to do something like that because you can implement pagination on API side (if needed) and your API response is supposed to change over time (or you would not need a way to refresh your data).
Anyway, the point is that what I did inside the updateData() function body is mostly irrelevant; the main take away of the snippet is supposed to be how you can handle a function that needs to be called inside hooks, inside the main component and by child components.
I am building a simple recipe app and I have a problem with fetching my data from the API, because the code seems to run on every render and I do not even understand why it re-runs since I found that if I add the dependency array, it should run only once, right ?
App.js
function App() {
const [recipesList, setRecipesList] = useState([]);
let [scroll, setScroll] = useState(0)
console.log(recipesList,"list");
return (
<div className="App">
<img className="logo" src={logo} alt="Logo"/>
<Recipes recipesList={recipesList} getRecipes={setRecipesList} />
</div>
);
}
export default App;
Recipes.js
import React, {useEffect, useState} from "react";
import Recipe from "../Recipe/Recipe";
import "./Recipes.css";
const Recipes = (props) => {
useEffect( () => {
if (props.recipesList.length === 0) {
fetch("myapi.com/blablabla")
.then(res => res.json())
.then(result => {
props.getRecipes(result.recipes);
}
)
}
else {
console.log("Do not fetch");
}
return () => console.log("unmounting");
}, [props])
const recipeComponent = props.recipesList.map( (item) => {
return <Recipe className="recipe" info={item}/>
})
return(
<div className="recipes">
{recipeComponent}
<h1>Hello</h1>
</div>
)
}
export default Recipes;
Components will re-render every time your the props or state changes inside of the component.
I would recommend keeping the fetching logic inside of the Recipes component, because A: its recipe related data, not app related data. And B: this way you can control the state in Recipes instead of the props. This will give you more control on how the component behaves instead of being dependent on the parent component.
In the useEffect hook, leave the dependency array empty. This will cause the component to render, call useEffect only the first time, load your data and then render the recipes without re-rendering further.
import React, { useEffect, useState } from "react";
import Recipe from "../Recipe/Recipe";
import "./Recipes.css";
const Recipes = () => {
const [recipesList, setRecipesList] = useState([]);
useEffect(() => {
fetch("myapi.com/blablabla")
.then((res) => res.json())
.then((result) => {
setRecipesList(result.recipes);
});
return () => console.log("unmounting");
}, []);
// On the first render recipeComponents will be empty.
const recipeComponents = recipesList.map((item) => <Recipe className="recipe" info={item}/>)
return (
<div className="recipes">
{recipeComponents}
<h1>Hello</h1>
</div>
);
};
export default Recipes;
try this code :
function App() {
const [recipesList, setRecipesList] = useState([]);
let [scroll, setScroll] = useState(0)
const getListPropd = (e) => {
setRecipesList(e)
}
console.log(recipesList,"list");
return (
<div className="App">
<img className="logo" src={logo} alt="Logo"/>
<Recipes recipesList={(e) => getListPropd (e)} getRecipes={setRecipesList} />
</div>
);
}
export default App;
const [checkData , setCheckData ] = useState(true)
useEffect( () => {
if (checkData) {
fetch("myapi.com/blablabla")
.then(res => res.json())
.then(result => {
props.recipesList(result.recipes);
}
if(props.recipesList.length > 0) {
setCheckData(false)
}
)
else {
console.log("Do not fetch");
}
return () => console.log("unmounting");
}, [checkData])
the useEffect hook uses an empty dependency array, [] if it should ONLY run once after component is mounted. This is the equivalent of the old lifecycle method componentDidMount()
If you add a non-empty dependency array, then the component rerenders EVERY time this changes. In this case, every time your component receives new props (i.e. from a parent component, this triggers a reload.
see more info here https://reactjs.org/docs/hooks-effect.html , especially the yellow block at the bottom of the page
Happy coding!
I am using React Hooks to get data from an existing API. Here is my code
import React, { useState, useEffect } from "react";
export const People = () => {
const [people, setPeople] = useState([]);
const url = "http://127.0.0.1:5000/people";
async function fetchData() {
console.log("calling api .. ");
const res = await fetch(url);
res.json().then((res) => setPeople(res));
}
useEffect(() => {
fetchData();
});
return (
<div>
<ul>
{people &&
people.map((person) => <li key={person.id}>{person.name}</li>)}
</ul>
</div>
);
};
const Dashboard = (props) => {
return (
<div>
<People />
</div>
);
};
export default Dashboard;
The problem that I am having is this API is getting called over and over again. Can you please let me know what I am doing wrong here.
Thanks
Currently, using useEffect(callback) will execute the callback on every render.
Try using useEffect with an empty dependencies array.
If you want to run an effect and clean it up only once, you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run.
Check my other answer on useEffect use cases.
useEffect(() => {
fetchData();
}, []);
As for the rest of the component, it should look like so (opinioned):
// Should be on the outer scope as the URL is decoupled from the component's logic
const url = "http://127.0.0.1:5000/people";
export const People = () => {
const [people, setPeople] = useState([]);
useEffect(() => {
// This way fetchData won't re-assigned on every render
async function fetchData() {
console.log("calling api .. ");
const res = await fetch(URL);
// Consitstance, don't mix `then` and `await` syntax
const people = await res.json();
setPeople(people);
}
fetchData();
}, []);
return (
<div>
<ul>
{people &&
people.map((person) => <li key={person.id}>{person.name}</li>)}
</ul>
</div>
);
};