Display a Spinner while loading Parent State Array - reactjs

I am using React with typescript for my application. What I have now is in my top level “app.tsx” a state called objects which is an array of objects. I have passed down through a few child components a function that is getObjects which is defined in the top app level. This function returns the state object[] if it is defined otherwise calls an asynchronous function to load in the objects.
What I would like to do is to display a loading spinner in my child component while this state object[] is still undefined in the app.
I know that if I were to load the object[] as a local state in my child component I could easily accomplish this. Is there any way that I can call a callback at the child level after the state in the app has finished loading? The only way that I see to do it is to pass the object[] itself to the child component, but my knowledge is still very limited. Thank you, I appreciate all help.
Edit:
I do need the getObjects function in my grandchild component. I've simplified the structure a bit. What really is the situation is in the app object is a Dictionary type that acts as a key-value pair with a number id corresponding to a specific array of objects. On app loading, one key-value pair is chosen and that is loaded asynchronously while the user can keep using the app. It loads in the backend for these initial child components so a spinner is not needed.
Later in the greatgrandchild component, there is a need to, if chosen by the user, load another id into the dictionary while using an API to populate the object[] portion of the dictionary. Once this portion of the dictionary is loaded, the getObjects call in the top app layer returns a converted array of objects in my greatgrandchild to be used for selection. What is happening now is that the selectbox is just being empty until the async function completes, then it populates as expected. What I would like to do is to display a spinner in the greatgrandchild in place of the selectbox while this operation completes.
Since the structure is a dictionary and technically two values for the dictionary could be trying to be loaded at a time, I'm not sure that I want to try to pass through and use a boolean for this loading.
I am reluctant to pass through the whole dictionary itself and wait for the particular dictionary key-value pair to be loaded since this seems like an anti-pattern. What is the best way to accomplish what I am trying to do?

In your child component conditionally render the spinner and the object contents.
{
yourObjectFromParent
? (<>Your object contents here</>)
: (<LoaderComponent/>)
}
OR
You can try to set a state called loading=true in the parent and set it to false when object is loaded. You should pass this loading state as props to the child components. If thats the case your code will be like this. This can also be implemented with a global state handler like redux.
{
props.loading
? (<>Your object contents here</>)
: (<LoaderComponent/>)
}

This would show a spinner until loading is completed.
class MyComponent extends Component {
state = {
isLoading: true,
data: null
}
componentDidMount() {
fetchSomeData(url)
.then(data => {
this.setState({isLoading: false, data: data});
})
}
render() {
const {isLoading, data} = this.state;
return isLoading ? <ChildComponent data={data} /> : <Spinner />
}
}

Related

React can not access variable from return

I am very new to React and I can not solve this issue for a very long time. The idea is the following, I get some user info from database and pass it into cookies. From react side I get this cookies as JSON object and I need to render menu for user depending on values in this object. Right now I pass this object that I get from cookies from parent Component and I try to access it from Settings component. The issue is that I can see access this object from test function, however when I try to access this data from return it gives me undefined error. The function that returns value from cookies is sync, I have no idea why that might happen, please help.....
Since this.state.shopSettings["new_orders"] is boolean, use ternary.
Don't copy props to the state inside constructor as the constructor is executed only once. So updates to the props won't be reflected in the component.
Like this
<button onClick={this.test}>
{props.shopSettings && (props.shopSettings["new_orders"] ? 'true button' : 'false button')}
</button>
It solves very easily with this code, now I can access any key from shopSettings inside return without any issues
class Index extends React.Component {
state = {
shopSettings: Cookies.getJSON('shopSettings')
}
render() {
const {shopSettings} = this.state
if (!shopSettings) {
return null
}
return (
<div>Hello</div>
)
}

How can I make sure a React parent component loads data before its child component mounts?

I'm writing a simple calendar application that uses a common layout to wrap different views of events (month view shows a larger calendar with all the days of the month and events for each day, week view just shows a vertical list of events for that week, etc.). The common layout includes a calendar picker control for selecting the date, and then a list of event categories that can be checked or unchecked to show events relating to sports, entertainment, etc.
When the layout mounts, I'm calling an async Redux action creator to get the list of event categories from the database. When those are retrieved, they're saved in a Redux store with a property of Selected set to true, since they're all selected at initial load.
async componentWillMount() {
await this.props.getEventTypes();
}
When the month view, which is a child of the layout view, mounts, it's grabbing all the events for the given month. Part of the selection process of getting those events is sending the list of selected event categories to the backend so it only gets events from the selected categories.
async componentWillMount() {
await this.props.getWeeks();
}
The problem is, the selected categories list is always empty when the month view goes to grab the events for the month. So it's not going to select anything since no categories are selected.
It seems the only way this can be happening is if the child component is mounting first, or if the parent component is taking so long to get the event categories that the getWeeks process finishes first (this is unlikely as the process to grab the weeks and days and their events is much more involved than just selecting the event category list).
So, how can I make sure the parent component grabs the event categories from the database and puts them in the Redux store before the child component selects its events?
I know one way, probably the best way, to do this would be to have the list of event categories render into the page on the server side, so it's just already present at initial load. I'll probably end up doing it that way, but I'd also like to know how to do it all through client-side actions, in case I need to do it that way for some reason in the future.
You can try like this
Set isDataLoaded when data is available.
Use ternary operator for conditional rendering.
In you render
return(
<>
....
{ isDataLoaded ? <ChildComponent /> : null }
....other sutff
</>
);
Use can also use the && operator
return(
<>
....
{ isDataLoaded && <ChildComponent /> }
....other sutff
</>
);
You can integrate componentDidUpdate() and use it to render your child-components in a somewhat synchronous flow.
Let's say the structure of your Parent Component should look something like the following:
Parent
class Parent extends React.Component{
state = {
renderChildren: false
}
async componentDidMount() {
await this.props.getEventTypes();
}
componentDidUpdate(prevProps){
if(this.props.yourUpdatedReducer !== prevProps.yourUpdatedReducer){
this.setState({
renderChildren: true
})
}
}
render(){
const { renderChildren } = this.state
return(
{ renderChildren ? <Child/> : "Loading" }
)
}
}
You want a key in your state that determines whether you should
render the Child component.
In componentDidMount(), you call the action-creator function, when
it completes, you get updated props.
With updated props, you trigger componentDidUpdate(), where you
check the values in your reducer. If the values are
different that means you got the updated data from your database, so
everything has loaded.
Great, so now you want to mount your Child component, so you
update-state, setting renderChildren to true, thus re-rendering the
Parent component. Now Child gets rendered and should behave as expected.

Preserve internal state on page refresh in React.js

It must be pretty regular issue.
I'm passing props down to the children and I'm using it there to request to the endpoint. More detailed: I'm clicking on the list item, I'm checking which item was clicked, I'm passing it to the child component and there basing on prop I passed I'd like to request certain data. All works fine and I'm getting what I need, but only for the first time, ie. when refreshing page incoming props are gone and I cannot construct proper URL where as a query I'd like to use the prop value. Is there a way to preserve the prop so when the page will be refresh it will preserve last prop.
Thank you!
(You might want to take a look at: https://github.com/rt2zz/redux-persist, it is one of my favorites)
Just like a normal web application if the user reloads the page you're going to have your code reloaded. The solution is you need to store the critical data somewhere other than the React state if you want it to survive.
Here's a "template" in pseudo code. I just used a "LocalStorage" class that doesn't exist. You could pick whatever method you wanted.
class Persist extends React.Component {
constuctor(props) {
this.state = {
criticalData = null
}
}
componentDidMount() {
//pseudo code
let criticalData = LocalStorage.get('criticalData')
this.setState({
criticalData: criticalData
})
}
_handleCriticalUpdate(update) {
const merge = {
...LocalStorage.get('criticalData')
...update
}
LocalStorage.put('criticalData', merge)
this.setState({
criticalData: merge
})
}
render() {
<div>
...
<button
onClick={e => {
let update = ...my business logic
this._handleCriticalUpdate(update) //instead of set state
}}
>
....
</div>
}
}
By offloading your critical data to a cookie or the local storage you are injecting persistence into the lifecycle of the component. This means when a user refreshes the page you keep your state.
I hope that helps!

Reactjs, is setState needed here, changing value of an object in state

My question is, is it ok to not use setState in this situation or if I should, how would I go about it?
I'm creating a sportsbetting app.
On the list of games, when a user clicks on Denver Broncos -3, a bet slip is created. This also calls
setState({gameSelected:[item]}) in the Parent Component.
It stores that object and adds to it using the spread operator when more bets are clicked.
I pass down that data to betSlip.js file.
side note
Some of the data in the gameSelected object has :
risk:'',
win:'',
On betSlip.js I am looping through that data and displaying it.
Relevant code inside the loop.
<input type="text" placeholder="Risk" onChange={(e)=>this.handleBet(e,item)}/>
<input type="text" placeholder="Win" value={item.win}/>
This function is inside betSlip.js
handleBet = (e,item) =>{
let bet=e.target.value
//function to calculate moneyline bet
let calcBet =(bet)=>{
let newAmount =(100/item.juice)*bet;
return newAmount
}
item.win = calcBet(bet)
}
Yes, you should be using setState here to update item.win. Otherwise, React is not aware of the mutation on item and does not rerender. Since item is a prop, you should treat it as immutable from within betSlip.js and update item.win through an event handler in the parent component where item is maintained. Your betSlip.js component could have an onWinUpdate handler that is called within handleBet as this.props.onWinUpdate(item, calcBet(bet)). Then, the parent component can have a function handleWinUpdate(item, bet) that updates the state. I would also keep things immutable in handleWinUpdate by doing something like this:
handleWinUpdate(item, bet) {
this.setState(state => ({
gameSelected: state.gameSelected.map(i => {
if (i === item) {
return { ...i, win: bet };
} else {
return i;
}
})
}));
}
Notice that we are setting gameSelected to a new array and replacing item in that array with a new copy of item, but with a new win value. No objects are ever mutated.
Even though you don't have to use immutability here, I find it easier to reason about and it opens the door for performance gains later using things like React.PureComponent that rely on immutability.

React conditional rendering not updating state in child component

I'm having a weird problem with conditional rendering in which state isn' going down into a child component. I have a viewer template, with a PDF viewer component and a Web viewer component (using an iframe). Depending on what comes back from the server as a media_type value, the appropriate component gets rendered. That's all working fine.
Externally, I have a sibling component responsible for searching inside the content, and in order to do so, it has to pass the search query up to the parent component, which then updates the parent state and then gets passed to the child as a prop. The reason for this is different content requires different search implementation, which is implemented inside the viewer component.
Apparently, my method of conditional rendering is breaking the search query prop update in the child component. None of the component update methods are being called when the prop changes, and therefore the search execution never gets called.
The sibling component calls this method in the common parent:
/**
* Search query execution handler. Passes the state as a prop to the catalog for search
* execution
* #param e Keyword or query string from SearchPanel
*/
searchQueryHandler(e) {
this.setState({
searchRequest: e
});
}
Parent component render method
render() {
let viewer = <div />;
if (this.state.link.media_type === 1)
viewer = <PDF file={this.state.link.id}
setOverlayVisibility={this.props.setOverlayVisibility}
searchQuery = {this.state.searchRequest}
searchMatchHandler={this.searchMatchHandler}
searchResultSelection={this.state.searchResultSelection}
/>;
else if (this.state.link.media_type !== '')
viewer = <WebViewer link={this.state.link}
setOverlayVisibility={this.props.setOverlayVisibility}
searchQuery={this.state.searchRequest}
/>;
return (
<Content>
<ContentLeft>
{viewer}
</ContentLeft>
<ContentRight>
<SidePanel institution={this.state.institution}
link={this.state.link}
searchQueryHandler={this.searchQueryHandler}
searchResults={this.state.searchResults}
searchResultClickHandler={this.searchResultClickHandler}
/>
</ContentRight>
</Content>
)
}
Now, the searchQueryHandler method is being hit by the event fired off in SidePanel, but none of componentWillReceiveProps, shouldComponentUpdate, willComponentUpdate are called inside PDF or WebViewer. I suspect this has to do with the if/else block inside render, but not sure how else to implement this.
The answer to this was the parent component was blocked from updating by a shouldComponentUpdate implementation that did not take into account the new state of the search query. As such, it was returning false all the time, and thus blocking propagation of state update to the appropriate child component.
shouldComponentUpdate(nextProps, nextState) {
return this.state.link !== nextProps.link || this.state.searchRequest !== nextState.searchRequest;
}
was the fix.
So simple, and yet so frustrating.

Resources