componentdidmount - prop-types validation - reactjs

Thank you for your time.
Noob React question... i know componentDidMount will fire after the first render of the component, which is why the prop-types eslint error i am getting is being triggered. However i am not sure how you would fix this and add validation, propTypes validation fires before the getArticles() function therefore if i declare a propTypes it returns undefined... as i.e. articles has not been defined until after the first render and and componentDidMount is called... have had a good dig and cant see a simple solution for what i assume is a common problem??
Question - how do you validate props that have not yet been fetched.
class ArticlesList extends Component {
componentDidMount() {
this.props.getArticles();
}
render() {
return this.props.articles.map(({ id, date, heading, body }) => (
<Article key={id} date={date} heading={heading} body={body} />
));
}
}
const mapStateToProps = (state) => {
return { articles: state.articles };
};
export default connect(mapStateToProps, { getArticles })(ArticlesList);

you would only get an issue for an undefined prop if you had add to your prop validation isRequired like PropTypes.array.isRequired. With that in mind you only need to define correctly your propType:
ArticlesList.propTypes = {
articles: PropTypes.array,
}

Destructure the componentDidMount i.e.
componentDidMount() {
{ getArticles } = this.props;
getArticles();
}
and adding
ArticlesList.propTypes = {
getArticles: PropTypes.func,
articles: PropTypes.array,
};

Related

How to get the DOM node from a Class Component ref with the React.createRef() API

I have these two components:
import { findDOMNode } from 'react-dom';
class Items extends Component {
constructor(props) {
super(props);
this.ref = React.createRef();
this.selectedItemRef = React.createRef();
}
componentDidMount() {
if (this.props.selectedItem) {
this.scrollToItem();
}
}
componentWillReceiveProps(nextProps) {
if (this.props.selectedItem !== nextProps.selectedItem) {
this.scrollToItem();
}
}
scrollToItem() {
const itemsRef = this.ref.current;
const itemRef = findDOMNode(this.selectedItemRef.current);
// Do scroll stuff here
}
render() {
return (
<div ref={this.ref}>
{this.props.items.map((item, index) => {
const itemProps = {
onClick: () => this.props.setSelectedItem(item.id)
};
if (item.id === this.props.selectedItem) {
itemProps.ref = this.selectedItemRef;
}
return <Item {...itemProps} />;
})}
</div>
);
}
}
Items.propTypes = {
items: PropTypes.array,
selectedItem: PropTypes.number,
setSelectedItem: PropTypes.func
};
and
class Item extends Component {
render() {
return (
<div onClick={() => this.props.onClick()}>item</div>
);
}
}
Item.propTypes = {
onClick: PropTypes.func
};
What is the proper way to get the DOM node of this.selectedItemRef in Items::scrollToItem()?
The React docs discourage the use of findDOMNode(), but is there any other way? Should I create the ref in Item instead? If so, how do I access the ref in Items::componentDidMount()?
Thanks
I think what you want is current e.g. this.selectedItemRef.current
It's documented on an example on this page:
https://reactjs.org/docs/refs-and-the-dom.html
And just to be safe I also tried it out on a js fiddle and it works as expected! https://jsfiddle.net/n5u2wwjg/195724/
If you want to get the DOM node for a React Component I think the preferred way of dealing with this is to get the child component to do the heavy lifting. So if you want to call focus on an input inside a component, for example, you’d get the component to set up the ref and call the method on the component, eg
this.myComponentRef.focusInput()
and then the componentRef would have a method called focusInput that then calls focus on the input.
If you don't want to do this then you can hack around using findDOMNode and I suppose that's why it's discouraged!
(Edited because I realized after answering you already knew about current and wanted to know about react components. Super sorry about that!)

Why can't I display array data in React Application?

I am quite new to development with React and I am currently trying to get my head around some basic react and redux things. Unfortunately I am experiencing an issue which I cannot fix on my own.
I have written a mock-api which returns players (profileUrl, username, realname, id). I am dispatching an action which successfully gets me one of those players and I can also pass it to my components props using redux' mapStateToPropsfunction. But I cannot render any of that data in my render function. The react devtools even show me that the single player is getting returned as an array.
The component:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as playerActions from '../../actions/playerActions';
class SinglePlayer extends React.Component {
componentDidMount() {
this.props.actions.loadPlayer(this.props.match.params.playerid);
}
/**
* Render the component.
*/
render() {
return (
<div>
{ this.props.currentPlayer.username }
</div>
)
}
}
/**
* Defines the state which is exposed to this component.
*
* #param { object } reduxStore The entire redux store.
* #param { object } ownProps The properties which belong to the component.
*/
const mapStateToProps = (reduxStore, ownProps) => {
return {
currentPlayer: reduxStore.playerReducer.currentPlayer
}
}
/**
* Defines which actions are exposed to this component.
*
* #param { function } dispatch This function is used to dispatch actions.
*/
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(playerActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SinglePlayer);
React DevTools:
Screenshot of React Devtools props
Redux DevTools:
Screenshot of Redux Devtools data
As you can tell from the image above, the currentPlayer props is inside the playerReducer object.
I have also tried looping over the array like so, with no success either. I just get the error-message stating that .map() is not a function.
this.props.currentPlayer.map(function(player, index) {
return <p>{ player.username }</p>
)}
Error when using .map():
TypeError: this.props.currentPlayer.map is not a function
Can someone tell me what I am doing wrong?
You set your current player by params id at componentDidMount . Your render takes place before that currentPlayer is set hence the error. Add a recheck in your render like below.
render() {
return (
<div>
{
this.props.currentPlayer &&
this.props.currentPlayer.map(function(player, index) {
return <p>{ player.username }</>
)}
}
</div>
)
}
Or
render() {
return (
<div>
{
this.props.currentPlayer ?
this.props.currentPlayer.map(function(player, index) {
return <p>{ player.username }</>
)}
:
null
}
</div>
)
}
Either way it should work. That way this.props.currentPlayer will not be rendered or accessed until its available.
Update
Udate your mapStateToProps to
const mapStateToProps = (state) => {
return {
currentPlayer: state.currentPlayer
}
}
I think from your reduxDev tool, currentPlayer is not under any object.
in first render this.props.currentPlayer is empty!
set empty array "currentPlayer" in state and insert insert this.props.currentPlayer in this.state.currentPlayer and render from state
I managed to solve the issue myself now. The posts here kinda inspired me to try some new things. It was my mock-api which returned the data in a strange and unexpected (at least for me it was unexpected) way.
The dataset:
const players = [
{
profileUrl: 'https://profile.url',
username: 'player1',
realname: 'Max Muster',
steamId: 'player1'
},
{
profileUrl: 'https://profile.url',
username: 'player2',
realname: 'Max Mustermann',
steamId: 'player2'
},
{
profileUrl: 'https://profile.url',
username: 'player3',
realname: 'Maxime Musterfrau',
steamId: 'player3'
},
];
The filtermethod used:
var player = players.filter(function(el) {
return el.steamId === 'player1';
});
If you assume an array of multiple players like above, the shown filtermethod extracts the correct object but still keeps it wrapped in an array. That was producing the mistake...
Thanks a lot for the help guys!

Why is data passed to component is causing TypeError: undefined error? Shows up in React dev tools

I am passing state data to props through mapStateToProps, but it is not recognized in the component.
// user_show_container
// propper import statements....
const mapStateToProps = state => ({
ids: state.entities.users.usersById.leagueIds;
});
const mapDispatchToProps = dispatch => ({
requestTargetUserData: id => dispatch(requestTargetUserData(id)),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserShow);
Action is called in 'componentWillMount()' and the props should be available
class UserShow extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.requestTargetUserData(this.props.match.params.userId)
}
render() {
const { ids } = this.props; // [1, 2, 3]
return (
<div className="">
<ul>
{ids.map(id => <li>id</li>)} // causes error
</ul>
</div>
)
}
}
export default UserShow;
When I remove the map function and do not render with data from props, the Redux dev tools show that the 'id' array is available to the component.
This is not happening with everything that is passed through props. For example swapping this out works. The username is rendered to the page.
...
return (
<div className="">
<h1>{targetUser.username}</h1>
</div>
)
}
}
export default UserShow;
I'm really at a loss here so any guidance would be hugely appreciated. It feels like I am missing something basic, but I haven't been able to find any answers.
Thanks for your time.
Issue is with initialisation :
You should initiate the :
state.entities.users.usersById.leagueIds with [] ,
for the first time and also if the value is not available.
In you case state.entities.users.usersById.leagueIds will be undefined, that's the only reason you are getting error of
Cannot read property of 'map' of undefined
If you don't want to initialise, you can do this also :
const ids = this.props.ids ? this.props.ids : [];
Or Shorter form as #MayankShukla suggested in comment:
const ids = this.props.ids || [];
Is this state.entities.users.usersById.leagueIds returning an array? Because if it returned undefined, that could also be the reason for the error.
const mappStateToProps = state => ({
ids: ["1", "2", "3"]
});
Try setting the above to see if you still see the error. If the error subsides, then it means your ids information is populated after the component is rendered?

Error Retrieving Data from React Redux Store and Mapping to Props

I have a React component which is fetching data via API to retrieve an product Object by using the ID passed in as a prop to the component.
I have a React / Redux app and I am fairly new to Redux flow.
I have the Products (array with one Product object) data loading via my Action / Reducer to the store.
I am trying to pass this from state to props using the mapStateToProps pattern.
I am getting the following error when it is rendering the { this.props.product.title }
Uncaught TypeError: Cannot read property '__reactInternalInstance$z9gkwvwuolc' of null
I think its due to it being data thats asynchronous.
What's the best way to solve for this?
Below is my code --
class ProductListItem extends Component {
componentDidMount() {
this.props.dispatch(fetchProduct(this.props.id));
}
render() {
return (
<div>
<h1>{ this.props.product.title }</h1>
</div>
);
}
}
// Actions required to provide data for this component to render in sever side.
ProductListItem.need = [() => { return fetchProduct(this.props.id); }];
// Retrieve data from store as props
function mapStateToProps(state, props) {
return {
product: state.products.data[0],
};
}
ProductListItem.propTypes = {
id: PropTypes.string.isRequired,
dispatch: PropTypes.func.isRequired,
overlay: PropTypes.string,
};
export default connect(mapStateToProps)(ProductListItem);
You need to check if the product exist, you will access the inner data only if it exist. This a common pattern:
class ProductListItem extends Component {
componentDidMount() {
this.props.dispatch(fetchProduct(this.props.id));
}
render() {
const { product } = this.props;
return (
<div>
{ product &&
<h1>{product.title}</h1>
}
</div>
);
}
}
If the product exist, then the component will render the <h1>.
In your redux reducer you can define the default state, set the default state and then you can do some ternary checking
export default function reducer(state={ title : undefined}, action) {
//ternary checking in render() on component
product.title !== undefined ? product.title : undefined
This means, if product.title isn't undefined, then render product.title else undefined.

Property not found - flowtype / react, using this

Any idea how to get rid of the error propertyunsubscribe: Property not found in FilterLink
class FilterLink extends React.Component { // container component - provides data and behaviour for the used Link presentation component
componentDidMount() {
this.unsubscribe= store.subscribe(()=> this.forceUpdate()); // FLOW ERROR: property `unsubscribe`Property not found in ...
// this is needed because if the parent component does not update when then
// store changes, this component would render a stale value
};
componentWillUnmount() {
this.unsubscribe();
}
render(){
const props = (this.props:{filter:State$VisibilityFilter,children:React$Element<*>});
const state = store.getState();
return (
<Link
active ={props.filter===state.visibilityFilter}
onClick = {()=> store.dispatch (({ type:'SET_VISIBILITY_FILTER', filter: props.filter }:Action$SetVisibilityFilter))}
children= {props.children} />
)
};
};
again, i got help from GreenJello in IRC, thanks !
this is the solution:
unsubscribe:Function; // SOLUTION !!
componentDidMount() {
this.unsubscribe= store.subscribe(()=> this.forceUpdate());
// this is needed because if the parent component does not update when then
// store changes, this component would render a stale value
};
componentWillUnmount() {
this.unsubscribe();
}
this helped too : https://flowtype.org/docs/react.html

Resources