Consider this HOC I use for fetching data
function withData(Component, endpoint) {
return class extends React.Component {
state = {
result: null,
loading: false,
error: { state: false, msg: '' }
};
fetchData = async () => {
try {
this.setState({ loading: true });
const response = await axios.get(`${endpoint}/${this.props.params || ''}`);
this.setState(state => ({ result: response.data, loading: !state.loading }));
} catch (err) {
console.log('Error caugh in withData HOC', err);
this.setState({ error: true });
}
};
componentDidMount() {
this.fetchData();
}
componentDidUpdate(prevProps) {
if (this.props.params !== prevProps.params) {
this.fetchData();
}
}
render() {
const { result, loading } = this.state;
if (loading) return <p>Loading...</p>;
if (!result) return null;
return <Component result={result} {...this.props} />;
}
};
}
You will notice I am saying if !result do not render the component. The problem is when this.props.params to this component changes, this.state.result is preserving the value of the older state. I want to reset result to null after each render, so it behaves the exact same as the initial render.
How can I achieve this?
To make it more clear, it would be great if I could do this in componentWillUnmount so that it's ready for the next Component lifecycle. However the component never unmounts.
Please note, it must be done in the HOC and not in the Component it returns.
In this case you'll want your component (rendered by the HOC) to accept a key option, which will be the params props in my case.
Usually you use those for lists, but it can be used here as well. When a key is used, if it changes instead of updating, it will make a new instance of the component. That would mean you would not need the componentDidUpdate anymore.
You can read more about the bahavior here https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key
Related
I'm new to the react.
I have this state :
state = {
isLoading: true,
};
And I have this lifecycle function
componentDidMount() {
const { setPageCount, receiveApiData } = this.props.actions;
const { isLoading } = this.state;
const getData = () => {
this.setState({ isLoading: !isLoading });
receiveApiData();
setPageCount();
};
setInterval(() => {
getData();
}, 30000);
}
Here is what I'm trying to return in render():
return isLoading ? (
<Loading></Loading>
) : ( `Some Code here`)
The problem is state is Always true and my lifecycle method is not changing it to the false so my app can not render the false condition.
I don't know what to do,any suggestions please?
Everything else in getData() is working correctly
The issue is here:
this.setState({ isLoading: !isLoading });
because isLoading what you are destructuring taking previous value i.e it is not taking latest state value so its not updating your isLoading . What you need to do is this:
this.setState({ isLoading: !this.state.isLoading });
Here is the demo: https://codesandbox.io/s/interesting-chandrasekhar-pluv7?file=/src/App.js:312-332
Since your new state depends on the value of your old state, you should use the functional form of setState
this.setState(prevState => ({
isLoading: !prevState.isLoading
}));
Official documentation: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
I have a container component in which I get the ID and drop this ID into the function and the request goes, in principle, the props should come right away, but they are undefined. But when you re-enter the same component, the necessary props are shown.
Explain how to make props appear on the first render?
class View extends React.Component {
componentDidMount() {
let id = this.props.match.params.id;
this.props.GetProjData(id);
}
render() {
return <ProjView {...this.props}></ProjView>;
}
}
let mapStateToProps = state => {
return {
initialValues: {
NameProj: state.project.OneProject.NameProj,
Text: state.project.OneProject.Text,
target: state.project.OneProject.target,
startdate: state.project.OneProject.startdate,
enddate: state.project.OneProject.enddate
},
error: state.settings.error,
loading: state.settings.loading
};
};
My request
export const GetProjData = data => async (
dispatch,
getState,
{ getFirestore }
) => {
const firestore=getFirestore()
try {
await firestore
.collection("Projects")
.where("idProject", "==", data)
.get().then(snap => {
snap.forEach(doc => {
let project=doc.data()
console.log(doc.data());
dispatch({type:getOne,project})
});
})
} catch (err) {}
};
If I'm understanding the flow of your app correctly, you need to account for the renders between when you request your project data and when you receive the project data.
class View extends React.Component {
// constructor fires first so we might as well move it here
constructor(props) {
const id = props.match.params.id;
props.GetProjData(id);
}
render() {
// Your component will rerender before receiving the new data.
// We block the component from mounting so that initialValues
// gets set only when we have the data needed
if (this.props.initialValues && this.props.initialValues.NameProj) {
// A better way to do this would be to listen to a loading variable
// that gets updated when your request finishes
return <ProjView {...this.props} />;
}
return null; // or loading graphic
}
}
I'd like to change what a component shows depending on the URL parameter but at the same time to use the same component. When I execute my code componentDidMount is evoked for the first time, but after the second time, it doesn't work. As a result, I can't change what the component shows.
I'm using react.js.
Although I used componentDidUpdate instead of componentDidMount it caused an infinitive loop.
import React from "react";
import ReactMarkdown from "react-markdown";
import Contentful from "./Contentful";
import "./Article.css";
class ArticlesWithTag extends React.Component {
state = {
articleFromContentful: []
};
async componentDidMount() {
console.log("componentDidMount");
const { tag } = this.props.match.params;
//get article from contentful API
const contentful = new Contentful();
try {
const article = await contentful.getArtcleWithTags(
undefined,
"blogPost",
tag
);
this.setState({ articleFromContentful: article.items });
} catch (err) {
console.log("error");
console.log(err);
}
}
render() {
const bodyOfArticle = this.state.articleFromContentful.map(data => {
const returnTitle = () => {
return data.fields.title;
};
const returnPublishedDate = () => {
let date = data.fields.publishDate.substring(0, 10);
let replacedDate = date.replace("-", "/");
while (date !== replacedDate) {
date = date.replace("-", "/");
replacedDate = replacedDate.replace("-", "/");
}
return replacedDate;
};
const returnBody = () => {
return data.fields.body;
};
const returnTags = () => {
const tagList = data.fields.tags.map(data => {
const listContent = `#${data}`;
return <li>{listContent}</li>;
});
return tagList;
};
returnTags();
return (
<div className="article-container">
<div className="article-title">{returnTitle()}</div>
<p className="article-date">{returnPublishedDate()}</p>
<div className="article-body">
<ReactMarkdown source={returnBody()}></ReactMarkdown>
</div>
<div className="article-tags">
<ul>{returnTags()}</ul>
</div>
</div>
);
});
return <div className="article-outer">{bodyOfArticle}</div>;
}
}
export default ArticlesWithTag;
Ok componentDidMount is a lifecylce method that runs only when the component mounts
and componentDidUpdate runs everytime there's an update
You can set state in componentDidUpdate but it must be wrapped around some condition
In your case you can do something like this
async componentDidMount() {
console.log("componentDidMount");
const { tag } = this.props.match.params;
//get article from contentful API
const contentful = new Contentful();
try {
const article = await contentful.getArtcleWithTags(
undefined,
"blogPost",
tag
);
this.setState({ articleFromContentful: article.items });
} catch (err) {
console.log("error");
console.log(err);
}
}
and for further updates use compnentDidUpdate as
async componentDidUpdate(prevProps) {
const { tag } = this.props.match.params;
//get article from contentful API
const contentful = new Contentful();
if ( tag !== prevProps.match.params ) // if props have changed
{
try {
const article = await contentful.getArtcleWithTags(
undefined,
"blogPost",
tag
);
this.setState({ articleFromContentful: article.items });
} catch (err) {
console.log("error");
console.log(err);
}
}
}
Hope it helps
componentDidMount is called only when your component is mounted.
componentDidUpdate is called when the props has changed.
You should create a function from your componentDidMount logic and call it in componentDidUpdate only when tag has changed
componentDidMount() is only called when the component initially mounts onto the DOM. Re-renders caused by prop or state changes trigger componentDidUpdate(). As you mentioned, making state changes in this function causes infinite loops.
What you can do is use componentDidUpdate() and check if props have changed before deciding to update the state again. This should prevent your infinite loop situation.
I have tried numerous ways to set state but for some reason, the state never gets updated.this is the JSON data that I want my state to change to
export class Provider extends Component {
state = {
posts: [],
profileinfo: {},
dispatch: action => this.setState(state => reducer(state, action))
};
componentDidMount() {
fetch("http://localhost:3001/login").then(response =>
response
.json()
.then(data => this.setState({ profileinfo: data.firstname }))
);
console.log(this.state.profileinfo);
}
render() {
// ...
}
}
setState is asynchronous. Your console log probably triggers before the state got updated. If you want to see the result after the setState call, do it this way:
data => this.setState({ profileinfo: data.firstname }, () => {
console.log(this.state);
});
I have an React code that needs to fetch some data from an API, put it on a redux-store, and then render a List with this data. This is what Im doing
constructor(props) {
super(props);
this.state = {
isLoading: false,
};
}
componentDidMount() {
this.setState({ isLoading: true });
this.loadData();
}
loadData = async () => {
try {
API.getList()
.then(data => {
this.updateState(data);
})
.then(data => this.setState({ isLoading: false }))
.catch(function(error) {
console.log(error.message);
});
} catch (e) {}
};
updateState = async (data) => {
if (data != null) {
await this.props.mainActions.receiveData(data);
}
};
render() {
const { isLoading } = this.state;
if (isLoading) {
return <p>Loading ...</p>;
}
let items = [];
if (this.props.items.data !== undefined) {
items = this.props.items.data.stadiums;
}
return <MyList items={items} />;
}
}
The problem is, the first time it renders, when I try to get "this.props.items" it is undefined yet.
So I need to put this ugly IF to dont break my code.
What will be a more elegant solution for this problem?
I am assuming the use of ES6 here:
I would set a defaultProp for items in the MyList component
export class MyList extends Component {
...
static defaultProps = {
items: []
}
...
}
This way, if you pass items as undefined and mapping over items in your render method it will produce an empty array which is valid jsx
Ok. Just change the "componentDidMount" with "componentWillMount".
Jsx doesn't render undefined or null so you can include your condition in your return statement.
Instead of writing an if statement, do this:
return (
{
this.props.items.data &&
this.props.items.data.stadiums &&
<Mylist
items={this.props.items.data.stadiums}
/>
}
);