data coming from custom fetch hook and local state - reactjs

I want to have a local setter to update data I'm extracting from custom fetch hook:
function ContactList(props) {
const contacts =useFetch(fetchUrl, []);**//contain data**
const [data,setData] = useState(contacts);**//remains empty**
return (
<div>
<Navbar />
<ul className="collection">
<ContactItem contacts={contacts} setContacts={setData} />
</ul>
</div>
);
}
fetchHook looks like this:
export const useFetch = (url, initialValue) => {
const [result, setResult] = useState(initialValue);
useEffect(() => {
const fetchData = async () => {
const result = await axios(url);
setResult(result.data.contact);
};
fetchData();
}, []);
return result
}
so I need the contactList to have a setter which I currently don't manage to achieve because state remain an empty array.

const contacts =useFetch(fetchUrl, []);
useFetch is asynchronous and makes the call and gives the result after the componentMounts, since I'm assuming you put that in a useEffect with empty dependency in your hook.
So, the data won't be available on the first render, making it initialize to empty.
EDIT:
To understand better, follow the console.log in the following example:
https://codesandbox.io/embed/friendly-ives-xt5k0
You'll see that it is empty first and then it gets the value.
Now, the same is the case in your code. On the first render it's empty and that is when you set the state of result. Instead, you can just use the value of contacts in your case.
EDIT 2:
After seeing what you need, all you have to do is to also return the setResult from the hook:
export const useFetch = (url, initialValue) => {
const [result, setResult] = useState(initialValue);
useEffect(() => {
const fetchData = async () => {
const result = await axios(url);
setResult(result.data.contact);
};
fetchData();
}, []);
return {result, setResult}
}
function ContactList(props) {
const {result, setResult} =useFetch(fetchUrl, []);
return (
<div>
<Navbar />
<ul className="collection">
<ContactItem contacts={result} setContacts={setResult} />
</ul>
</div>
);
}

Related

Use multiple React useState() hooks to fetch Category title and posts from json api

I am using React useState() and useEffect to fetch multiple data from an api (mock-data/my-data.json).
From the api I want to fetch the Category Title and the list of Posts and render those in the jsx.
Is this the correct way to use useState() and useEffect() to fetch the data? I made multiple useState constants so after I can render the Title and the list op Posts in the render method:
function ArticleList() {
const [categoryTitle, setCategoryTitle] = useState('')
const [posts, setPosts] = useState([])
const [loading, setLoading] = useState(false)
useEffect(() => {
setLoading(true)
axios
.get('mock-data/my-data.json')
.then(res => {
console.log(res.data)
setCategoryTitle(res.data['title'])
setPosts(res.data.allItems)
setLoading(false)
})
.catch(err => {
console.log(err)
})
}, [])
if (loading) {
return <p>Loading articles...</p>
}
return (
<div>
<h1>{categoryTitle}</h1>
<ul>
{posts.map(post => (
<li key={post.id}>{post.titel}</li>
))}
</ul>
</div>
)
}
Usually its a good idea to use useReducer hook instead of using multiple useState hooks if you want to set multiple states simultaneously. Your example is that case where you are trying to set multiple states together.
useReducer uses reducer function where consolidated state can be manipulated. This makes code more cohesive and more readable in my opinion.
Please see below sample code. For simplicity I have removed axios but you should get the idea.
https://codesandbox.io/s/bold-platform-hv6pi?file=/src/ArticleList.js
This looks good to me.
If I were to refactor this code, I would prefer to use async/await, and I like to use state enums rather than booleans, for loading/loaded/error states.
function ArticleList() {
const [categoryTitle, setCategoryTitle] = useState('');
const [posts, setPosts] = useState([]);
const [state, setState] = useState('loading');
useEffect(() => {
const fetchPosts = async () => {
setState('loading');
let response = axios.get('mock-data/my-data.json');
try {
response = await response;
} catch (error) {
setState('error');
return;
}
setCategoryTitle(response.data['title']);
setPosts(response.data.allItems);
setState('loaded');
};
fetchPosts();
}, []);
if (state === 'loading') {
return <p>Loading articles...</p>;
}
if (state === 'error') {
return <p>Error loading articles...</p>;
}
return (
<div>
<h1>{categoryTitle}</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.titel}</li>
))}
</ul>
</div>
);
}
Yup, this is the correct way. If you wanna use only one useState, you can clone the state object.
For example:
const [state, setState] = useState({
title: "",
posts: []
});
// For Title
setState({...state, title: "New Title"})
// For Posts
setState({...state, posts: [{...post...}]}
But I would like to use lodash clone, immer library or something like that.
This is pretty much it, the only thing that I would highly recommend is use await instead of the famous hadoukens/pyramids of doom, so a plain code would be:
function ArticleList() {
const [categoryTitle, setCategoryTitle] = useState('')
const [posts, setPosts] = useState([])
const [loading, setLoading] = useState(false)
useEffect(() => {
async function getData(){
try{
setLoading(true)
res = await axios.get('mock-data/my-data.json');
console.log(res.data)
setCategoryTitle(res.data['title'])
setPosts(res.data.allItems)
setLoading(false)
}catch(err){
console.log(err)
}
}
getData();
}, [])
if (loading) {
return <p>Loading articles...</p>
}
return (
<div>
<h1>{categoryTitle}</h1>
<ul>
{posts.map(post => (
<li key={post.id}>{post.titel}</li>
))}
</ul>
</div>
)
}

Simple function that retrieves data from an API is not returning the data

I have this React component that used to return an HTML element like this:
const PartsList = () => {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://localhost:44376/api/parts',
);
setData(result.data);
};
fetchData();
}, []);
return (
<>
{data.map((item, index) => (
<label key={index} className="inline">
<Field key={index} type="checkbox" name="machineParts" value={item.id} />
{item.name}
</label>
))}
</>
);
}
export default PartsList;
Now, I want it to return only an array of JSON, no HTML.
So I tried modifying the component so that it looks like this:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://localhost:44376/api/machines',
);
setData(result.data);
console.log("data as seen in function: ", JSON.stringify(result, null, 2));
};
fetchData();
}, []);
return data;
When I write it out to the console in this function, I see all the needed data.
But when I write it out to the console in the main App.js, I just see undefined.
What could I be doing wrong?
Thanks!
Originally you wanted a component because it had to render HTML.
Now what you actually need is to move everything out to a function.
So you can do this in your main App.js:
import React from 'react';
import axios from 'axios';
const fetchData = async () => {
const result = await axios(
'https://localhost:44376/api/machines',
);
return JSON.stringify(result, null, 2);
};
const App = () => {
const result = await fetchData()
console.log(result)
return <div>Main App<div>
}
export default App
This is how you make a function to return data that you can call to see the console result in your main App component.
This obviously just demonstrates the concept, you can take it further by moving that function out to its own file that you can import into your App.js folder.

How can I make react's useEffect to stop rerender in an infinite loop?

So I am working on a small personal project that is a todo app basically, but with a backend with express and mongo. I use useEffect() to make an axios get request to the /all-todos endpoint that returns all of my todos. Then inside of useEffect()'s dependency array I specify the todos array as dependency, meaning that I want the useEffect() to cause a rerender anytime the tasks array gets modified in any way. Take a look at this:
export default function () {
const [todos, setTodos] = useState([]);
const currentUser = JSON.parse(localStorage.getItem('user'))._id;
useEffect(() => {
function populateTodos () {
axios.get(`http://localhost:8000/api/all-todos/${currentUser}`)
.then(res => setTodos(res.data))
.catch(err => console.log(err));
}
populateTodos();
}, [todos]);
console.log(todos);
return (
<div className="App">
...
</div>
);
}
So I placed that console.log() there to give me a proof that the component gets rerendered, and the problem is that the console.log() gets printed to the console forever, until the browser gets slower and slower.
How can I make it so that the useEffect() gets triggered obly when todos gets changed?
You should execute the hook only if currentUser changes:
export default function () {
const [todos, setTodos] = useState([]);
const [error, setError] = useState(null);
const currentUser = JSON.parse(localStorage.getItem('user'))._id;
useEffect(() => {
function populateTodos () {
axios.get(`http://localhost:8000/api/all-todos/${currentUser}`)
.then(res => setTodos(res.data))
.catch(err => setError(err));
}
populateTodos();
}, [currentUser]);
console.log(todos);
if (error) return (
<div className="App">
There was an error fetching resources: {JSON.stringify(error)}
</div>
)
return (
<div className="App">
...
</div>
);
}

React .map an object

I've got an eslint error Property 'date' does not exist on type 'never', and it's on data.date. Any idea how can I fix it?
import React from 'react'
import app from './../Flamelink'
function App() {
const [data, setData] = React.useState([])
React.useEffect(() => {
const fetchData = async () => {
const data = await app.content.get({
schemaKey: 'persons'
})
setData(data)
}
fetchData()
}, [])
return (
<ul>
{data.map(data => (
<li>{data.date}</li>
))}
</ul>
)
}
export default App
First of all, I would use a different variable name instead of just calling everything data. This way you avoid variable shadowing errors.
If you want your state variable to be data then call the answer you get from your fetch something else, result maybe. Then, when mapping your data state, call the current value something else too, maybe item or dataItem.
Second, since you appear to be using TypeScript, you need to tell TS the structure of your data state array.
Try this:
function App() {
const [data, setData] = React.useState<Array<{date: string}>>([]);
React.useEffect(() => {
const fetchData = async () => {
const result = await app.content.get({
schemaKey: "persons"
});
setData(result);
};
fetchData();
}, []);
return (
<ul>
{data.map(item=> (
<li>
{item.date}
</li>
))}
</ul>
);
}

Too many re-renders with React Hooks

Similar questions have been asked but I haven't found a solution for this particular one. I have one component which renders all boards and I am using a custom useFetch hook to fetch all boards.
const BoardsDashboard = () => {
let [boards, setBoards] = useState([]);
const { response } = useFetch(routes.BOARDS_INDEX_URL, {});
setBoards(response);
return (
<main className="dashboard">
<section className="board-group">
<header>
<div className="board-section-logo">
<span className="person-logo"></span>
</div>
<h2>Personal Boards</h2>
</header>
<ul className="dashboard-board-tiles">
{boards.map(board => (
<BoardTile title={board.title} id={board.id} key={board.id} />
))}
<CreateBoardTile />
</ul>
</section>
</main>
);
};
const useFetch = (url, options) => {
const [response, setResponse] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url, options);
const json = await res.json();
setResponse(json);
} catch (error) {
setError(error);
}
};
fetchData();
}, []);
return { response, error };
};
I am getting too many re-renders due to setBoards(response) line. What is the right way to handle this?
Thanks!
Sounds like you might want a useEffect hook to take action when response is updated.
useEffect(() => {
setBoards(response);
}, [response]);
Note: if you have no need to ever change the boards state, then maybe it doesn’t need to be stateful at all, you could just use the returned value from your useFetch hook and be done with it.
const { response: boards } = useFetch(...);

Resources