Issues revolving Promises - javascript

I am having issues with Promises pending and not resolving in time. When I try to use async to wait for a value, what I end up getting "Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead." and that's probably because I'm calling this peculiar method I'm trying to use that returns a promise in the render lifecycle method. Well, I tried using .then to retrieve the value but that didn't work either.
I'm going to be laying out a couple of files and explaining the best I can what does what and if there are better ways to do what I am trying to do, suggestions would be wonderful! If it's fixable, even better! Any help is greatly appreciated!
App.js (main app)
Components
- Navigation.js (Navigation bar.)
- MainContent.js(Main content: once you click on a navigation item, everything inside of main content changes)
MainContent.js
tabHandler = (tab) => {
//what do I do here? Immediately if I place the async keyword in the definition, the compiler hates me, but if I don't, then I don't get what I want.
const test = this.props.tabAPICall(tab).then(value => { console.log(value) })
console.log(test);
//if(tab === all the different navigation tabs, render stuff differently){
//as an example:
// return <Songs />
//}
//else
}
render(){
const { chosenTab } = this.props;
return (
<React.Fragment>
<ul.className="main-content">
{
chosenTab !== "" ? this.tabHandler(chosenTab) : null
}
</ul>
</React.Fragment>
)
}
the tabAPICall comes from App.js here:
tabAPICall = (tab) => {
const { token, baseUrl } = this.state;
fetch(`${baseUrl}/albums`, {
method: 'GET',
headers: { 'Authorization': 'Bearer ' + token }
})
.then((response) => response.json())
.then((data) => {
return data;
})
chosenTab gets updated at the app level to trigger a re-render which is what I want

You should set data fetching from API to your component state. And display data in the state:
in the tabHandler, after we get the data, set it to this.state.song through setState({ song: value }). When the data in state in changed. React will do the re-render and inject new data in state to your component.
constructor() {
this.state = { song: null }
}
componentDidMount() {
this.tabHandler(this.props.chosenTab)
}
componentWillReceiveProps(nextProps) {
if (nextProps.chosenTab != this.props.chosenTab) {
this.tabHandler(nextProps.chosenTab)
}
}
tabHandler = (tab) => {
//what do I do here? Immediately if I place the async keyword in the definition, the compiler hates me, but if I don't, then I don't get what I want.
this.props.tabAPICall(tab).then(value => { this.setState({ song: value }) })
}
render(){
const { chosenTab } = this.props;
return (
<React.Fragment>
<ul.className="main-content">
{ this.state.song ? <Song someProp={song} /> : null }
</ul>
</React.Fragment>
)

Related

Display data from an API when clicking a button. Using ReactJS

I am trying to display the data of each character when I click the Info Button.
I know it is because in the onButtonClickHandler function it can not see the state. I have also tried this.state.person but it gives me an error saying "can not read state". And if I try just state.person it will give me "undefined".
What is the best way to do that? Thank you
API Link: https://swapi.dev/people/
import React from "react";
export default class FetchActors extends React.Component {
state = {
loading: true,
person: null
};
async componentDidMount() {
const url = "https://swapi.dev/api/people/";
const response = await fetch(url);
const data = await response.json();
this.setState({ person: data.results, loading: false });
}
render() {
if (this.state.loading) {
return <div>loading...</div>;
}
if (!this.state.person.length) {
return <div>didn't get a person</div>;
}
function onButtonClickHandler(state) {
console.log(state.person);
};
return (
<div>
<h1>Actors</h1>
{this.state.person.map(person =>(
<div>
<div>
{person.name}
<button onClick={onButtonClickHandler}>Info</button>
</div>
</div>
))}
<button onClick={onButtonClickHandler}>Enter</button>
</div>
);
}
}
Please correct me if I'm wrong
The most likely reason why you are seeing this is because of the way javascript internally works. The syntax:
function xyz() {
}
has an implicit this
Maybe try changing your code from:
function onButtonClickHandler(state) {
console.log(state.person);
};
to:
const onButtonClickHandler = () => {
console.log(this.state.person);
};
Further Reading: Here
You have defined your function onButtonClickHandler as a function that takes one argument, and logs the person property of that argument. The argument state in your function has nothing to do with the state of your component. As javascript sees it, they are two totally unrelated variables which just happen to have the same name.
function onButtonClickHandler(state) {
console.log(state.person);
};
When button calls onClick, it passes the event as the argument. So your onButtonClickHandler is logging the person property of the event, which obviously doesn't exist.
Since you are not using any information from the event, your function should take no arguments. As others have said, you should also move this function outside of the render() method so that it is not recreated on each render. The suggestion to use bind is not necessary if you use an arrow function, since these bind automatically.
export default class FetchActors extends React.Component {
/*...*/
onButtonClickHandler = () => {
console.log(this.state.person);
};
}
Inside render()
<button onClick={this.onButtonClickHandler}>Enter</button>
You could also define the function inline, as an arrow function which takes no arguments:
<button onClick={() => console.log(this.state.person)}>Enter</button>
If you are new to react, I recommend learning with function components rather than class components.
Edit:
Updating this answer regarding our comments. I was so caught up in explaining the errors from doing the wrong thing that I neglected to explain how to do the right thing!
I am trying to display the data of each character when I click the Info Button.
Once we call the API, we already have the info loaded for each character. We just need to know which one we want to display. You can add a property expanded to your state and use it to store the index (or id or name, whatever you want really) of the currently expanded item.
When we loop through to show the name and info button, we check if that character is the expanded one. If so, we show the character info.
Now the onClick handler of our button is responsible for setting state.expanded to the character that we clicked it from.
{this.state.person.map((person, i) =>(
<div>
<div>
{person.name}
<button onClick={() => this.setState({expanded: i})}>Info</button>
{this.state.expanded === i && (
<CharacterInfo
key={person.name}
person={person}
/>
)}
</div>
CodeSandbox Link
there are a few ways you can resolve your issue; I'll give you the more common approach.
You want to define your click handler as a class (instance) method, rather than declare it as a function inside the render method (you can define it as a function inside the render method, but that's probably not the best way to do it for a variety of reasons that are out of scope).
You will also have to bind it's 'this' value to the class (instance) because click handlers are triggered asynchronously.
Finally, add a button and trigger the fetch on click:
class Actors extends React.Component {
state = {
loading: false,
actors: undefined,
};
constructor(props) {
super(props);
this.fetchActors = this.fetchActors.bind(this);
}
async fetchActors() {
this.setState({ loading: true });
const url = "https://swapi.dev/api/people/";
const response = await fetch(url);
const data = await response.json();
this.setState({ actors: data.results, loading: false });
}
render() {
console.log('Actors: ', this.state.actors);
return <button onClick={this.fetchActors}>fetch actors</button>;
}
}
Sometimes i takes react a min to load the updated state.
import React from "react";
export default class FetchActors extends React.Component {
state = {
loading: true,
person: null
};
async componentDidMount() {
const url = "https://swapi.dev/api/people/";
const response = await fetch(url);
const data = await response.json();
if(!data.results) { // throw error }
this.setState({ person: data.results, loading: false }, () => {
console.log(this.state.person) // log out your data to verify
});
}
render() {
if (this.state.loading || !this.state.person) { // wait for person data
return <div>loading...</div>;
}else{
function onButtonClickHandler(state) { // just make a componentDidUpdate function
console.log(state.person);
};
return (
<div>
<h1>Actors</h1>
{this.state.person.map(person =>(
<div>
<div>
{person.name}
<button onClick={onButtonClickHandler}>Info</button>
</div>
</div>
))}
<button onClick={onButtonClickHandler}>Enter</button>
</div>
);
}
}}

How to lazy load remote data with React

I'm fairly new to React and I'm trying to lazy load a markdown file stored on the server.
I've tried setting up an async arrow function that fetches the file and runs it through marked.
I found this demo here https://codesandbox.io/s/7zx3jlrry1 which I've tried following but haven't figured out how to follow it.
class Markdown extends React.Component {
constructor(props) {
super(props)
this.state = {
// some state
}
}
render() {
let markdown = React.lazy(async () => {
// fetch the file
const response = await fetch(path)
if (!response.ok) {
return (<p>Response was not ok</p>)
} else {
// if successful, return markdown
let blob = await response.blob()
return marked(blob)
}
})
return (
<React.Suspense fallback={<div class="markdown">Loading</div>}>
<div class="markdown" id={this.props.id} dangerouslySetInnerHTML={{__html: markdown}} />
</React.Suspense>
)
}
}
When I try debugging it the arrow function isn't actually executed, and the inner HTML of the div is "[object Object]".
Any help as to how I can achieve this would be greatly appreciated.
You get [object Object] in your html because dangerouslySetInnerHTML expects a function returning the object {__html: '<div>some html string</div>'}. Other than that you are not using recommended way of fetching data through network requests. Please read on to get more details on how to perform your task.
React Suspense is used to lazy load Components not for fetching data as the react docs state:
In the future we plan to let Suspense handle more scenarios such as data fetching.
React.Suspense lets you specify the loading indicator in case some components in the tree below it are not yet ready to render. Today, lazy loading components is the only use case supported by :
You don't require lazyload in this scenario. Use react-lifecycle methods in order to do things like fetching data at the correct time. What you require here is react-lifecylce method componentDidMount. Also you can use component state to manipulate what is rendered and what is not. e.g you can show error occured or loading by setting variables.
class Markdown extends React.Component {
constructor(props) {
super(props)
this.state = {
loading: true,
error: false,
html: ""
}
}
componentDidMount = () => {
this.fetchMarkdown()
}
fetchMarkdown = async () => {
const response = await fetch(path)
if (!response.ok) {
// if failed set loading to false and error to true
this.setState({ loading: false, error: true })
} else {
// If successful set the state html = markdown from response
let blob = await response.text()
this.setState({ loading: false, html: blob })
}
}
getMarkup = () => {
return { __html: this.state.html }
}
render() {
if (this.state.error) {
return <div>Error loading markup...</div>
}
else if (this.state.loading){
return <div>Loading...</div>
}
else {
return <div class="markdown" id={this.props.id} dangerouslySetInnerHTML={this.getMarkup()} />
}
}
}

React setState inside componentDidUpdate causing infinite loop

Can someone help me solve how do I setState inside componentDidUpdate and not have an infinite loop? Some suggestions said to have a conditional statement, but I am not too familiar with how do I set the conditional for my code.
This is what my code looks like:
I have a dashboard component that gets all the companies and projects data from external functions where the fetch happens and then updates the state. The projects are associated with the company's id.
I am able to get the list of all the projects in JSON, but I can't figure out how to update my projects state inside componentDidUpdate once rendered.
CompanyDashboard.js
import { getCompanys } from "../../actions/companyActions";
import { getProjects } from "../../actions/projectActions";
class CompanyDashboard extends Component {
constructor(props) {
super(props);
this.state = {
companies: [],
projects: []
};
}
componentWillMount() {
// get all companies and update state
getCompanys().then(companies => this.setState({ companies }));
}
componentDidUpdate(prevState) {
this.setState({ projects: this.state.projects });
}
render() {
const { companies, projects } = this.state;
{
companies.map(company => {
// get all the projects
return getProjects(company);
});
}
return <div />;
}
}
export default CompanyDashboard;
companyActions.js
import { getUser, getUserToken } from './cognitoActions';
import config from '../../config';
export function getCompanys() {
let url = config.base_url + '/companys';
return fetch(url, {
method: 'GET',
headers: {'token': getUserToken() }
})
.then(res => res.json())
.then(data => { return data })
.catch(err => console.log(err));
}
projectActions.js
import { getUserToken } from './cognitoActions';
import config from '../../config';
export function getProjects(company) {
let url = config.base_url + `/companys/${company._id['$oid']}/projects`;
return fetch(url, {
method: 'GET',
headers: {'token': getUserToken() }
})
.then(res => res.json())
.then(data => { return data })
.catch(err => console.log(err));
}
The following code is not doing anything meaningful. You are setting your state.projects to be equal to your state.projects.
componentDidUpdate() {
this.setState({ projects: this.state.projects })
}
Also, the following code is not doing anything meaningful because you are not saving the result of companies.map anywhere.
{
companies.map((company) => {
return getProjects(company)
})
}
It's hard to tell what you think your code is doing, but my guess is that you think that simply calling "companies.map(....) " inside your render function is going to TRIGGER the componentDidUpdate function. That is not how render works, you should go back to the drawing board on that one. It also looks like you think that using the curly brackets {} inside your render function will display the objects inside your curly brackets. That's also not true, you need to use those curly brackets inside the components. For instance: {projects}
If I had to guess... the following code is how you actually want to write your component
import { getCompanys } from '../../actions/companyActions';
import { getProjects } from '../../actions/projectActions';
class CompanyDashboard extends Component {
constructor(props) {
super(props);
this.state = {
companies: [],
projects: []
}
}
componentWillMount() {
getCompanys().then(companies => {
const projectPromises = companies.map((company) => {
return getProjects(company)
});
Promise.all(projectPromises).then(projects => {
//possibly a flatten operator on projects would go here.
this.setState({ companies, projects });
});
/*
* Alternatively, you could update the state after each project call is returned, and you wouldn't need Promise.all, sometimes redux can be weird about array mutation in the state, so look into forceUpdate if it isn't rerendering with this approach:
* const projectPromises = companies.map((company) => {
* return getProjects(company).then(project => this.setState({projects: this.state.projects.concat(project)}));
* });
*/
)
}
render() {
const { companies, projects } = this.state;
//Not sure how you want to display companies and projects, but you would
// build the display components, below.
return(
<div>
{projects}
</div>
)
}
}
export default CompanyDashboard;
componentDidUpdate has this signature, componentDidUpdate(prevProps, prevState, snapshot)
This means that every time the method gets called you have access to your prevState which you can use to compare to the new data, and then based on that decide if you should update again. As an example it can look something like this.
componentDidUpdate(prevProps, prevState) {
if (!prevState.length){
this.setState({ projects: this.state.projects })
}
}
Of course this is only an example since I don't know your requirements, but this should give you an idea.
When componentDidUpdate() is called, two arguments are passed:
prevProps and prevState. This is the inverse of
componentWillUpdate(). The passed values are what the values were,
and this.props and this.state are the current values.
`componentDidUpdate(prevProps) {
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}`
You must check the state/props if new state/props different from previous one then you can allow to update your component.
You may call setState() immediately in componentDidUpdate() but
note that it must be wrapped in a condition like in the example above,
or you’ll cause an infinite loop. It would also cause an extra
re-rendering which, while not visible to the user, can affect the
component performance. If you’re trying to “mirror” some state to a
prop coming from above, consider using the prop directly instead.
This is because componentDidUpdate is called just after a component takes up somechanges in the state. so when you change state in that method only then it will move to and from from that method and state change process

React/Redux: Can't map over state [object Object]

Trying to orient through the dark depths of Redux-React-API jungle - managed to fetch data from API and console.log it - but neither me nor my Google skills have managed to find out why it doesn't render.
React Components
Parent Component:
class Instagram extends Component {
componentWillMount(){
this.props.fetchInfo();
}
render() {
return (
<div className="container">
<div className="wrapper">
<InstagramPost />
</div>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({ fetchInfo }, dispatch);
}
export default connect(null, mapDispatchToProps)(Instagram);
Child Component:
class InstagramPost extends Component {
render() {
console.log(this.props.info);
this.props.info.map((p,i) => {
console.log("PROPS ID: " + p.id);
})
return (
<div>
<h1>POSTS</h1>
<ul className="uls">
{
this.props.info.map((inf, i) =>
<li key={i}>{inf.id}</li>
)
}
</ul>
</div>
)
}
}
const mapStateToProps = ({ info }) => {
return { info }
}
export default connect(mapStateToProps)(InstagramPost);
Redux Action method:
const ROOT_URL = 'https://jsonplaceholder.typicode.com/posts';
export const fetchInfo = () => {
const request = axios.get(ROOT_URL);
return {
type: types.FETCH_INFO,
payload: request
};
}
Redux Reducer method:
export default function(state = [], action) {
switch (action.type) {
case FETCH_INFO:
return action.payload.data;
default:
return state;
}
}
The JSON file looks like this:
In the console - it works and I get my Objects:
The state is also updated:
But when I map over this.props.info, trying to render this.props.info.id, nothing is rendered on the page.. Incredibly thankful for any input!
Looks like your props aren't set on the initial render. I'm guessing your API call hasn't finished.
Try checking the the variable is set or is an array first:
Something like this:
class InstagramPost extends Component {
render() {
if(!this.props.info) return null
return (
<div>
<h1>POSTS</h1>
<ul className="uls">
{
this.props.info.map((inf, i) => {
return <li key={i}>{inf.id}</li>
})
}
</ul>
</div>
)
}
}
const mapStateToProps = ({ info }) => {
return { info }
}
export default connect(mapStateToProps)(InstagramPost);
Or you may want to check the length this.props.info.length > 0.
There were two problems. As Mayank Shukla pointed out, nothing was returned from the map callback because of the block braces ({}) without a return statement.
The other problem was in the reducer. As the redux state for info is an array of users, you need to replace the old state on FETCH_INFO rather than add the fetched array to the beginning of it. Otherwise, you're maintaining an array of arrays of users, which will grow by one on each fetch.
Note that you don't need any checks on this.props.info, as it will be initialized to [] by your reducer and [].map(f) == [].
For redux debugging I can very much recommend installing the Redux DevTools extension, as it will allow you to inspect all updates to the store. It needs a little setup per project, but that's well worth it.
Oh, and in the future, you might want to refrain from updating your question with suggestions from the comments/answers, as the question will no longer make sense to other visitors :-)

what to do with response object in react js

i'm working with react to complete the front end of a rest application.
I have json being sent to the front end, and I use fetch .
fetch('/task')
.then(function(data) {
return data.json();
})
.then(function(json) {
json.tasks.forEach(function(task) {
console.log(task.name)
})
});
So i'm able to console.log each task.name, but where to now? How do I get my component to display each task as a ?
Basically, where in a component does this type of logic go? Do i save the fetch request to a variable and then setState = variable?
this is my component:
class Task extends React.Component {
render() {
return <p> hey </p>
}
}
You need to initialize a state object, which you can update when the fetch is complete:
class Task extends React.Component {
constructor () {
super()
this.state {
tasks: null
}
}
componentDidMount () {
fetch('/task')
.then((data) => {
return data.json()
})
.then((json) => {
this.setState({ tasks: json.tasks })
})
}
renderTaskList () {
if (this.state.tasks) {
return (
<ul>
{this.state.tasks.map((task, i) => <li key={i}>{task.name}</li>)}
</ul>
)
}
return <p>Loading tasks...</p>
}
render () {
return (
<div>
<h1>Tasks</h1>
{this.renderTaskList()}
</div>
)
}
}
Edit: Re-reading this answer, I just wanted to note that it is not necessary to initialize the tasks property of the state object in this case. You could also just do something like:
this.state = {}
However, I think there is some value in explicitly naming the various properties of your state object, even if they are initialized as null. This allows you to write components whose state is documented in the constructor, and will prevent you or your teammates from later guessing how a component's state is modeled.

Categories