Is there a cleaner way to stop React rendering on initial load? - javascript

So my react class calls to a json response and then renders.
But as it stands, the class will render on initial load then render again when response has come back.
To get around this I have do this -
componentDidMount: function() {
axios.get.......
.then(res => {
const jobs = res.data;
this.setState({ jobs });
});
},
render: function () {
if (Object.keys(this.state.jobs).length == 0)
{
}
else {
return (
<div>
{this.state.jobs.data.map(function (ob) {
return <li key={ob.id}>{ob.name}</li>
})}
</div>
)
}
return null;
}
});
Is there a nicer way to do this? without using an if statement?

React always has to render in the initial load. So you just need to render null like you already did. Code wise, maybe you can write
return this.state.jobs.data ? <YourTemplate /> : null;
which is cleaner.

Related

React: Abstraction for components. Composition vs Render props

Long question please be ready
I've been working with react-query recently and discovered that a lot of the code has to be duplicated for every component that is using useQuery. For example:
if(query.isLoading) {
return 'Loading..'
}
if(query.isError) {
return 'Error'
}
if(query.isSuccess) {
return 'YOUR ACTUAL COMPONENT'
}
I tried creating a wrapper component to which you pass in the query information and it will handle all the states for you.
A basic implementation goes as follows:
const Wrapper = ({ query, LoadingComponent, ErrorComponent, children }) => {
if (query.isLoading) {
const toRender = LoadingComponent || <DefaultLoader />;
return <div className="grid place-items-center">{toRender}</div>;
}
if (query.isError) {
const toRender = ErrorComponent ? (
<ErrorComponent />
) : (
<div className="dark:text-white">Failed to Load</div>
);
return <div className="grid place-items-center">{toRender}</div>;
}
if (query.isSuccess) {
return React.Children.only(children);
}
return null;
};
And, using it like:
const Main = () => {
const query = useQuery(...);
return (
<Wrapper query={query}>
<div>{query.message}</div>
</Wrapper>
)
}
The issue with this is that query.message can be undefined and therefore throws an error.
Can't read property message of undefined
But, it should be fixable by optional chaining query?.message.
This is where my confusion arises. The UI elements inside wrapper should have been rendered but they donot. And, if they don't then why does it throw an error.
This means that, the chilren of wrapper are executed on each render. But not visible. WHY??
Render Props to rescue
Wrapper
const WrapperWithRP = ({ query, children }) => {
const { isLoading, data, isError, error } = query;
if (isLoading) return 'Loading...'
if (isError) return 'Error: ' + error
return children(data)
}
And using it like,
const Main = () => {
const query = useQuery(...);
return (
<Wrapper query={query}>
{state => {
return (
<div>{state.message}</div>
)
}
}
</Wrapper>
)
}
This works as expected. And the children are only rendered when the state is either not loading or error.
I am still confused as to why the first approach doesn't show the elements on the UI event when they are clearly executed?
Codesandbox link replicating the behaviour: https://codesandbox.io/s/react-composition-and-render-prop-tyyzh?file=/src/App.js
Okay, so i figured it out and it is simpler than it sounds.
In approach one: the JSX will still be executed because it's a function call React.createElement.
But, Wrapper is only returning the children when the query succeeds. Therefore, the children won't be visible on the UI.
Both approaches work, but with approach 1, we might have to deal with undefined data.
I ended up using render-prop as my solution as it seemed cleaner.

Call another code while array.map() is running

I have the following react code:
{myArray.map(arr => {
return ( <MyComponent title={arr.ttile} /> )
})}
I would like to call a Loading component while the map() is not completely finished. Is it possible to do that? If yes, how would I do that?
If you are getting your data from an API, you might want to render the data as usual, but you can get the data in the componentDidMount hook instead, and e.g. keep an additional piece of state isLoading which you can use in the render method to decide if you should show a loading component.
Example
function getBooks() {
return new Promise(resolve => {
setTimeout(() => resolve([{ title: "foo" }, { title: "bar" }]), 1000);
});
}
function MyComponent(props) {
return <div> {props.title} </div>;
}
class App extends React.Component {
state = { books: [], isLoading: true };
componentDidMount() {
getBooks().then(books => {
this.setState({ books, isLoading: false });
});
}
render() {
const { isLoading, books } = this.state;
if (isLoading) {
return <div> Loading... </div>;
}
return (
<div>
{this.state.books.map(book => <MyComponent title={book.title} />)}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
If you want to actually be able to see the components being loaded behind/under the loading indicator, then it would be more challenging and would probably need more work than this proposed solution. But if you just want a loading indicator to show while the .map() prototype function is working, I believe this would do the trick:
constructor(props) {
super(props);
this.state = { loadingIndicator : null };
}
getArrayOfMyComponents() {
return myArray.map((arr, index) => {
if (index === 0) {
const loadingIndicator = <Loading/>;
this.setState({ loadingIndicator : loadingIndicator });
} else if (index === myArray.length - 1) {
this.setState({ loadingIndicator : null });
}
return ( <MyComponent title={arr.title} /> );
});
}
render() {
const arrayOfMyComponents = this.getArrayOfMyComponents();
return (
<div>
{this.state.loadingIndicator}
{arrayOfMyComponents}
</div>
);
}
Array.prototype.map() is really just a fancier version of Array.prototype.forEach(). So we can leverage that fact to launch the display of the loading indicator on the first iteration and remove it on the last.
you can have a boolean in a state, and just before you start array map put boolean true and run another code o component render, and then when array maps end you put that state to false, for redux im using state fetch start, fetching, fetched, and then you can take the control of situation

Returning options from mapping over values in react

Hey everyone so I am making a get request to a google API and pulling in some data. Initially, my state value is just an empty object and from the ajax request I am expecting the state values to be filled with setState.
Then, in another method I am taking that state data and mapping over the items and returning an option for each element within that array. The weird thing is that right before I start returning the values that I am mapping over I am console.loging the values and they are exactly the values that I want. However, when I return an option with that value inside there is still nothing inside my select.
Can anyone please elaborate what I could be possibly doing incorrectly?
constructor() {
super();
this.state = {
};
componentDidMount() {
let URL = is the url with my api key (it works)
axios.get(URL)
.then((data) => {
console.log("data" + data);
this.setState({
googleFonts: data
})
})
.catch((err) => {
console.log(err);
this.setState({
errors: err
})
})
}
renderFonts() {
let data = this.state.googleFonts.data.items;
return data.map((font, index) => {
console.log(font.family);
return (
<ul>
<li>{font.family}</li>
</ul>
)
})
}
<FormControl
style={inputFieldStyle}
componentClass="select"
placeholder="select" >
{setTimeout(this.renderFonts, 100)}
</FormControl>
Could you try something like this:
//after `render()`
const data = this.state.googleFonts.data.items
return (
<FormControl
style={inputFieldStyle}
componentClass="select"
placeholder="select"
>
{data && data.map((font, index) =>
<option>{font.family}</option>
)}
</FormControl>
I believe that's how React-Bootstrap is expecting it to be written, and it's a little less code. Using the logical && operator allows it to populate the dropdown after the state loads.
There are a few things wrong here.
First: {setTimeout(this.renderFonts, 100)} returns timeoutID is a positive integer value which identifies the timer created by the call to setTimeout() and hence although renderFonts is executed nothing is returned from it to be rendered
Second: componentDidMount is called after render and you have your API request in componentDidMount, so delaying the value to be rendered by using a setTimeout is a horrible idea, since you are never sure as to when you would get a response from the API, for a slow network it could take a really long time and hence even after a timeout of 100ms, you may still not have the data and so this.state.googleFonts.data.items might still be undefined or the response may come within 10ms in which case you are unnecessarily delaying the render.
A better way to solve this is to have provide a check for the presence of data.
You could do it like
constructor() {
super();
this.state = {
};
componentDidMount() {
let URL = is the url with my api key (it works)
axios.get(URL)
.then((data) => {
console.log("data" + data);
this.setState({
googleFonts: data
})
})
.catch((err) => {
console.log(err);
this.setState({
errors: err
})
})
}
renderFonts() {
let data = this.state.googleFonts? this.state.googleFonts.data.items: [];
return data.map((font, index) => {
console.log(font.family);
return (
<ul>
<li>{font.family}</li>
</ul>
)
})
}
<FormControl
style={inputFieldStyle}
componentClass="select"
placeholder="select" >
{this.renderFonts()}
</FormControl>
This should help you. It's more to do with how you have structured your code and callback. Take a look at this sample -
import React from 'react';
import axios from 'axios'
class Hello extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'there',
title: null
};
}
componentWillMount() {
let URL = 'https://fakerestapi.azurewebsites.net/api/Books'
axios({
method: 'get',
url: URL,
responseType: 'json'
})
.then((data) => {
console.log(data.data);
this.setState({
title: data.data[0].Title
})
})
.catch((err) => {
console.log(err);
this.setState({
errors: err
})
})
}
render() {
return (
<div className="component-app">
<h1>Hello {this.state.name}! {'\u2728'}</h1>
<h2>{this.state.title}</h2>
</div>
);
}
}
export default Hello;
I have coded it here. Have a look
Instead of componentDidMount you can use componentWillMount. Also you would be seeing errors in console with your code somthing like React - setState() on unmounted component. If you fix that you should be able to get your code working.

Trouble Getting Component to Render in setTimeout

Not sure what I'm doing wrong but my component wrapped in setTimeout is not being rendered to the DOM:
const ContentMain = Component({
getInitialState() {
return {rendered: false};
},
componentDidMount() {
this.setState({rendered: true});
},
render(){
var company = this.props.company;
return (
<div id="ft-content">
{this.state.rendered && setTimeout(() => <Content company={company}/>,3000)}
</div>
)
}
})
I'd bet this isn't working because the render method needs all of its input to be consumed at the same time and it can't render other components in retrospect, there's a certain flow to React. I'd suggest to separate the timeout from render method anyway for logic's sake, and do it in componentDidMount like this:
const ContentMain = Component({
getInitialState() {
return {rendered: false};
},
componentDidMount() {
setTimeout(() => {
this.setState({rendered: true});
}, 3000);
},
render(){
if (!this.state.rendered) {
return null;
}
var company = this.props.company;
return (
<div id="ft-content">
<Content company={company}/>
</div>
)
}
})
Changing the state triggers the render method.
On a side note - even if your original approach worked, you'd see the component flicker for 3 seconds every time it got rendered after the initial load. Guessing you wouldn't want that :)

ReactJS. Quite slow when rendering and updating a simple list of 1500 <li> elements. I thought VirtualDOM was fast

I was really disappointed by the performance I got on the following simple ReactJS example. When clicking on an item, the label (count) gets updated accordingly. Unfortunately, this takes roughly ~0.5-1 second to get updated. That's mainly due to "re-rendering" the entire todo list.
My understanding is that React's key design decision is to make the API seem like it re-renders the whole app on every update. It is supposed take the current state of the DOM and compare it with the target DOM representation, do a diff and update only the things that need to get updated.
Am I doing something which is not optimal? I could always update the count label manually (and the state silently) and that will be an almost instant operation but that takes away the point of using ReactJS.
/** #jsx React.DOM */
TodoItem = React.createClass({
getDefaultProps: function () {
return {
completedCallback: function () {
console.log('not callback provided');
}
};
},
getInitialState: function () {
return this.props;
},
updateCompletedState: function () {
var isCompleted = !this.state.data.completed;
this.setState(_.extend(this.state.data, {
completed: isCompleted
}));
this.props.completedCallback(isCompleted);
},
render: function () {
var renderContext = this.state.data ?
(<li className={'todo-item' + (this.state.data.completed ? ' ' + 'strike-through' : '')}>
<input onClick={this.updateCompletedState} type="checkbox" checked={this.state.data.completed ? 'checked' : ''} />
<span onClick={this.updateCompletedState} className="description">{this.state.data.description}</span>
</li>) : null;
return renderContext;
}
});
var TodoList = React.createClass({
getInitialState: function () {
return {
todoItems: this.props.data.todoItems,
completedTodoItemsCount: 0
};
},
updateCount: function (isCompleted) {
this.setState(_.extend(this.state, {
completedTodoItemsCount: isCompleted ? this.state.completedTodoItemsCount + 1 : this.state.completedTodoItemsCount - 1
}));
},
render: function () {
var updateCount = this.updateCount;
return (
<div>
<div>count: {this.state.completedTodoItemsCount}</div>
<ul className="todo-list">
{ this.state.todoItems.map(function (todoItem) {
return <TodoItem data={ todoItem } completedCallback={ updateCount } />
}) }
</ul>
</div>
);
}
});
var data = {todoItems: []}, i = 0;
while(i++ < 1000) {
data.todoItems.push({description: 'Comment ' + i, completed: false});
}
React.renderComponent(<TodoList data={ data } />, document.body);
<script src="http://fb.me/react-js-fiddle-integration.js"></script>
jsFiddle link, just in case: http://jsfiddle.net/9nrnz1qm/3/
If you do the following, you can cut the time down by a lot. It spends 25ms to 45ms to update for me.
use the production build
implement shouldComponentUpdate
update the state immutably
updateCompletedState: function (event) {
var isCompleted = event.target.checked;
this.setState({data:
_.extend({}, this.state.data, {
completed: isCompleted
})
});
this.props.completedCallback(isCompleted);
},
shouldComponentUpdate: function(nextProps, nextState){
return nextState.data.completed !== this.state.data.completed;
},
Updated fiddle
(there are a lot of questionable things about this code, daniula points out some of them)
When you are generating list of elements you should provide unique key prop for everyone. In your case:
<ul className="todo-list">
{ this.state.todoItems.map(function (todoItem, i) {
return <TodoItem key={i} data={ todoItem } completedCallback={ updateCount } />
}) }
</ul>
You can find out about this mistake by warning message in browser console:
Each child in an array should have a unique "key" prop. Check the render method of TodoList. See fb.me/react-warning-keys for more information.
There is another warning which you can easily fix by changing event handler on <input type="checkbox" /> inside <TodoItem /> from onClick to onChange:
<input onClick={this.updateCompletedState} type="checkbox" checked={this.state.data.completed ? 'checked' : ''} />
You are doing some string concatenation to set proper className. For more readable code try using nice and simple React.addons.classSet:
render: function () {
var renderContext = this.state.data ?
var cx = React.addons.classSet({
'todo-item': true,
'strike-through': this.state.data.completed
});
(<li className={ cx }>
<input onChange={this.updateCompletedState} type="checkbox" checked={this.state.data.completed ? 'checked' : ''} />
<span onClick={this.updateCompletedState} className="description">{this.state.data.description}</span>
</li>) : null;
return renderContext;
}
I'm looking at where you render() the list...
<div>
<div>count: {this.state.completedTodoItemsCount}</div>
<ul className="todo-list">
{ this.state.todoItems.map(function (todoItem) {
return <TodoItem data={ todoItem } completedCallback={ updateCount } />
}) }
</ul>
</div>
This should not be called every time a TodoItem is updated. Give the above element a surrounding div and an id like this:
return <div id={someindex++}><TodoItem
data={ todoItem }
completedCallback={ updateCount }
/></div>
Then simply rerender a single TodoItem as it is changed, like so:
ReactDOM.render(<TodoItem ...>, document.getElementById('someindex'));
ReactJS is supposed to be fast, yes, but you still need to stick to general programming paradigms, i.e., asking the machine to do as little as possible, thereby producing the result as fast as possible. Rerendering stuff that doesn't need to get re-rendered gets in the way of that, whether or not it's "best practice".

Categories