I have just started learning JavaScript and am building an application using React.
I am struggling with an issue and would appreciate your help. I did come across similar questions but wasn't convinced with the answers and they didnt help me.
Please find my issue below . I am hiding the real name of the variables , components .
myService.js
export const findThisById = async Id=> {
const response = await fetch(`${URL}/${Id}`);
const json = await response.json();
return json;
};
myContainer.js // This is the parent component which has many chil component
.......
import {findThisById} from "../myService"
findThis= async Id=> {
const xyz= await findThisById(Id);
return xyz;
<Component1 findThis = {this.findThis}/>
......
Component1.js
const Component1 = ({findThis}) => (
<Component2 findThis = {findThis} id = {Id} // Id is being passed successfully
/>
)
Component2.js
class Component2 extends React.Component {
constructor(props) {
super(props);
console.log("Inside Constructor");
const something = this.props.findThis(this.props.id);
// course.then(data => console.log(data) // This works, i get the right data
)
console.log(something ); // Using this i get Promise state pending
}
// I need to use this value "something" inside a <h1></h1>
}
I have ommited all exports/imports but they work correctly
The network tab in the console also make a 200 request and the response sub tab shows the correct values.
Should i use .then when i call the function findThis? Why? I am using await for fetch and its response.
Please help me. Struggl
As mentioned in one of the comments async / await is just syntactic sugar for promises, so generally speaking you can either await or .then the result of findThis. That said, since you are calling this inside of a constructor function you won't be able to use await since the constructor isn't an async function.
One way to get the result of the Promise to render in your component would be to set the result in the component state and then render from the state. As mentioned by #Emile Bergeron in a follow up comment, you probably don't want to call setState in your constructor but instead in componentDidMount. This is to avoid calling setState on a potentially unmounted component.
class Component2 extends React.Component {
constructor(props) {
super(props);
this.state = {
searchResult: ''
}
}
componentDidMount() {
this.props.findThis(this.props.id).then(searchResult => {
this.setState({
searchResult
})
});
}
render() {
return <div>{this.state.searchResult}</div>
}
}
Related
All articles I have read on promises show examples with console.log - I am using AWS Athena and want to display the result on the webpage in my React export. The react export does not allow the use of .then. So I need to resolve the promise to an external variable.
client is a aws athena client which returns a promise I need to resolve.
async function getResult(){
try {
return await client.send(command);
} catch (error) {
return error.message;
}
}
export default getResult()
I want to display the result in App.js
render()
{
return (
{ athena }
)
It displays in the console but not on the webpage as the page is loaded before the variable is resolved.
More complete example of App.js
import athena from './athena';
class App extends Component {
render()
{
let athena_result = athena.then(function(result) {
console.log(result)
}
)
return ( athena_result )
Causes Error
Error: Objects are not valid as a React child (found: [object Promise])
The render method of all React components is to be considered a pure, synchronous function. In other words, there should be no side effects, and no asynchronous logic. The error Error: Objects are not valid as a React child (found: [object Promise]) is the component attempting to render the Promise object.
Use the React component lifecycle for issuing side-effects. componentDidMount for any effects when the component mounts.
class App extends Component {
state = {
athena: null,
}
componentDidMount() {
athena.then(result => this.setState({ athena: result }));
}
render() {
const { athena } = this.state;
return athena;
}
}
If you needed to issue side-effects later after the component is mounted, then componentDidUpdate is the lifecycle method to use.
Class components are still valid and there's no plan to remove them any time soon, but function components are really the way going forward. Here's an example function component version of the code above.
const App = () => {
const [athenaVal, setAthenaVAl] = React.useState(null);
React.useEffect(() => {
athena.then(result => setAthenaVAl(result));
}, []); // <-- empty dependency array -> on mount/initial render only
return athenaVal;
}
The code is a little simpler. You can read more about React hooks if you like.
You can use a state, and just set the state to the response's value when it's done:
const Component = () => {
const [athena, setAthena] = useState(""); // Create a state with an empty string as initial value
// Send a request and on success, set the state to the response's body, and on fall set the state to the error message
useEffect(() => client.send(command).then((response) => setAthena(response.data)).catch((error) => setAthena(error.message)), []);
return <>{athena}</>;
};
I can't quite wrap my head around this.
I'm having to pass data that's fetched asynchronously. Issue is, the props are asynchronous as well. Here's a simplified version of the component:
import React, { Component } from 'react'
import CSVLink from 'react-csv'
import generateData from './customApi/generateData
type Props = { job?: JobType | undefined }
type State = { csvData: string[][] }
class MyComponent extends Component<Props, State> {
state = {
csvData = [],
}
generateCsv = async (jobId: string) => {
const csvData = await generateData(jobId)
this.setState({ csvData })
}
async componentDidMount() {
const { job } = this.props
await this.generateCsv(job.id)
}
render() {
const { csvData } = this.state
return (
<CSVLink data={csvData}>
<p>Download csv</p>
</CSVLink>
)
}
}
export default connectFirestore(
(db, params) => ({ getJob(db, params.id) })
)
Basically my props are fetched from an API call to firestore, so it takes a while to load the job. Issue is, when I'm trying to pass the jobId inside the async componentDidMount(), it ends up passing undefined, because the job props are not loaded yet.
I don't link the whole passing state to async call business going on, but I can't think of any other way, how I would passing the csvData from the generateDate() async call only once the Promise is resolved.
I guess the easiest way to approach this would be, to perhaps somehow check if the props inside the componentDidMount() are already fetched?
What would be the correct way to approach this?
You are missing to implement the constructor where the props are set
constructor(props){
super(props);
this.state = {
csvData = [],
};
}
componentDidMount(){
//This will work
console.log(this.props.job);
};
If job property should by async, do it async: rather than passing a value which will change in future, you can pass a Promise which resolves with the id, than change your componentDidMount as follows:
componentDidMount() {
const id = await this.props.job;
this.generateCsv(id)
}
Hope this helps.
Maybe you should change the code inside your parent component, I imagine that you are making the API call there, as you are passing this data as props in this component you are showing to us.
I also imagine that you are making the API call with the fetch command, which can have a .then(()=>{}) method triggered when the API call finished loading, after that you can change the state of that component carrying the API fetched data and THEN render this child. Something I used recently for my project was to load from API, update state and conditionally render the child component, which will not know I made an API call because I am passing already loaded data. Normally while it is waiting I put something like this:
if(this.state.dataFetched == null)
return(<h1>Loading page...</h1>)
else return <childComponent loadedData = {this.state.dataFetched}/>
And then access that data as this.props.loadedData
Not sure if it's the optimal solution, but it works.
I've decided to use componentDidUpdate() life-cycle method, where I'm comparing whether the props have already update and once they did, I'm calling the asynchornous function for generating the csv data.
async componentDidUpdate(prevProps: Props) {
if (prevProps !== this.props && !!this.props) {
const { job } = this.props
if (job) {
await this.generateCsv(job.id)
}
}
}
This way we generate new data every time the data inside the props.job changed and also we don't attempt to call generateCsv() on undefined while it's still being fetched from firestore.
i have a problem where one of my components has to await to get some data while its rendering but i can't find a way to do that.
so i have the render method
render() {
const getComponentProps = async () => {
return await this.props.Store.getComponentProperties(id);
};
componentProps = getComponentProps(id);
return <MyComponent
.
.
data={componentProps}/>;
}
the problem is that my component is rendering before the data is fetched. i can't make the whole render await, i also tried making the componentProps a state on the hope it would rerender once it's ready, but that also didn't work. and finally i tried the new Suspense/Lazy feature in the new react version, which also didn't work.
the data i'm fetching is making a REST call to my database and I have to await it. also, the render it mapping over a list of components not just one, and for each component is has to get its properties and load them.
any thoughts on how to make this async data fetch in render ???
To achieve this, you will have to use your state.
When your component is mounted, you can call the function setState and wait for your data to be fetched. Once the data is fetched, your component will re-render with the correct data :
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = {
data: null
}
}
componentDidMount = async() => {
this.setState({ data: await this.props.Store.getComponentProperties(id) })
}
render() {
return <MyComponent2 data={this.state.data} />;
}
}
If you do not want your child component to receive empty data, you can choose to not render it while your data has not been fetched :
render() {
return this.state.data ? <MyComponent data={this.state.data} /> : <div/>
}
I am new to ReactJS and am unsuccessfully attempting to manage a state change. The initial state renders as expected, the state successfully changes, however the elements do not render afterwards. There are no errors in the DOM console to go off of. I've made sure to set the initial state in the constructor of the component class, and I've also tried binding the method I'm using in the constructor since I've read auto-binding is not a part of ES6. The relevant component code is as follows:
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
myIDs: Array(6).fill('0')
};
this.getMyIDs = this.getMyIDs.bind(this);
};
componentDidMount() {
var ids = this.getMyIDs();
ids.then((result)=> {
this.setState({ myIDs: result }, () => {
console.log(this.state.myIDs)
});
})
};
componentWillUnmount() {
this.setState({
myIDs: Array(6).fill('0')
});
};
getMyIDs() {
return fetch('/api/endpoint').then((response) =>{
return response.json();
}).then((myIDs) => {
return myIDs.result
})
};
render() {
return (
<Tweet tweetId={this.state.myIDs[0]} />
<Tweet tweetId={this.state.myIDs[1]} />
);
}
}
export default MyComponent
UPDATE: The 'element' being updated is the 'Tweet' component from react-twitter-widgets. Its source is here:
https://github.com/andrewsuzuki/react-twitter-widgets/blob/master/src/components/Tweet.js'
export default class Tweet extends React.Component {
static propTypes = {
tweetId: PropTypes.string.isRequired,
options: PropTypes.object,
onLoad: PropTypes.func,
};
static defaultProps = {
options: {},
onLoad: () => {},
};
shouldComponentUpdate(nextProps) {
const changed = (name) => !isEqual(this.props[name], nextProps[name])
return changed('tweetId') || changed('options')
}
ready = (tw, element, done) => {
const { tweetId, options, onLoad } = this.props
// Options must be cloned since Twitter Widgets modifies it directly
tw.widgets.createTweet(tweetId, element, cloneDeep(options))
.then(() => {
// Widget is loaded
done()
onLoad()
})
}
render() {
return React.createElement(AbstractWidget, { ready: this.ready })
}
}
As in React docs:
componentWillMount() is invoked just before mounting occurs. It is
called before render(), therefore calling setState() synchronously in
this method will not trigger an extra rendering. Generally, we
recommend using the constructor() instead.
Avoid introducing any side-effects or subscriptions in this method.
For those use cases, use componentDidMount() instead.
you should not use ajax calls in componentWillMount
call ajax inside: componentDidMount
another thing: why do you use
componentWillUnmount
the object will be removed no reason to have that call there.
The only issue that is present in your current code is that you are returning multiple Element component instances without wrapping them in an array of a React.Fragment or a wrapper div. With the latest version of react, you must write
render() {
return (
<React.Fragment>
<Element Id={this.state.myIDs[0]} />
<Element Id={this.state.myIDs[1]} />
</React.Fragment>
);
}
}
Also as a practice you must have your Async calls in componentDidMount instead of componentWillMount as the React docs also suggest. You might want to read this answer on where write async calls in React for more details
Another thing that you must remember while using prop Id in your Element component is that componentWillMount and componentDidMount lifecycle functions are only called on the initial Render and not after that, so if you are using this.props.Id in one of these function in Element component then you will not be able to see the update since the result of async request will only come later, check this answer on how to tacke this situation
This is the promise within a render() method of a React component.
firebaseRef.child("users/" + auth.uid).once('value').then((snapshot) => {
let userName = snapshot.val().name;
});
I want to get the data of snapshot.val().name and put it in the
return(
<h1>{userName}</h1>
)
I could get the data out of the promise with an action dispatch to the redux store and then retrieving it where I need it but there should be a shorter way of achieving it I suppose? I tried different ways to do so but I failed due to the asynchronicity so... please help!
I haven't done React in a while, but I think it's:
firebaseRef.child("users/" + auth.uid).once('value').then((snapshot) => {
let userName = snapshot.val().name;
this.setState({ userName: userName });
});
I was going to upvote the previous comment, but he failed to mention that doing this inside of the render method is a bad idea. That would make an infinite loop. Render -> promise resolves -> set state -> render -> repeat. You should do your firebase promise like this:
class Test extends React.Component{
constructor() {
super()
this.state = {}
}
componentDidMount() {
let { auth } = this.props //or wherever it comes from
firebaseRef.child("users/" + auth.uid).once('value').then((snapshot) => {
this.setState({ userName: snapshot.val().name });
});
}
render() {
let { userName } = this.state
return (
<h1>{userName}</h1>
)
}
}