How to properly implement "isLoading" in a parent component - reactjs

I'd like to create a reusable component that may contain various children that might require loading data. The parent component doesn't (and should not) know, if any childs need to fetch additional data. It looks like this:
function MyPage(props) {
return (
<>
<WidgetA/>
<WidgetB/>
<WidgetC/>
</>
);
}
Now, instead of showing 3 spinners for each individual widget, I'd like to show just one spinner for the whole page. Also, when there is no data available (none of the childs returned any HTML), I'd like to show another component saying something like "There is no data available".
I've already tried a simple idea, namely to return null from a child to indicate that it isn't ready yet.
function MyPage(props) {
const widgetA = <WidgetA/>
if(!widgetA) {
return <div>Loading</div>
}
return (
<div>
<WidgetA/>
</div>
);
}
function WidgetA() {
// ...
if(loading) return null
}
However, this does not work because I am unable to determine whether a component is returning something or not. The component is never null, React.Children.count doesn't work and so on.

You can use ternary condition for this- Sample app
function MyPage() {
const [isLoading, setLoading] = React.useState(true);
React.useEffect(()=>{
fetch('https://jsonplaceholder.typicode.com/users')
.then(res=>res.json())
.then(res=>{
setLoading(false)
console.log(res)
})
},[])
return (
<div>
{!isLoading ?
<>
<WidgetA/>
<WidgetB/>
<WidgetC/>
</>:<h2>Loading....</h2>
}
</div>
);
}
isLoading will be set true/false as per your condition like fetching api or inside click function you can set true/false.
Live working demo to use loading.

Use some central store management like redux. And call that loading variable in header component which is common for all three child components.
Make api calls from redux and toggle isLaoding variable on api calls.
Your job done.

Related

React apollo client api calling two time

This is my React code of ApolloClient
function EmpTable() {
const GET_EMPLOYEE = gql`
query refetch($id: String) {
employeeById(id: $id) {
id
name
role
}
}
`;
const {refetch} = useQuery(GET_EMPLOYEE)
const getEmpByID = (id) => {
refetch({
id: id
}).then((response) => {
// do something
})
}
return (
<div className="row">
{/* { I am rending list of employee with map and passing id this way
<a onClick={() => getEmpByID(id)}>get employ info</a>
} */}
</div>
);
}
export default EmpTable;
Everything is working very well in this code, the only problem is the API being called two times, first time it returns no data, and 2nd time, it returns the expected data.
How can I prevent calling this twice?
I guess this is executing first time: const {refetch} = useQuery(GET_EMPLOYEE) and making the first request without data, because, no variable is passed there. I know I can can pass a variable in useQuery first time but the problem is that I can’t pass this from there, because the query params are not in my state or props.
Can anyone tell me what is the possible solution for this?
Due to documentantion:
When React mounts and renders a component that calls the useQuery
hook, Apollo Client automatically executes the specified query. But
what if you want to execute a query in response to a different event,
such as a user clicking a button? The useLazyQuery hook is perfect for
executing queries in response to events other than component
rendering. This hook acts just like useQuery, with one key exception:
when useLazyQuery is called, it does not immediately execute its
associated query. Instead, it returns a function in its result tuple
that you can call whenever you're ready to execute the query:
Example :
const GET_COUNTRIES = gql`
{
countries {
code
name
}
}
`;
export function DelayedCountries() {
const [getCountries, { loading, data }] = useLazyQuery(GET_COUNTRIES);
if (loading) return <p>Loading ...</p>;
if (data && data.countries) {
console.log(data.countries);
}
return (
<div>
<button onClick={() => getCountries()}>
Click me to print all countries!
</button>
{data &&
data.countries &&
data.countries.map((c, i) => <div key={i}>{c.name}</div>)}
</div>
);
}
useLazyQuery will be executed at the moment, when getCountries is called.
https://codesandbox.io/s/apollo-client-uselazyquery-example-6ui35?file=/src/Countries.js
This is a normal behavior in React 18, all events are called twice, this is new in React 18, and, only happens in development.
Now, why are they doing this? because it helps to identify bugged effects and hooks, and it works.
If you wanna disable this you can do it by disabling strict mode (https://reactjs.org/docs/strict-mode.html).
So, if the component is working, you have no warning about data leaking stay calm and continue. Another option to try is compile your application and run it in production mode, if the hook it's called twice then yeah, by all means, validate your component code (and it's parent components)

synchronous operation in componentDidMount()

I have a stateful component in which i fetch a userlist in componentDidMount(). Right after I'm checking whether the list is empty, and if so, create a placeholder to display.
Since the fetching of the list is an asynchronous operation I should probably not check for the empty list like so:
componentDidMount() {
this.props.dispatch(fetchUsersOfList());
if (this.props.userBases.length === 0) {
...
}
}
I have this currently solved by writing the if statement inside a componentDidUpdate():
componentDidUpdate(prevProps) {
if (this.props.userBases.length === 0) {
if (this.props.currentBase !== prevProps.currentBase) {
...
}
}
}
I am using redux and updating the state after each function has run/completed.
The issue now is that each time I delay an operation like this I delay displaying data to the user. Eventually this adds up and is noticeable. Hence the times for users that they see turning loading-wheels increases.
Is there another, faster way to solve this concept?
I belive you're returning response payload from your action and throwing error as well.
You get your resolved promise in then like this:
componentDidMount() {
this.props.dispatch(fetchUsersOfList()).then((response)=>{
if (// your condition goes here") {
...
}
})
}
Feel free to ask any question
I am not sure what you mean by a placeholder, but most probably you have one of two needs:
If your list is empty, show a message instead of a li. (No data found or something on that vein)
or Show some initial text while you wait for data (for e.x. Loading...)
The first is basically conditional rendering. in your render method, you can have something like:
if (this.props.userBases && this.props.userBases.length > 0 ) {
return <Blah />
} else {
return <Placeholder />
}
The 2nd challenge is essentially setting an initial state. Idea is that you give your component an initial state and then when the async action has finished, it updates the state and react re-renders it. I am not using redux, but they seem to offer a recipe for it.
Update On closer reading of your question, I think you are using the lifecycles in the wrong way as well. I would highly recommend reading the official blog post on async rendering in react to understand the lifecycles better.
You don't need to save things on state. You can return early from render with a different result. When the props change, render will be called again with newer props.
class MyComponent {
render() {
const { items } = this.props;
if (!items || !items.length) return <span>Loading...</span>;
return <List items={items} />;
}
}
Handle your subscription via connect method.
import { connect } from 'react-redux';
const mapStateToProps = (state) => {
const items = cartItemsSelector(state);
return {
items,
fetched: items !== null
}
};
export const CartItemListContainer = connect(
mapStateToProps
)(CartItemListDisplay);

Calling a graphQL query from within react client

I'm trying to return a list of all users transactions, but I can't seem to call the graphQL query correctly. I've defined the query at the bottom and I'm trying to call it in refreshData(). What am I missing?
async refreshData() {
const allTransactions = await graphql(getTransactionsQuery);
console.log(allTransactions);
this.setState({
transactions: allTransactions
});
}
render() {
const transactionsFromState = this.state.transactions;
const transactions = transactionsFromState.map((transaction) =>
<li key={transaction.id}>{transaction}</li>
);
return (
<div className="App">
<button onClick={() => this.refreshData()}> Refresh Data </button>
<ul>{ transactions }</ul>
</div>
);
}
}
const getTransactionsQuery = gql`
query getTransactions($id: ID!) {
transactions(userId: $id){Transaction}
}
`;
//change User ID use context.user.id
export default graphql(getTransactionsQuery)(App);
Using the graphql Higher Order Component
The graphql function you're calling returns a higher order component that can be then used to wrap another React component. It does not return a promise that can be awaited like you're trying to do.
You are already utilizing it (more or less) correctly here:
export default graphql(getTransactionsQuery)(App)
When you wrap your component with the graphql HOC like this, your component receives a data prop that can then be used inside your component's render method. This negates the need to utilize component state to persist your query results. Your render method should look something like this:
render() {
const { transactions } = this.props.data
const transactionItems = transactions
? transactions.map(t => <li key={t.id}>{t}</li>)
: null
return (
<div className="App">
<button onClick={() => this.refreshData()}> Refresh Data </button>
<ul>{ transactionItems }</ul>
</div>
);
}
data.transactions will be initially undefined before the query is fetched, so it's important to be mindful of that inside your render function. You can also check data.loading to determine if the query is still in flight. Please check the Apollo docs (here) for more examples.
Fetching a query with variables
Your query utilizes variables, so those variables need to be sent along with it. You'll need to pass those into the graphql function as options like this:
export default graphql(getTransactionsQuery, { options: { variables: { id: 'youridhere' } } })(App)
Options is normally passed an object, but it can also be passed a function. This function takes props as an argument, so that you can derive your variables from props if needed. For example:
const options = props => ({ variables: { id: props.id } })
export default graphql(getTransactionsQuery, { options })(App)
Check here for more information.
Refetching data
Since Apollo does the heavy lifting for you, there's no need to have a button to fetch the data from your server. But should you need to refetch your data, you can do so through the data prop passed down to your component by calling props.data.refetch(). You can read more about refetch here.

How to get the updated props immediately after service call in reactjs?

In component I want to get the data immediately in props after calling
webapi service call and do some operation,but issue is that it is not
updating the props immediately because as we know that call will be
async, So what will be the solution? My codes in component are like
this:-
openPreviewClick=(event) => {
this.props.GetReport();
console.log(this.props.reportData);
}
function mapStateToProps (allReducers) {
return {reportData: allReducers.reportData
}}
const matchDispatchToProps = (dispatch) => ({
GetReport: () => dispatch(LoadReportData())
})
export default connect(mapStateToProps, matchDispatchToProps)(MyContainer)
Now I have to open a pdf for this I have tried with two solution:-
Handling life cycle of the page
componentWillReceiveProps(nextProps) {
if(nextProps.reportPath!=undefined){
window.open(nextProps.reportPath,"thePop","menubar=1,resizable=1,scrollbars=1,status=1,top=280,width=850,height=600");
}
Writing the code in render
render () {
if(this.props.reportPath!=undefined && this.props.reportPath!=""){}
window.open(this.props.reportPath,"thePop","menubar=1,resizable=1,scrollbars=1,status=1,top=280,width=850,height=600");
}
openPreviewClick is my button click on which I want to access the
props named as reportData.But console.log(this.props.reportData); is
giving me the null value for the first time,second time if I will
click then we are getting the data.How we can manage this? I already tried above two solution but it is not working.
Simple answer, you don't ^1
If this is truely an async request, there is no guarantee when the data will come back, so your component needs to "understand" that is can exist in a "without data" state.
Simplest form of this is:
render() {
if( ! this.props.reportData) return null;
// normal render code, at this point we have data
return <div>{this.props.reportData.map(foo, ...)}</div>
}
A better form, would be something like:
render() {
if( ! this.props.reportData) {
return <div><img src="loading.gif" /></div>;
}
// normal render code, at this point we have data
return <div>{this.props.reportData.map(foo, ...)}</div>
}
^1 Note: You could technically use async functions, but I feel that would complicate the problem, especially without a fundamental understanding of what is already going on.
In your main file where you create the store you can dispatch action and set initial value just like
import configureStore from './store/configureStore;
import {LoadReportData} from './actions/LoadReportData';
const store = configureStore();
store.dispatch(LoadReportData());

react dynamically override child render

I have the following reactJS component structure
<Parent>
<Child1/>
</Parent>
<Parent>
<Child2/>
</Parent>
the children have a function that performs different API calls.. Until thats finished, the child is not ready to be rendered. So is there a way for me to have the parent display
"waiting for data..."
and call the method in the child to do the API call
in the child i would like to have a simple render method which does not have to check if the get API call has completed or not
I have tried two approaches but both unsuccessful
Try call a method in the child with out rendering it.. React.Children.map(this.props.children, (child)=>child.doAPICall()) but this child does not seem to have its functions available
Override the render function dynamically so it renders nothing, then after the children have completed the API calls to swap the render mthod back
React.Children.map(this.props.children, (item, i) =>
(React.cloneElement(item, {
render: () => false
})))
this will allow me to override props but not the render method
Any advice would be greatly appriciated
You should do a conditional render. I would suggest in one of the following two ways:
Either in the parent component. Do the necessary API calls to seed the data, and then render the children when the data is ready.
Or in the children. Do the data calls (for example in componentDidMount), and then render the data when it's ready. Until then render something else, ie some text or an image that says 'Loading'.
Whether or not you decide to conditionally render the children from the parent, or if you simply decide to do a conditional render within the children's render themselves, it would look something like this:
render() {
return (
<div>
{this.state.data?
<div>{this.state.data.somedata}</div>
:
<div>Loading...</div>}
</div>
);
}
or even:
render() {
if (!this.state.data) {
return <div>Loading...</div>
}
return (
<div>
<div>{this.state.data.somedata}</div>
</div>
)
}
Finally, an even more concise way to conditionally render is with this syntax:
render() {
return(
<div>
{this.state.data && <div>{this.state.data.someField}</div>}
</div>
);
}
or for example
render() {
return this.state.data && <div>{this.state.data.someField}</div>;
}
.. hopefully you get the idea :)

Resources