I have an array in a components state that I want to fill with some data from an API call. The problem is that it apparently always gets set do "undefined", and hence I cannot do any functions on it/present the data in the DOM.
This is my code right now:
class DocumentsPage extends Component {
constructor(props) {
super(props);
this.state = { documents: [] };
}
componentDidMount() {
this.getDocuments();
}
getDocuments = (e) => {
fetch('api/GetDocuments').then(documents =>
documents.json()).then(data => {
this.setState({
documents: data
});
})
}
render() {
return (
<div>
{this.state.documents.map(document => <div> {document} </div>)}
</div>
)
}
}
But I get this error when trying to present the data: "TypeError: Cannot read property 'map' of undefined". What am I doing wrong?
EDIT: I changed to this.state.documents.map, but now nothing get presented anyways, no errors either whatsoever. What am I missing?
EDIT #2: Solved it by writing the following code in the render() instead:
return (
<ul>
{this.state.documents.map((document) => (
<li key={document.id}>{document.name}</li>))}
</ul>
)
Write your render like this
render() {
return (
<div>
{this.state.documents && this.state.documents.map(document => <div> {document} </div>)}
</div>
)
}
Related
I'm trying to build a products page with list of products with their details fetched from an external API and being displayed as cards. I looked through how to do it and found this to be similar to what I wanted to do but I mimicked the code in this post React fetch api data to component and I'm getting an error TypeError: Cannot read property 'map' of undefined
Products component
class Products extends Component {
constructor(props){
super(props);
this.state = {
items: [],
isLoaded: false,
}
};
componentDidMount = () => {
fetch("https://api.themoviedb.org/3/movie/popular?api_key=xxxxxxxx&page=1")
.then(resp => resp.json())
.then(resp => {
this.setState({
isLoaded: true,
items: resp.results
})
console.log(this.state.items)
})};
render() {
var {isLoaded, items} = this.state;
return (
<div>
{items.map(item => (<Card key={item.id} item={item} />))};
</div>
);
}
}
export default Products;
Card Component
const Card = (props) => {
const { item } = props;
return (
<div className="movie-container">
<img src="https://image.tmdb.org/t/p/w185/{items.poster_path}" alt="NO PHOTO" className="movie-container__img" />
<div className="movie-container__about">
<span className="movie-container__percent">{item.vote_average}</span>
<h2 className="movie-container__title">{item.original_title}</h2>
<p className="movie-container__date">{item.release_date}</p>
<p className="movie-container__text">{item.overview}</p>
MORE
</div>
</div>
)
}
export default Card;
It seems that if render() executes, state.items can be null or undefined, most likely as a result of what your API returns, or how you process what it returns.
The reason to 'blame' the API is because you initialize items to [] in the constructor, so initially there is an array and calling map will work.
To fix this, check if items has a value; if not then don't show anything. And then check if you access the API correctly, perhaps also dive into the API to see what it does and fix it.
It would also be good to check isLoaded so you don't show data unless the API call has finished.
render() {
var {isLoaded, items} = this.state;
if (!isLoaded || !items)
return null; // or if you like, show a "Waiting" indicator
return (
<div>
{items.map(item => (<Card key={item.id} item={item} />))};
</div>
);
}
I am new to using React and I'm not sure what's the best practice or correct way I should be displaying data.
Should I always be looking to break down things that can be potentially their own component into smaller components, rather than having one big component render? I am assuming yes. I just don't know when.
Anyways, I have the following component which makes a fetch request to an api. It returns some data. I set the state to the data. Then I create a variable coins, which maps data into a <ul> with <li>. I then return it as a <div> containing the data as {coins}. My question is, should the <ul> and the <li> be created as a separate component, which takes props, which will be the state of the this Coins' component? Can someone help me get started?
export default class Coins extends React.Component {
constructor(props) {
super(props);
this.state = {
lists: [],
error: null,
};
}
//lifecycle method to call loadCoins when Coins component is displayed?
componentDidMount() {
this.loadCoins();
}
//Data fetch from Coins API
loadCoins() {
this.setState({
error: null,
loading: true
});
return fetch(API_BASE_URL)
.then(res => {
if(!res.ok) {
return Promise.reject(res.statusText);
}
return res.json();
})
.then(coins => {
console.log('coins:', coins);
this.setState({
lists: coins.Data,
loading: false
})
}
)
.catch(err => {
this.setState({
error: 'Could not load coins',
loading: false
})
});
}
//Map the fetch data into individual cards/uls as JSX
render() {
const coins = this.state.lists.map((coin, index) => {
return <ul className='coin-containers' key={coin.CoinInfo.Id}>
<li><img className='coinImages' src={`${API_BASE_IMAGE}${coin.CoinInfo.ImageUrl}`} alt={coin.CoinInfo.FullName}/></li>
<li>{index+1}</li>
<li>{coin.CoinInfo.FullName}</li>
<li className='ticker'>{coin.CoinInfo.Name}</li>
<li>{coin.DISPLAY.USD.PRICE}</li>
<li>{coin.DISPLAY.USD.SUPPLY}</li>
<li>{coin.DISPLAY.USD.MKTCAP}</li>
<li>24HR</li>
<li>{coin.DISPLAY.USD.CHANGEPCT24HOUR}%</li>
</ul>
});
//display the new array of mapped coins data
return (
<div className='purse'>
{coins}
</div>
);
}
}
Ya I would make a component (or a function if that seems more appropriate) to render an individual coin.
You could make a <Coin> component and pass the needed data to it as props:
render() {
return (
<div className='purse'>
{this.state.lists.map((coin, index) => <Coin data={coin} index={index} key={coin.CoinInfo.Id} />)}
</div>
);
}
Or just move the rendering of a coin to a new function:
renderCoin(coin, index) {
return <ul className='coin-containers' key={coin.CoinInfo.Id}>
<li><img className='coinImages' src={`${API_BASE_IMAGE}${coin.CoinInfo.ImageUrl}`} alt={coin.CoinInfo.FullName}/>
</li>
<li>{index + 1}</li>
<li>{coin.CoinInfo.FullName}</li>
<li className='ticker'>{coin.CoinInfo.Name}</li>
<li>{coin.DISPLAY.USD.PRICE}</li>
<li>{coin.DISPLAY.USD.SUPPLY}</li>
<li>{coin.DISPLAY.USD.MKTCAP}</li>
<li>24HR</li>
<li>{coin.DISPLAY.USD.CHANGEPCT24HOUR}%</li>
</ul>;
}
render() {
return (
<div className='purse'>
{this.state.lists.map((coin, index) => this.renderCoin(coin, index))}
</div>
);
}
I'm learning React and don't have a Javascript background. The goal is to build a drop-down menu using values grabbed from an API. Rather than display the actual values, each option in my form displays [object Object] rather than the values from the API.
I can display the entire array or any values from the array anywhere except my forms. I believe the issue is a lack of understanding of how the forms (and perhaps Javascript objects) work to display.
class Test extends React.Component {
constructor() {
super();
this.state = {
randomName: [],
};
}
Here's where I fetch from the API:
componentDidMount() {
fetch('https://randomuser.me/api/?results=10')
.then(results => {
return results.json();
}).then(data => {
let randomName = data.results.map((info => {
return(
<div key={info.results}>
{info.name.first} {info.name.last} - {info.email}
</div>
)
}))
this.setState({randomName: randomName});
console.log("state", this.state.randomName);
})
}
And here's where I'm attempting to create the drop-down (in the same component):
render() {
var test = this.state.randomName[0];
return (
<div>
{test} { /* value prints as expected here */}
<form>
<select>
<option>{test}</option> { /* value prints as [object Object] here */}
<option>first last - none#none.com</option>
<option>first last - none#none.com</option>
<option>first last - none#none.com</option>
</select>
</form>
</div>
)
}
I'm expecting the first option in the drop-down to display as "someFirstName someLastName - someEmail" but it is instead displaying as "[object Object]".
This seems to be specific to forms.
Using JSON.stringify(test) returns what appears to be a string of the entire JSON object. Why does using {test} somewhere in the html deal with that object correctly but not when wrapped in a form option?
Instead of adding div in the state on componentDidMount you can do the following:
componentDidMount() {
fetch('https://randomuser.me/api/?results=10')
.then(results => {
return results.json();
}).then(data => {
let randomName = data.results
this.setState({ randomName });
})
}
and after then in the render method:
renderInfo = () => {
const {randomName} = this.state
return (
{randomName.map((info) => <option>{info.name.first} {info.name.last} - {info.email}</option>)}
)
}
render() {
return (
<div>
<form>
<select>
{this.renderInfo()}
</select>
</form>
</div>
)
}
Hope this may help, thanks
<div id="example"></div>
<script type="text/babel">
let Dropdown = mui.react.Dropdown,
DropdownItem = mui.react.DropdownItem;
class Example extends React.Component {
constructor(props){
super(props);
this.state={
randomName:''
}
}
componentDidMount() {
fetch('https://randomuser.me/api/?results=10')
.then(results => {
return results.json();
}).then(data => {
let randomName = data.results.map((info => {
return(
<DropdownItem key={info.results}>
{info.name.first} {info.name.last} - {info.email}
</DropdownItem>
)
}))
this.setState({randomName: randomName});
console.log("state", this.state.randomName);
})
}
render() {
return (
<div>
<Dropdown color="primary" label="Dropdown">
{this.state.randomName}
</Dropdown>
</div>
);
}
}
ReactDOM.render(<Example />, document.getElementById('example'));
</script>
I have the following react code:
{myArray.map(arr => {
return ( <MyComponent title={arr.ttile} /> )
})}
I would like to call a Loading component while the map() is not completely finished. Is it possible to do that? If yes, how would I do that?
If you are getting your data from an API, you might want to render the data as usual, but you can get the data in the componentDidMount hook instead, and e.g. keep an additional piece of state isLoading which you can use in the render method to decide if you should show a loading component.
Example
function getBooks() {
return new Promise(resolve => {
setTimeout(() => resolve([{ title: "foo" }, { title: "bar" }]), 1000);
});
}
function MyComponent(props) {
return <div> {props.title} </div>;
}
class App extends React.Component {
state = { books: [], isLoading: true };
componentDidMount() {
getBooks().then(books => {
this.setState({ books, isLoading: false });
});
}
render() {
const { isLoading, books } = this.state;
if (isLoading) {
return <div> Loading... </div>;
}
return (
<div>
{this.state.books.map(book => <MyComponent title={book.title} />)}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
If you want to actually be able to see the components being loaded behind/under the loading indicator, then it would be more challenging and would probably need more work than this proposed solution. But if you just want a loading indicator to show while the .map() prototype function is working, I believe this would do the trick:
constructor(props) {
super(props);
this.state = { loadingIndicator : null };
}
getArrayOfMyComponents() {
return myArray.map((arr, index) => {
if (index === 0) {
const loadingIndicator = <Loading/>;
this.setState({ loadingIndicator : loadingIndicator });
} else if (index === myArray.length - 1) {
this.setState({ loadingIndicator : null });
}
return ( <MyComponent title={arr.title} /> );
});
}
render() {
const arrayOfMyComponents = this.getArrayOfMyComponents();
return (
<div>
{this.state.loadingIndicator}
{arrayOfMyComponents}
</div>
);
}
Array.prototype.map() is really just a fancier version of Array.prototype.forEach(). So we can leverage that fact to launch the display of the loading indicator on the first iteration and remove it on the last.
you can have a boolean in a state, and just before you start array map put boolean true and run another code o component render, and then when array maps end you put that state to false, for redux im using state fetch start, fetching, fetched, and then you can take the control of situation
I have a Dashboard component that renders an array of cards with data fetched from a backend server. Users can create additional cards by submitting a form, which then redirects them back to the dashboard page.
My issue is that when the form is submitted, a javascript error 'cannot read property "includes" of undefined' is thrown and the dashboard does not render. If I manually refresh the page, the list renders as expected with the new card. I use Array.includes method to filter the cards based on the filterText state value. Does this error happen because the data has not been fetched when render is called? If so, how can I force the component to wait until there is data before rendering? Please see the components and redux action below.
const CardList = (props) => {
const cards = props.cards.map(({ _id, title}) => {
return (
<Card key={_id} title={title} />
)
});
return (
<div className="container">
<input onChange={ (e) => props.handleChange(e.target.value) } />
<div className="row">
{cards}
</div>
</div>
);
}
export default CardList;
export class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
filterText: ''
}
}
componentDidMount() {
this.props.fetchCards();
}
handleChange = (filterText) => {
this.setState({filterText});
}
render() {
const cardList = this.props.cards.filter(card =>
card.title.includes(this.state.filterText.trim().toLowerCase())
);
return (
<div>
<CardList cards={cardList}
handleChange={filterText => this.handleChange(filterText)} />
</div>
);
}
};
function mapStateToProps({ cards: { cards }}) {
return {
cards,
}
}
export default connect(mapStateToProps, {fetchCards})(Dashboard);
export class SurveyForm extends Component {
render() {
return (
<div>
<form>
<Field component={CardField} type="text"
label={'title'} name={'title'} key={'title'} />
<Button type="submit" onClick={() => submitCard(formValues, history)}>Next</Button>
</form>
</div>
);
}
}
REDUX ACTION DISPATCHER:
export const submitCard = (values, history) => async dispatch => {
const res = await axios.post('/api/cards', values);
try {
dispatch({ type: SUBMIT_CARD_SUCCESS, payload: res.data });
dispatch({ type: FETCH_USER, payload: res.data })
}
catch(err) {
dispatch({ type: SUBMIT_CARD_ERROR, error: err });
}
history.push('/cards');
}
Similar to what #JasonWarta mentioned, it's worth noting that React does not render anything when false, null, or undefined is returned, so you can usually use && to be more succinct than using the conditional ("ternary") operator:
render() {
return this.props.cards && (
<div>
<CardList
cards={this.props.cards.filter(card => card.title.includes(this.state.filterText.trim().toLowerCase())}
handleChange={filterText => this.handleChange(filterText)}
/>
</div>
);
}
Because && short-circuits, the latter part won't be evaluated so you can avoid TypeErrors, and the component will also render no content (same as when you return null).
I've used ternary operators in this kind of situation. You may need to adjust the check portion of the pattern, depending on what your redux pattern is returning. null value is returned if this.props.cards is falsey.
render() {
return (
{this.props.cards
?
<div>
<CardList
cards={this.props.cards.filter(card => card.title.includes(this.state.filterText.trim().toLowerCase())}
handleChange={filterText => this.handleChange(filterText)}
>
</CardList>
</div>
:
null
}
);
}
As an alternative to other answers you can return something else suitable if there is no data in your render function with an if statement. I prefer moving functions like your filter one outside of render. Maybe one other (better?) approach is doing that filter in your mapStateToProps function.
Also, if I'm not wrong you don't need to pass anything to your handleChange function. Because you are getting filterText back from CardList component then setting your state.
cardList = () => this.props.cards.filter(card =>
card.title.includes(this.state.filterText.trim().toLowerCase()));
render() {
if ( !this.props.cards.length ) {
return <p>No cards</p>
// or return <SpinnerComponent />
}
return (
<div>
<CardList cards={this.cardList()}
handleChange={this.handleChange} />
</div>
);
}