In the ReactJS, I am changing the route to some new one with an "id", then based on this "id", I must call an API and fetch data.
I have used the API call in componentDidMount and componentWillMount and tried setState() to have my data in the state. but they didn't work in my case.
The problem is in the render() part when I want to use my data (from state), the data is not there because the API call takes some time to update the state.
Here is my code:
componentDidMount() {
api.get(id).then((response) => {
this.setState({response,});
});
With this approach, I don't have the data when I want it (render method), it will eventually be in the state but too late!
How can I change my approach to fix the loading problem?
The way you are doing it, you could validate the response state object in your render method, and if it is null/empty, then return <div>Loading...</div>.
When your state is then updated based on the response, the component will automagically re-render. This time, the object will be populated and you can return your component HTML.
e.g.
render(){
if (!this.state.response){
return <div>Loading...</div>
}
return (
<div>Proper Stuff</div>
);
}
Edit On a site note, if you set the initial state in the constructor, you can make sure the object isn't empty, but just has null/empty values. This could also help depending what your render method is doing, e.g.
constructor(props){
super(props);
this.state = {
response: {...}
};
}
Your render code should be
render(){
return({this.state.response?
<div> Render your component here</div>:
<div> Loading ... </div>});
}
This way your component would not render till it has the data and show Loading ... text instead.
Related
Overview
We have a page with a header (Blue color) and content section (Green color) that can be seen in image below. The requirement is when a user selects a year in header, then the content page will show data as per the selected year.
What is happening right now Technically
When user selects a year in header, we dispatch the selected value and the active container's mapStateToProps function is triggered and the selected year is passed to the component.
class Page1Content extends Component {
}
function mapStateToProps(state) {
return { selectedYear : state.userSelectedValue };
}
export default connect(mapStateToProps, null)(Page1Content);
Question 1
How will data on Page1Content will refresh? Few approaches:
In ComponentDidUpdate react life cycle method of the Page1Content API to fetch data can be called. However have seen opinions where we should avoid React hooks and life cycle methods with Redux.
In mapStateToProps function API can be called.
Can any one suggest what is the better place to call the API?
Question 2
Data on Page1Content will be used only by this page. This data will not be used by any other component and hence need not be shared by any other Component. Now question 2 is
In case we decide to use ComponentDidUpdate should we again dispatch the API call using Thunk or any other library and then catch the response in mapStatesToProps again?
Or we should make the API call and resolve it in the component itself as a promise. Then the response will be set in State and respective Template will be refreshed.
ComponentDidUpdate is a lifecycle method not a hook. Hooks is functionality that allows functional components to have class based functionality such as state.
You are using a class based component in your example so you are not using hooks.
https://reactjs.org/docs/hooks-intro.html
Yes Redux shouldnt be used with hooks since context is a better option.
You can lift state up so to speak and update the local state in the parent component getting rid of redux completely.
Just pass down the setState function and the state itself to the appropriate children.
class App extends Component {
constructor(props) {
super(props);
this.state = {
some_prop: false
}
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({some_prop: true})
console.log('Click happened');
}
render() {
return (
<Header onClick={this.handleClick } />
<Page1Component props={this.state.some_prop} />
}
}
Edit:
Question 1
How will data on Page1Content will refresh?
best option is with a ternary expression in your render method, there is no need to check if the state updated. In react if the state is changed your component will automatically re render.
render() {
return (
<div>
{this.props.selectedYear
? <p> {this.props.selectedYear}</p>
: null
}
</div>
}
}
Question 2
Data on Page1Content will be used only by this page. This data will not be used by any other component and hence need not be shared by any other Component. Now question 2 is
If I understand this correctly you will need to use an action creators, redux thunk is overkill here.
class Header extends Component {
constructor(props) {
super(props);
}
handleClick() {
this.props.dispatchActionCreator({some_value})
console.log('Click happened');
}
render() {
return (
<button onClick={(some_value) => this.handleClick(some_value)}>Click </button>
}
}
function mapDispatchToProps(state) {
return {
dispatchActioNCreator: (some_value) => dispatch(ACTIONS.action_creator(some_value) };
}
This will save your value from your header to the global redux state and then you can just access with mapStateToProps in your Page1Component.
I made some app to get asynchronously data from remote server.
The app just parent component - to get data, and two child components.
One child component for display asynchronous data.
Other child for filter functionality. Just input string where user typing and data in first component display appropriate items.
There are a lot code with console.log everywhere, but in simple scheme it:
class App extends Component {
state = {isLoading:true, query:''}
getData = (location) => {
axios.get(endPoint).then(response=>{ response.map((item) => { places.push(item)})
// ***** first setState
this.setState({isLoading:false})
})
}
updateQuery = (e) => {
// ***** second setState
this.setState({query:e.target.value.trim()})
}
componentDidMount(){
this.getData(location)
}
render() {
if (!this.state.isLoading){
if (this.state.query){
const match = new RegExp(escapeRegExp(this.state.query),'i')
searchTitles = places.filter(function(item){return match.test(item.name)})
}else{
searchTitles = places.slice();
}
}
return (
<div className="App">
<input type='text' onChange={this.updateQuery} value={this.state.query}/>
<List places = {searchTitles}/>
</div>
);
}
}
export default App;
When state change in case of using everything is OK - content refreshed in next child component.
But child component that display data - some items not full of content... no photos and some text information. So probably its rendered before getting remote data.
But why its not re-render it after state.isLoad toggled to 'false' (in code - after got response) ?
I put among code console.log to track processes ... and weird things: state.isLoad switched to false before some part of data came from server. (((
I dont use ShouldComponentUpdate() inside child component.
Per React's documentation for setState
setState() will always lead to a re-render unless
shouldComponentUpdate() returns false.
As mentioned, one way to avoid a re-render is shouldComponentUpdate returning false (shouldComponentUpdate takes in nextProps and nextState) but it's not clear why someone would trigger a state change with setState and then nullify that state change with shouldComponentUpdate.
I am creating a component that fetches some apis to populate result.There are some transformations needed on those api results before rendering. I've used componentWillMount method to set redux state. I need to handle render method until redux those results to props. This takes some time. Since data is not available, render method will fail at that moment. I've tried setting default state but that doesn't seem to work since redux state will be mapped to props with mapStateToProps method. What is the correct and efficient approach to handle such case??
Thanks in advance
Normally you will render a different thing (or you won't render at all) when your data is not present.
{this.props.unreadMessages.length > 0 &&
<h2>
You have {this.props.unreadMessages.length} unread messages.
</h2>
}
or:
if (this.props.unreadMessages.length > 0) {
return <Main unreadMessages={this.props.unreadMessages} />;
} else {
return <Loading />
}
You can also have your default initial state and render based on that when props does not have data. maybe with something like:
<h2>
Hello, {this.props.username || this.state.username}.
</h2>
Also, the recommended lifecycle hook for Ajax call is componentDidMount.
If your data is not ready, you can use conditional rendering. Add additional indicator about your data, Like this
render() {
if (!this.props.data) {
return null;
}
return <YourComponent />;
}
And you'd better to fetch data in componentDidMount lifecycle.
I would check for the props in the render method with a ternary expression as a sort of guard clause. Something like this, for example:
render(){
return (<React.Fragment>
{ this.props.user ? <IntendedComponent ...this.props.user /> : <Loading user={defaultUser}/> }
<React.Fragment/>);
}
If the user prop has not been populated yet, it should be empty and display an alternate component (like the loading component in the example) or the intended component with default, hardcoded values. Once the data is in place, you can pass the data to a component as you see fit.
First of all componentWillMount is not a good method to call apis,
as per react documentation:
componentDidMount() is invoked immediately after a component is
mounted (inserted into the tree). Initialization that requires DOM
nodes should go here. If you need to load data from a remote endpoint,
this is a good place to instantiate the network request.
This method is a good place to set up any subscriptions. If you do
that, don’t forget to unsubscribe in componentWillUnmount().
Here is the link: https://reactjs.org/docs/react-component.html#componentdidmount
Here is what you can do till data is loaded:
get one flag into redux state called dataLoaded:false
Once the data is loaded you performed an operations you can easily change it.
And in render()
if(this.props.dataLoaded){
<loadingComponent></loadingComponent>
} else {
//operation is performed
}
Since you're using Redux, you can set the default Redux state in Reducers using ES6 default parameters. Something like this:
const apiStatus = (state='fetching', action) => {
// handle dispatch action
}
And in React, you can use Conditional Rendering to render a loading icon, for instance, while state is fetching. Then when state is finished, take the result and render the real elements.
To work with Async in Redux, see this link:
Async Action
Is it adviceable to change the props to load my page with fresh data.
I have a requirement where I need to load the HTML page from JSON, example given below
formSchema:{
"label": "Step 1",
"action": "Next",
"Fields":[
{
"type":"text",
"label":"First Name",
"name":"fname"
"value":"Abraham"
}
]
}
I'm able to load the HTML page from the above JSON schema, but when user clicks the submit button there will a new schema loaded through ajax call. Now if I pass the new schema to my reducers can I directly replace the props with the newSchema, as I heard mutating the props is not adviceable. In such case how do I update my props only with new data.
If I do it as follows in my reducer
return Object.assign(...formSchema, action.formSchema);
I see in console the next state is shown as object instead of an array even though I load the same set of data.
You can use componentWillReceiveProps() method in React to update your view with new props.
componentWillReceiveProps() is invoked before a mounted component receives new
props. If you need to update the state in response to prop changes (for example, to reset it), you may compare this.props and nextProps and perform state transitions using this.setState() in this method.
Note that React may call this method even if the props have not changed, so make sure to compare the current and next values if you only want to handle changes. This may occur when the parent component causes your component to re-render.
React doesn't call componentWillReceiveProps with initial props during mounting. It only calls this method if some of component's props may update. Calling this.setState generally doesn't trigger componentWillReceiveProps.
It looks like this:
class YourComponent extends Component{
constructor(props) {
this.state = {
message: props.message // initial prop
}
}
componentWillReceiveProps(nextProps){
this.setState({message: nextProps.message} // updated prop
}
render() {
return (
<div><h1>{this.state.message}</h1></div>
)
}
}
Note: In this code you're not updating/mutating props, instead you're using new props (nextProps) and updating the state of your view.
I'm attempting to make a nice ApiWrapper component to populate data in various child components. From everything I've read, this should work: https://jsfiddle.net/vinniejames/m1mesp6z/1/
class ApiWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
response: {
"title": 'nothing fetched yet'
}
};
}
componentDidMount() {
this._makeApiCall(this.props.endpoint);
}
_makeApiCall(endpoint) {
fetch(endpoint).then(function(response) {
this.setState({
response: response
});
}.bind(this))
}
render() {
return <Child data = {
this.state.response
}
/>;
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
data: props.data
};
}
render() {
console.log(this.state.data, 'new data');
return ( < span > {
this.state.data.title
} < /span>);
};
}
var element = < ApiWrapper endpoint = "https://jsonplaceholder.typicode.com/posts/1" / > ;
ReactDOM.render(
element,
document.getElementById('container')
);
But for some reason, it seems the child component is not updating when the parent state changes.
Am I missing something here?
There are two issues with your code.
Your child component's initial state is set from props.
this.state = {
data: props.data
};
Quoting from this SO Answer:
Passing the intial state to a component as a prop is an anti-pattern
because the getInitialState (in our case the constuctor) method is only called the first time the
component renders. Never more. Meaning that, if you re-render that
component passing a different value as a prop, the component
will not react accordingly, because the component will keep the state
from the first time it was rendered. It's very error prone.
So if you can't avoid such a situation the ideal solution is to use the method componentWillReceiveProps to listen for new props.
Adding the below code to your child component will solve your problem with Child component re-rendering.
componentWillReceiveProps(nextProps) {
this.setState({ data: nextProps.data });
}
The second issue is with the fetch.
_makeApiCall(endpoint) {
fetch(endpoint)
.then((response) => response.json()) // ----> you missed this part
.then((response) => this.setState({ response }));
}
And here is a working fiddle: https://jsfiddle.net/o8b04mLy/
If the above solution has still not solved your problem I'll suggest you see once how you're changing the state, if you're not returning a new object then sometimes react sees no difference in the new previous and the changed state, it's a good practice to always pass a new object when changing the state, seeing the new object react will definitely re-render all the components needing that have access to that changed state.
For example: -
Here I'll change one property of an array of objects in my state, look at how I spread all the data in a new object. Also, the code below might look a bit alien to you, it's a redux reducer function BUT don't worry it's just a method to change the state.
export const addItemToCart = (cartItems,cartItemToBeAdded) => {
return cartItems.map(item => {
if(item.id===existingItem.id){
++item.quantity;
}
// I can simply return item but instead I spread the item and return a new object
return {...item}
})
}
Just make sure you're changing the state with a new object, even if you make a minor change in the state just spread it in a new object and then return, this will trigger rendering in all the appropriate places.
Hope this helped. Let me know if I'm wrong somewhere :)
There are some things you need to change.
When fetch get the response, it is not a json.
I was looking for how can I get this json and I discovered this link.
By the other side, you need to think that constructor function is called only once.
So, you need to change the way that you retrieve the data in <Child> component.
Here, I left an example code: https://jsfiddle.net/emq1ztqj/
I hope that helps.
Accepted answer and componentWillReceiveProps
The componentWillReceiveProps call in accepted answer is deprecated and will be removed from React with version 17 React Docs: UNSAFE_componentWillReceiveProps()
Using derived state logic in React
As the React docs is pointing, using derived state (meaning: a component reflecting a change that is happened in its props) can make your components harder to think, and could be an anti-pattern. React Docs: You Probably Don't Need Derived State
Current solution: getDerivedStateFromProps
If you choose to use derived state, current solution is using getDerivedStateFromProps call as #DiogoSanto said.
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing. React Docs: static getDerivedStateFromProps()
How to use componentWillReceiveProps
This method can not access instance properties. All it does describing React how to compute new state from a given props. Whenever props are changed, React will call this method and will use the object returned by this method as the new state.
class Child extends React.Component {
constructor() {
super(props);
// nothing changed, assign the state for the
// first time to teach its initial shape.
// (it will work without this, but will throw
// a warning)
this.state = {
data: props.data
};
}
componentWillReceiveProps(props) {
// return the new state as object, do not call .setState()
return {
data: props.data
};
}
render() {
// nothing changed, will be called after
// componentWillReceiveProps returned the new state,
// each time props are updated.
return (
<span>{this.state.data.title}</span>
);
}
}
Caution
Re-rendering a component according to a change happened in parent component can be annoying for user because of losing the user input on that component.
Derived state logic can make components harder to understand, think on. Use wisely.