React Asynchronous Fetching - javascript

Using React and React-Dom CDN 16
I am new to React and trying to build a dashboard component that takes the value of one of three buttons in a Buttons component and sends the value to a List component. The List component fetches data from an API and renders the results.
The feature works fine up until the data fetching, which it only does once the app is rendered the first time. I've logged that the state that's set by the Buttons component is making its way to the List component and the fetch action is updating dynamically correctly, but the fetching functionality isn't getting triggered when that state updates.
Here's the code.
const { useState, useEffect } = React
const App = props => {
return (
<div className="app-content">
<Dashboard />
</div>
);
};
const Dashboard = props => {
const [timespan, setTimespan] = useState('week');
const changeTime = time => setTimespan(time);
return(
<div>
<p>{timespan}</p> // this state updates when the buttons are clicked
<Buttons onUpdate={changeTime} />
<List timespan={timespan}/>
</div>
);
};
const Buttons = props => {
return (
<div>
<button onClick={props.onUpdate.bind( this, 'week' )}>
week
</button>
<button onClick={props.onUpdate.bind( this, 'month' )}>
month
</button>
<button onClick={props.onUpdate.bind( this, 'all' )}>
all
</button>
</div>
);
};
const List = props => {
const timespan = props.timespan;
const homepage = `${location.protocol}//${window.location.hostname}`;
const action = `${homepage}?fetchDataset=1&timespan=${timespan}`;
// if I console.log(action) here the URL is updated correctly
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [obj, setObj] = useState([]);
useEffect(() => {
fetch(action)
.then(res => res.json())
.then(
(result) => { // if I console.log(result) here I only get a response at initialization
setIsLoaded(true);
setObj(result);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
}, []);
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<div>
// my API returns "timespan is: $timespan", but this is only ever "week" because that's the initial state of the timespan
{obj}
</div>
);
};
};
ReactDOM.render(
<App />,
document.getElementById('app')
);
I think I must be overlooking something very obvious because this seems like one of the core purposes of React, but it's hard to find documentation that is relevant with version 16 updates like function classes and hooks.
I really appreciate any help. Thanks!

you need to add timeSpan (or action) to your useEffect dependency array:
useEffect(() => {
fetch(action)
.then(res => res.json())
.then(
result => {
setIsLoaded(true);
setObj(result);
},
error => {
setIsLoaded(true);
setError(error);
}
);
}, [timeSpan]); // [action] will also solve this
This way the effect will know it needs to run every time the timeSpan prop changes.
By passing an empty dependency array you are telling the effect to only run once - when the component it mounted.

Related

How to make a loading screen on react router?

I start learning react about 2 month ago. Right now I am trying to build my portfolio with some interactive design using spline 3d. The problem is the loading time is too long and I want to make a loading screen that stop loading exact time when my 3d start element render
There are multiple ways to create it by your self.
you can you use the library react-loader-spinner
on the console type npm install react-loader-spinner --save
import React from 'react';
import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";
import Loader from "react-loader-spinner";
import '../style.css';
const LoaderComponent = () => {
return (
<div className="loader">
<Loader
type="Circles"
color="#dc1c2c"
height={50}
width={100}
//timeout={1000} //3 secs
/>
</div>
);
};
export default LoaderComponent;
To display the component there are multiple ways, here is a way for GraphQL fetching data from the DB
const [results] = useQuery({ query: PRODUCT_QUERY });
const { data, fetching, error } = results;
//Check or the data coming in
if (fetching) return <p>Loading...</p>;
if (error) return <p>Oh no... {error.message}</p>;
Here is a way from fetching data with HTTP Request:
const UserList = () => {
const auth = useContext(AuthContext);
const { isLoading, error, sendRequest, clearError } = useHttpClient();
const [loadedUsers, setLoadedUsers] = useState();
useEffect(() => {
const fetchUsers = async () => {
try {
//with fetch, the default request type is GET request
const responseData = await sendRequest(
process.env.REACT_APP_BACKEND_URL + "/users"
);
setLoadedUsers(responseData.users); //users propeties is the given value from the backend (user-controllers.js on getUsers())
} catch (err) {}
};
fetchUsers();
}, [sendRequest]);
return (
<React.Fragment>
<ErrorModal error={error} onClear={clearError} />
{isLoading && <LoadingSpinner asOverlay />}
{/* we need to render loadedUsers only if not empty*/}
{!isLoading && loadedUsers && (
<div className="userList">
<span className="Title">Display Here the data</span>
</div>
)}
</React.Fragment>
);
};
// this logic is simple
// first, you have created one boolean usestate(false) and then load your screen that time usestate are true and process is complete after usesate are false
// I will show you the following example. I hope that helps you.
export default function Gradients(props) {
const [isLoading, setIsLoading] = useState(false);
const getAllGradient = () => {
setIsLoading(true);
axios
.get("https://localhost:5000")
.then((res) => {
const gradientColors = res.data;
// process complete after isLoading are false
// your process (this only example)
setIsLoading(false);
})
}
return(
<div>
{
isLoading ? <Loader> : <YourComponent />
}
</div>
)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

why useEffect is called infinitely

In useEffect in my react component I get data and I update a state, but I don't know why the useEffect is always executed:
const Comp1 = () => {
const [studies, setStudies]= useState([]);
React.useEffect( async()=>{
await axios.get('/api/expert/',
)
.then((response) => {
setStudies(response.data.studies);
}, (error) => {
console.log(error );
})
console.log("called+ "+ studies);
},[studies]);
return(
<Comp2 studies={studies}/>
)
}
Here is my second Component used in the first component...
const Comp2 = (props) => {
const [studies, setStudies]= useState([]);
React.useEffect( ()=>{
setStudies(props.studies)
},[props.studies, studies]);
return(
studies.map((study)=>{console.log(study)})
}
EDIT
const Comp2 = (props) => {
// for some brief time props.studies will be an empty array, []
// you need to decide what to do while props.studies is empty.
// you can show some loading message, show some loading status,
// show an empty list, do whatever you want to indicate
// progress, dont anxious out your users
return (
props.studies.map((study)=>{console.log(study)}
)
}
You useEffect hook depends on the updates that the state studies receive. Inside this useEffect hook you update studies. Can you see that the useEffect triggers itself?
A updates B. A runs whenever B is updated. (goes on forever)
How I'd do it?
const Comp1 = () => {
const [studies, setStudies]= useState([]);
React.useEffect(() => {
const asyncCall = async () => {
await axios.get('/api/expert/',
)
.then((response) => {
setStudies(response.data.studies);
}, (error) => {
console.log(error );
})
console.log("called+ "+ studies);
}
asyncCall();
}, []);
return(
<Comp2 studies={studies}/>
)
}
useEffect() has dependency array which causes it to execute if any value within it updates. Here, setStudies updates studies which is provided as dependency array and causes it to run again and so on. To prevent this, remove studies from the dependency array.
Refer: How the useEffect Hook Works (with Examples)

React useEffect updating with default initialized useState

I'm trying to create an array state variable called usersInfos that I can append to whenever I do an api call, but useEffect is updating with the initial value of the userData state variable.
// api.js
import axios from 'axios';
export function fetchUserData () {
return axios.get('https://randomuser.me/api')
.then(res => {
return res;
})
.catch(err => {
console.error(err);
})
}
import { fetchUserData } from '../../src/api';
import { useState, useEffect } from 'react';
import ProfileCard from './profilecard';
export default function UserProfile() {
const [userData, setUserData] = useState();
const [usersInfos, setUsersInfos] = useState([]);
const getUserData = async () => {
let ud = await fetchUserData()
setUserData(ud);
}
useEffect(() => {
getUserData();
}, [])
useEffect(() => {
const newInfos = [
...usersInfos,
userData,
]
setUsersInfos(newInfos);
console.log(usersInfos);
}, [userData])
return (
<div className='userprofile'>
<button onClick={() => { getUserData() }}> Fetch Random User </button>
<button onClick={() => { console.log(usersInfos) }}> log usersInfos </button>
{usersInfos.map((user, idx) => {
<ProfileCard userData={user} key={idx} />
})}
</div>
)
}
I'm assuming it's something to do with the state batch updating, but I'm not sure. What would be the best practice way of doing this?
I am console logging with the button at the bottom, after the page has loaded.
When userData is initialized to (), I get console log: (2) [undefined, {…}]
When userData is initialized to (0), I get console log: (2) [0, {…}].
When userData is initialized to ([]), i get console log:(2) [Array(0), {…}]
Thanks
When the 2nd effect runs for the first time, userData is undefined because the network request isn't resolved yet. You could simply run the effect only when userData is not undefined.
useEffect(() => {
if (userData) {
const newInfos = [
...usersInfos,
userData,
]
setUsersInfos(newInfos);
}
}, [userData])
However, if the code presented in the question is almost complete, I would use the code below instead. In this way I can update both states with a single effect.
The code below is a snippet from the CodeSandbox fully functional example.
export default function UserProfile() {
const [userData, setUserData] = useState();
const [usersInfos, setUsersInfos] = useState([]);
const getUserData = useCallback(async () => {
let ud = await fetchUserData();
setUserData(ud);
setUsersInfos((prevInfos) => [...prevInfos, ud]);
}, []);
useEffect(() => {
getUserData();
}, [getUserData]);
return (
<div className="userprofile">
<button onClick={getUserData}>Fetch Random User</button>
<button
onClick={() => {
console.log(usersInfos);
}}
>
log usersInfos
</button>
{userData && (
<p>
Current user: {userData.name.first} {userData.name.last}
</p>
)}
<ul>
{usersInfos.map((user, idx) => (
<ProfileCard userData={user} key={idx} />
))}
</ul>
</div>
);
}
If you try to log userInfos just after the setter, it will log the previous state because the function inside the effect is a closure and it captures the value of the state when the effect runs. Since React state is immutable the state update does not works like an assignment but it will be updated at the next render.

How to render based on AJAX result in react js?

Just want to render movie cards based on results that come from ajax call.
Currently, the movie cards components are rendered based on that hard code array named list. I just want to make it dynamic and replace it with my ajax data.
const getlist = async () => {
const res = await fetch('http://localhost:3001/customize');
const data = await response.json();
getlist();
};
export default function Index() {
const list = ['dexter', 'bb', 'got'];
return (
<>
<main className={parentstyle.main_container}>
<NavBar />
<div className={style.searchbar_container}>
<SearchBar />
</div>
<div className={style.card_container}>
{test.map((element, i) => {
return <MovieCard movieName={element} key={i} />;
})}
</div>
</main>
</>
);
}
Use the useState hook to set up your component state (the list) and fetch data in a useEffect hook...
The Effect Hook lets you perform side effects in function components:
Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects. Whether or not you’re used to calling these operations “side effects” (or just “effects”), you’ve likely performed them in your components before.
import { useEffect, useState } from "react"
const getlist = async () => {
const res = await fetch("http://localhost:3001/customize")
if (!res.ok) {
throw new Error(`${res.status}: ${await res.text()}`)
}
return res.json()
}
const Index = () => {
const [ list, setList ] = useState([]) // start with an empty array
useEffect(() => {
getList()
.then(setList)
.catch(console.error)
}, []) // empty dependencies array, this runs only once
return (
// ...
{list.map((element, i) => (
<MovieCard movieName={element} key={i} />
))}
// ...
)
}
export default Index

Lifting up the state to the main component in React application using hooks

I am learning reactjs and trying to implement small things for practice. The idea is simple, to add records (feedbackTasks) to the database and list these records (when first time page is loaded and later when a new record is added). Please see the image below.
The main component is ManageFeedbackTasks. I keep the list of the feedbackTask items in its state (st_feedbackTaskList). The update of this list is performed through add_to_st_feedbackTask function. If the first time this list is generated, all the fetched data (coming from PrintFeedbackTasks component) is set to the st_feedbackTaskList. If not, only the added item (coming from ShowAddingFeedbackTaskForm) is inserted in the list.
export function ManageFeedbackTasks() {
const [st_feedbackTaskList, setFeedbackTaskList] = useState([]);
const add_to_st_feedbackTask = (data) => {
if (st_feedbackTaskList.length == 0) {
setFeedbackTaskList(data);
} else {
const { id, title, description } = data;
setFeedbackTaskList([...st_feedbackTaskList, { id, title, description }]);
}
}
return (
<>
<ShowAddingFeedbackTaskForm onAddingItem={add_to_st_feedbackTask} />
<PrintFeedbackTasks onListingFeedbackTasks={add_to_st_feedbackTask} feedbackTasks={st_feedbackTaskList} />
</>
);
}
Below is the PrintFeedbackTasks function. This function receives the feedbackTasks list from the main component ManageFeedbackTasks. This list is first time fetched from the database using fetchFeedbackTasks. Inside fetchFeedbackTasks, props.onListingFeedbackTasks(response.data) sends the fetched list back to the main component to update the state (st_feedbackTaskList).
const PrintFeedbackTasks = (props) => {
const [st_isInitialized, setInitialized] = useState(false);
const fetchFeedbackTasks = () => {
axios.get('api/FeedbackTask/Index')
.then(response => props.onListingFeedbackTasks(response.data))
.catch(error => console.log(error));
}
useEffect(() => {
if (!st_isInitialized) {
fetchFeedbackTasks();
}
setInitialized(true);
});
return (
<React.Fragment>
{
props.feedbackTasks.map(taskItem =>....
}
</React.Fragment>
);
}
The component below displays the Add form and handles the form submission. When a new item is added, this new item is again sent back to the main component using props.onAddingItem.
const ShowAddingFeedbackTaskForm = (props) => {
const [st_title, setTitle] = useState('');
const [st_description, setDescription] = useState('');
const handleSubmit = async (event) => {
event.preventDefault();
await axios(...)
.then(function (response) {
setTitle('');
setDescription('');
//This will update the list of the feedback task in the main component
props.onAddingItem({
id: response.data,
title: st_title,
description: st_description
});
//GET THE ID HERE
console.log(response.data);
}).catch(function (error) {
console.log(error);
});
}
return (
<form onSubmit={handleSubmit}>
<input
placeholder="Title..."
type="text"
value={st_title}
onChange={(event) => setTitle(event.target.value)}
/>
<input
placeholder="Description..."
type="text"
value={st_description}
onChange={(event) => setDescription(event.target.value)}
/>
<button>Add Feedback Task</button>
</form>
);
}
I wonder if this way of lifting and managing the state is robust. Any suggestions to improve the code? Also, I wonder if I should put these components into their own pages (for example, one page or adding a record and another one for listing). Would this make more sense in the react world?
The idea to lift the state up to the parent is correct. However due to your code structure you could be causing a lot of re-renders and a few performance optimizations can be made in your solution. One more thing is that instead of fetching feedbackTasks in PrintFeedbackTasks component you should do it in the parent itself. Also useEffect takes a second parameter which you can use to execute it on initial mount
You can use useCallback hook to memoize functions too.
ManageFeedbackTasks
export function ManageFeedbackTasks() {
const [st_feedbackTaskList, setFeedbackTaskList] = useState([]);
const fetchFeedbackTasks = useCallback(() => {
axios.get('api/FeedbackTask/Index')
.then(response => props.onListingFeedbackTasks(response.data))
.catch(error => console.log(error));
}, []);
useEffect(() => {
fetchFeedbackTasks();
}, []);
const add_to_st_feedbackTask = useCallback((data) => {
setFeedbackTaskList(prevTaskList => {
if (prevTaskList.length == 0) {
return data;
} else {
const { id, title, description } = data;
return [...prevTaskList, { id, title, description }];
}
});
}, [])
return (
<>
<ShowAddingFeedbackTaskForm onAddingItem={add_to_st_feedbackTask} />
<PrintFeedbackTasks onListingFeedbackTasks={add_to_st_feedbackTask} feedbackTasks={st_feedbackTaskList} />
</>
);
}
PrintFeedbackTasks
const PrintFeedbackTasks = (props) => {
return (
<React.Fragment>
{
props.feedbackTasks.map(taskItem =>....
}
</React.Fragment>
);
}
As far as the idea to split show and update TaskList is concerned it as product decision which can be made depending on how long is the list of fields that the user needs to fill at once

Categories

Resources