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
}
}
Related
Newbie question.
I'm learning ReactJS in Typescript and am coming from an AngularJS background (trying to unlearn AngularJS).
The code below runs and displays the props.compiler. When I tap the H1, the handler fires but the React code doesn't update the display like it would in AngularJS.
What am I missing?
export interface IHelloProps {
compiler: string;
framework: string;
}
export class Hello extends React.Component<IHelloProps> {
constructor(props: IHelloProps) {
super(props);
this.x = props.compiler;
}
private x: string;
get Name(): string {
return this.x;
}
set Name(value: string) {
this.x = value;
}
render() {
return <h1 onClick={this.handler}>Hello from {this.Name}!</h1>;
}
handler = async (t: any) => {
const resp = await fetch('https://api.github.com');
const data = await resp.json();
this.Name = data.current_user_url;
};
}
There is no two-way binding like in AngularJS, you need to use setState in order to schedule an update to a component’s state object:
// A bit more React-Class oriented example
class App extends React.Component {
state = {
currentUser: this.props.compiler
};
handler = async t => {
const resp = await fetch('https://api.github.com');
const data = await resp.json();
this.setState({ currentUser: data.current_user_url });
};
render = () => {
const { currentUser } = this.state;
return <h1 onClick={this.handler}>Hello from {currentUser}!</h1>;
};
}
// Original OP class
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
x: props.compiler
};
}
get Name() {
return this.state.x;
}
set Name(value) {
this.setState({ x: value });
}
handler = async t => {
const resp = await fetch('https://api.github.com');
const data = await resp.json();
this.setState({ x: data.current_user_url });
};
render = () => {
return <h1 onClick={this.handler}>Hello from {this.Name}!</h1>;
};
}
Try reading the Getting Started section which explains it in depth.
In React you don't set the values like you did in your setter function.
React renders the DOM asynchronously by comparing the virtual DOM.
So you need to use setState() to update the state of your DOM.
You can add state in your code and pass it to the React component.
handler = async (t: any)=> {
const resp = await fetch('https://api.github.com');
const data = await resp.json();
this.Name=data.current_user_url;
this.setState({name:data.current_user_url}); //name being the state variable
}
Hope it helps!!
TypeScript's interface is purely for typechecking. You should not be directly referencing any of its properties.
Instead, I would recommend you to create an interface for your state, which holds the values of current_user_url from your fetch request.
Then, on your click handler, you update the state by calling setState() to re-render the component with the updated state. On important thing to take note, is that you should never mutate state directly.
In order to fully implement TypeScript's typechecking capabilities on your component, you should use provide React.Component with its prop and state type parameters.
I have modified your code such that your component is TypeScript-compliant. Do take note of the new IHelloState interface.
export interface IHelloProps {
compiler: string;
framework: string;
}
export interface IHelloState {
name: string;
}
export class Hello extends React.Component<IHelloProps, IHelloState> {
constructor(props: IHelloProps) {
super(props);
this.state = {
name: '',
};
}
handler = async (t: React.MouseEvent<HTMLButtonElement>)=> {
const resp = await fetch('https://api.github.com');
const data = await resp.json();
this.setState({
name: data.current_user_url,
});
}
render() {
const { name } = this.state;
return <h1 onClick={this.handler}>
Hello from {name}
</h1>;
}
}
You may read more about working with React and TypeScript over here.
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 a React component getting an item's info and returning JSX:
const detail = props => {
const service = new Services()
const detail = service.findItem(props.match.params.id)
.then(item => {
console.log(item) // logs correct details, including the title property
return item
})
.catch(err => console.log(err))
return (
<h1>{detail.title}</h1> // !! prints nothing inside the <h1> tag
)
}
As seen above, returning object logs correctly all properties, but when trying to access them through JSX, no info is shown.
There are no console errors.
Its because the detail has not yet resolved, you can have React.Component and use
export class detail extends React.Component {
state = {
item: {}
}
componentDidMount(){
const service = new Services()
const detail = service.findItem(props.match.params.id)
.then(item => {
this.setState({ item:item });
})
.catch(err => console.log(err))
}
render() {
return <h1>{this.state.item.title}</h1>
}
}
I'm using map to get the IDs from the array of objects in the render.
My code:
class AppliedCandidates extends Component {
render() {
const {
appliedjobs
} = this.props
const {
joblists
} = this.props {
joblists && joblists.map(joblist => {
this.props.getAppliedJobs(joblist.id)
})
}
return ( <
div > {
appliedjobs && appliedjobs.map(appliedjob => {
return <ol >
<
li > {
appliedjob.jobid
} < /li> <
li > {
appliedjob.candidatephoneno
} < /li> <
/ol>
})
} <
/div>
);
}
}
const mapStateToProps = (state) => {
console.log("state", state);
return {
joblists: state.getJobs.job,
appliedjobs: state.getAppliedJobs.appliedjob
}
}
const mapDispatchToProps = (dispatch) => {
return {
getAppliedJobs: (joblists) => dispatch(getAppliedJobs(joblists))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AppliedCandidates);
and in the following code which is in the Redux action.js, if I uncomment the array variable "appliedjobs" the process goes into an endless loop. If I comment it out, I only get the last value.
var appliedjobs = []
const getAppliedJobs = (joblists) => {
return (dispatch, getState, {
getFirebase,
getFirestore
}) => {
const firestore = getFirestore();
firestore.collection('Jobs').doc(joblists).collection('AppliedJobs').where("jobid", "==", joblists)
.get()
.then((querySnapshot) => {
if (querySnapshot.empty === true) {
dispatch({
type: 'GET_APPLIED_JOB_ERROR',
joblists
});
} else {
//appliedjobs =[]
querySnapshot.forEach(function(doc3) {
appliedjobs.push({
candidatephoneno: doc3.data().candidatephoneno,
jobid: doc3.data().jobid,
});
});
dispatch({
type: 'GET_APPLIED_JOB',
payload: appliedjobs
});
}
})
}
};
How to get the values?
You shouldn't dispatch actions in your render function. If you need to populate your data by dispatching actions to the store, you should do it in a lifecycle method, in this case I think componentDidUpdate fits best.
from facebook's documentation:
componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.
Use this as an opportunity to operate on the DOM when the component has been updated. This is also a good place to do network requests as long as you compare the current props to previous props (e.g. a network request may not be necessary if the props have not changed).
As Dor Shinar has said, don't call actions on the render function. Why? Because render function will be invoked every time any props/state is updated. So, if you call the action there, you'll keep re-rendering your page since you'll keep getting new updated props from the dispatched action.
I'm not sure about your action, since I never use firebase. But I guess it's some query calls.
class AppliedCandidates extends Component {
componentDidMount() {
// here, the joblists props will get automatically updates from the dispatched redux action, dont mutate/change the props by yourself, change it via action.
const { getAppliedJobs, joblists } = this.props;
getAppliedJobs(joblists); // to be honest, why your action have the input of array and you put up id of individual array item? So I just put the entire joblists as the function parameter.
}
render() {
const { appliedjobs } = this.props;
if (appliedjobs.length === 0) {
return null;
}
return ( // you can add more
<ol>
{appliedjobs.map(appliedjob => <li>{appliedjob.id}</li>)}
</ol>
)
}
const mapStateToProps = (state) => {
return {
joblists: state.getJobs.job,
appliedjobs: state.getAppliedJobs.appliedjob
}
}
const mapDispatchToProps = (dispatch) => {
return {
getAppliedJobs : (joblists) => dispatch(getAppliedJobs(joblists))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AppliedCandidates);
My app has a loading state while using fetch to retrieve data from an API.
I'm using a callback to setState to toggle between loading: true and loading: false. Everything works perfectly in FF and Chrome but chokes and dies in Safari.
With react-devtools I can see that I'm receiving data from the API. My state.loading is even updated to false.
However, the child components are not being updated and I'm stuck with an eternal spinner.
The Safari console is completely blank.
If I pull the loading state out of the app everything loads. But then I get a TypeError: null is not an object (evaluating 'openingComment.nextSibling') on every click event. Not getting that in Chrome and FF either.
I'm at a loss here. How do I exorcise these Safari demons? Any help would be much appreciated.
Here is the relevant code. I'm pre-refactor stage so I know a lot of this can be improved. i.e. calling setState twice in the same function etc.
from App.js
class App extends Component {
state = {
data: {},
formData: {},
loading: true
}
getFormData = () => {
const newState = {
// Getting formData from url params
}
const mergedState = {...this.state, ...newState}
this.setState(mergedState, () => {
// get data from API
getData(this.state.formData, (data) => {
let newState = {
data,
loading: false
}
const mergedState = {...this.state, ...newState}
this.setState(mergedState)
})
})
}
setFormData = (formData) => {
const parsedAddress = this.parseAddress(formData.address.address_components)
const newState = {
formData: {
// Get formData from form submission
}
}
const mergedState = {...this.state, ...newState}
// Put formData in state
this.setState(mergedState, () => {
// Get API data
getData(this.state.formData, (data) => {
let newState = {
data,
loading: false
}
const mergedState = {...this.state, ...newState}
this.setState(mergedState)
})
})
}
renderComponents = () => {
if(isEmpty(this.state.formData) && this.state.data.medianScore === 0) {
// Return Component
} else {
return <Report
loading={this.state.loading}
// A bunch more props.
/>
}
}
render () {
return (
<div>
{this.renderComponents()}
</div>
)
}
}
export default App
from Report
class Report extends React.Component {
render () {
const renderReport = () => {
// This is where our problem is
if (this.props.loading) {
// These components render properly
} else {
// Even though this.props.loading gets set to false
// These components never get renderd
}
}
return (renderReport())
}
}
export default Report