I have a component that does not appear to be firing the componentDidMount event. The component is a parent that is accessed using react-router Link via another component.
here is my list component and the child components:
CoursesPage
import React from 'react';
import CourseList from './CourseList';
import CourseApi from '../../api/courseApi';
import {browserHistory} from 'react-router';
class CoursesPage extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
courses: []
};
this.redirectToAddCoursePage = this.redirectToAddCoursePage.bind(this);
}
componentDidMount(){
CourseApi.getAllCourses().then(coursesData => {
this.setState({ courses: coursesData });
}).catch(error => {
throw(error);
});
}
redirectToAddCoursePage() { browserHistory.push('/course'); }
render() {
const courses = this.state.courses;
return (
<div>
<div className="page-header">
<h3>Courses</h3>
</div>
<input type="submit" value="New Course" className="btn btn-default btn-toolbar pull-right" onClick={this.redirectToAddCoursePage} />
<div className="panel panel-default ">
<div className="panel-heading">
<span> </span>
</div>
<CourseList courses={courses} />
</div>
</div>
);
}
}
export default CoursesPage;
CourseListRow
import React from 'react';
import PropTypes from 'prop-types';
import CourseListRow from './CourseListRow';
const CourseList = ({courses}) => {
return (
<table className="table table-hover">
<thead>
<tr>
<th>Id</th>
<th>Title</th>
<th>Author</th>
<th>Category</th>
<th>Length</th>
</tr>
</thead>
<tbody>
{ courses.map(course => <CourseListRow key={course.CourseId} course={course} /> )}
</tbody>
</table>
);
};
CourseList.propTypes = {
courses: PropTypes.array.isRequired
};
export default CourseList;
CourseListRow
import React from 'react';
import PropTypes from 'prop-types';
import {Link} from 'react-router';
const CourseListRow = ({course}) => {
return (
<tr>
<td><Link to={'/course/' + course.CourseId}>{course.CourseId}</Link></td>
<td>{course.Title}</td>
<td>{course.Author.FirstName + ' ' + course.Author.LastName}</td>
</tr>
);
};
CourseListRow.propTypes = {
course: PropTypes.object.isRequired
};
export default CourseListRow;
My routes
import React from 'react';
import { Route, IndexRoute } from 'react-router';
import App from './components/App';
import CoursesPage from './components/course/CoursesPage';
import ManageCoursePage from './components/course/ManageCoursePage';
export default (
<Route path="/" components={App}>
<IndexRoute component={HomePage} />
<Route path="courses" component={CoursesPage} />
<Route path="course" component={ManageCoursePage} />
<Route path="course/:id" component={ManageCoursePage} />
</Route>
);
All of the above components work fine. However, when I click on the Link for a course in the CourseListRow component to route to the component below, the state for the course object is always empty. I put a debugger statement in the componentDidMount event and it never hits it, so this components CourseForm child component (not shown) never gets the course:
import React from 'react';
import PropTypes from 'prop-types';
import CourseForm from './CourseForm';
import {authorSelectData} from '../../selectors/selectors';
import CourseApi from '../../api/courseApi';
import AuthorApi from '../../api/authorApi';
export class ManageCoursePage extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
course: {},
authors: [],
};
}
componentDidMount() {
let id = this.props.params.id;
if (id) {
CourseApi.getCourse(id).then(courseData => {
this.setState({ course: courseData });
}).catch(error => {
throw(error);
});
}
AuthorApi.getAllAuthors().then(authorsData => {
this.setState({
authors: authorSelectData(authorsData)
});
}).catch(error => {
throw(error);
});
}
render() {
return (
<CourseForm
course={this.state.course}
allAuthors={this.state.authors}
/>
);
}
}
ManageCoursePage.contextTypes = {
router: PropTypes.object
};
export default ManageCoursePage;
For the life of me, I cannot figure why componentDidMount is not firing and populating the course state object. Any help is appreciated
Follow up:
I changed my render method of my parent (ManageCoursePage) component to the following, commenting out the CourseForm child compnonent:
render() {
return (
<h2>Hi {this.state.course.CourseId}</h2>
/*<CourseForm
course={this.state.course}
authors={this.state.authors}
onChange={this.updateCourseState}
onSave={this.saveCourse}
onDelete={this.deleteCourse}
onCancel={this.cancelChange}
errors={this.state.errors}
saving={this.state.saving}
deleting={this.state.deleting}
/>*/
);
}
This worked, I got "Hi 11". It appears for whatever reason my child component is not receiving the props from my parent. Could this be something to do with react router, that I am missing something? This has me really perplexed
I think calling the getCourse in the componentWillReceiveProps and not in ComponentDidMount will solve your issue.
componentWillReceiveProps(nextProps) {
var nextId = nextProps.params.id;
if (nextId !== this.props.params.id) {
CourseApi.getCourse(nextId).then(courseData => {
this.setState({ course: courseData });
}).catch(error => {
throw(error);
});
}
}
Well, I figured it out. This app was initially using redux, which I removed from the app. I thought since I was just learning react, jumping in with redux right away might make me miss how react really works.
Anyway, the problem was one line of code in my child component (not shown in the post). For what ever reason when using redux, I had to convert numbers to strings to get things to work:
value={ course.CourseId.toString() }
This was the line that was erring out.
Uncaught TypeError: Cannot read property 'toString' of undefined
Since the render method runs before componentDidMount and the course object properties were not set yet, tostring was trying to convert an undefined value. It blew up before componentDidMount was called, which is why I never hit it when debugging.
I don't know how I misinterpreted this error, but once I got rid of the toString() it all worked.
I had the same problem.
Here is how it happened and how I solved it.
I have parent component A that holds child component B.
I update A to generate a new child component B' instead of B (but B & B' are of the same type, content is different).
So A would trigger "componentDidMount", B would trigger "componentDidMount" but not B'.
It took me a moment to understand that React actually reuses the component and only changes its content instead of recreating one.
My solution was to add a "unique key" to B & B'
key={`whateverMakesIt_${Unique}`}
As simple as that my component B' start to trigger its call properly.
Related
I tried many things. Same code is working in my other project but not in the current one
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import getProductCategories from '../../redux/actions/productCategoryAction'
import "./ProductCategory.css"
export class ProductCategory extends Component {
static propTypes = {
productCategories: PropTypes.array.isRequired
}
componentDidMount() {
console.log('Mounted!');
this.props.getProductCategories();
}
render() {
return (
<div className="main-card-body">
<div className="main-card-container">
{this.props.productCategories.map((pc, i) => {
return (
<div key={i} className="main-card-card" >
<div className="main-card-face main-card-face1">
<div className="main-card-content">
<img src={pc.image} alt={pc.alt} />
</div>
</div>
<div className="main-card-face main-card-face2">
<div className="main-card-content">
<h3> {pc.title}</h3>
<p>{pc.description}</p>
</div>
</div>
</div>
)
})}
</div>
</div>
)
}
}
const mapStateToProps = state => ({
productCategories: state.productCategory.productCategories
})
const mapDispatchToProps = dispatch => {
return {
getProductCategories: () => {
dispatch(getProductCategories())
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(ProductCategory)
tried without mapDispatchToProps as:
export default connect(mapStateToProps, {getProductCategories})(ProductCategory)
componentDidMount failing without any error, not showing console.log string as well.
Although i crosschecked with each and every means i do have still can't resolve.
enter image description here
Found answer all thanks to Michalis Garganourakis and cubrr
In App.js i was importing this class based component "ProductCategory" with curly braces. importing it without curly braces did the job as i am exporting it as "export default"
Again thanks Michalis Garganourakis and cubrr
Cubrr answered this on the very first go. it took me lot of time to understand this silly thing :D :D
Based on the image you added, the error seems to occur on render function, so the componentDidMount never gets triggered for this exact reason.
Try checking if this.props.productCategories exists before trying to use .map() on it. This should allow render function to run succesfully, and this will then trigger the componentDidMount as per react's lifecycle method order
this.props.productCategories && this.props.productCategories.map((pc, i) ...
Also, try removing the export on your first row, keeping just the export default of your last row where you also make use of connect HOC, like:
class ProductCategory extends Component {
// ...
}
export default connect(mapStateToProps, mapDispatchToProps)(ProductCategory)
In my react app when I make a serverside update I return a response which I use to update the state of the parent component. But for my components where I use react-responsive-tabs they don't get updated.
Here's my react code:
import React, {Component, Fragment} from 'react';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import PageTitle from '../../../Layout/AppMain/PageTitle';
import {
faAngleUp,
faAngleDown,
faCommentDots,
faBullhorn,
faBusinessTime,
faCog
} from '#fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '#fortawesome/react-fontawesome';
import Tabs from 'react-responsive-tabs';
import Roles from './Roles';
import Priviledges from './Priviledges';
export default class Apage extends Component {
constructor(props) {
super(props);
this.state = {
api: this.props.api,
session: this.props.session
}
}
componentWillMount() {
this.tabsContent = [
{
title: 'Roles',
content: <Roles api={this.state.api} session={this.state.session} />
},
{
title: 'Priviledges',
content: <Priviledges api={this.state.api} session={this.state.session} />
}
];
}
getTabs() {
return this.tabsContent.map((tab, index) => ({
title: tab.title,
getContent: () => tab.content,
key: index,
}));
}
onTabChange = selectedTabKey => {
this.setState({ selectedTabKey });
};
render() {
return (
<Fragment>
<PageTitle
heading="Roles & Priviledges"
subheading=""
icon="lnr-apartment icon-gradient bg-mean-fruit"
/>
<Tabs selectedTabKey={this.state.selectedTabKey} onChange={this.onTabChange} tabsWrapperClass="body-tabs body-tabs-layout" transform={false} showInkBar={true} items={this.getTabs()}/>
</Fragment>
)
}
}
I have tried using this within my <Roles /> tag:
shouldComponentUpdate(nextProps, nextState) {
return nextProps.session!= this.props.session;
}
but I couldn't get it to work for me. Any clue?
I'm running my React JS within laravel using laravel-mix. I actually intend to update a dropdown whenever I submit a form using setState. I've done this many other times when I use React JSas a REST API.
I ended up using socket IO to trigger a setSate within my component after a response comes from the server. Although i'd prefer something neater.
You need to onChange like this - onChange={() => this.onTabChange()}
see below-
<Tabs onChange={() => this.onTabChange()} selectedTabKey={this.state.selectedTabKey} tabsWrapperClass="body-tabs body-tabs-layout" transform={false} showInkBar={true} items={this.getTabs()}/>
New to React, in the following code I am passing data between two components via the parent. Flow is from Search to the parent App then to another child Sidebar. I am able to send to both from Search to App and App to Sidebar individually but for some reason setState is not behaving as expected making the link to trigger a refresh of <Search updateMenu={this.handleSearchResult} /> as you can see in the console.log code comments below:
import React, { Component } from 'react';
import './App.css';
import Search from './Search';
import Sidebar from './Sidebar';
class App extends Component {
constructor() {
super()
this.state = {
menu: []
}
}
handleSearchResult = (array) => {
// always the correct value
console.log('in ', array);
this.setState( {menu: menuList})
// 1st call : empty
// 2nd call : previous value not showing on 1st call + new value as expected
// does not trigger <Sidebar list={this.state.menu}/>
console.log('out', this.state.menu);
}
render() {
return (
<div className="App">
// not refreshing
<Search updateMenu={this.handleSearchResult} />
<Sidebar list={this.state.menu}/>
</div>
);
}
}
export default App;
Logging this.setState(). Is not so straight forward. this.setState() is asynchronus.
Here is a reference on Medium.
I'm new to Redux and React. I'm trying to implement sorting for a react component, but as far as I know you're not allowed to manipulate the props in Redux. What's the correct way of doing that in redux-react
Here's my component
import React, { Component, PropTypes } from 'react';
import _ from 'lodash';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as ArticlesActions from '../../actions/ArticlesActions';
import { ArticleList } from '../../components';
class ArticleListApp extends Component {
static propTypes = {
articles: PropTypes.array.isRequired,
next: PropTypes.string.isRequired,
actions: PropTypes.object.isRequired
};
componentWillMount() {
this.props.actions.fetchArticles('57bde6d6e4b0dc55a4f018e0');
}
renderLoadMoreButton() {
if (!_.isEmpty(this.props.next)) {
return (
<button type="button" className="btn btn-link center-block" onClick={this.props.actions.fetchMoreArticles.bind(this, this.props.next)}>Load More</button>
);
}
return '';
}
render () {
return (
<div>
<ArticleList articles={this.props.articles}/>
<div className="row">
<div className="col-md-12 center-block">
{this.renderLoadMoreButton()}
</div>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
articles: state.articleList.articleList,
next: state.articleList.next
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(ArticlesActions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ArticleListApp);
Once I get articles I pass that to ArticleList component which I think it's a dump component in redux-react
import React, { Component, PropTypes } from 'react';
export default class ArticleList extends Component {
static propTypes = {
articles: PropTypes.array.isRequired
}
constructor(props, context) {
super(props, context);
}
renderList() {
return this.props.articles.map((article) =>
<tr>
<td>{article.title}</td>
<td>Author</td>
<td>{article.words}</td>
<td>ago</td>
</tr>
);
}
sortArticles() {
this.props.articles = this.props.articles.sort(); // I'm not allowed to do this.
}
render() {
return (
<table className="table table-hover">
<thead>
<tr>
<th>UNPUBLISHED ARTICLES ({this.props.articles.length})</th>
<th>AUTHOR</th>
<th>WORDS</th>
<th onClick={this.sortArticles.bind(this)}>SUBMITTED</th>
</tr>
</thead>
<tbody>
{this.renderList()}
</tbody>
</table>
);
}
}
In this component, I'd like to have a sort feature where a user can click on the column and sort the whole table by that column.
The example of articles object would be
[{title: 'title', word: 10}, {title: 'title', word: 5}]
If I understand correctly, then there are two ways to go about this:
Articles should be kept in the reducer, sorted or not sorted, depending on the user interaction. Technically this is done by having the "sort" action fired (by the container) and then a reducer handling that event by changing its state (returning a new state that is the previous array but sorted). Regardless of how this is done, this introduces a problem: any component subscribed to the articles reducer will always get a sorted list. Is this what you want? Maybe the "sorted" display is part of the state of the ui component and is specific to that component so....
Have the reducer keep the unsorted array and have two different components (or a component with an additional prop stating if its "sorted" or not) and during the render functions of this component, either sort or don't sort, according to the prop given by the components container. Technically this is done by having the container component have a state (sorted or not sorted) that is passed as a prop to the view component.
If you are new to react/redux and what I said makes little sense to you, I don't mind adding code - but I think your question is about the principle and not about the actual code.
so the Redux flow is component will invoke an action that the reducer will listen to and thereby updating the state. Any container (component that is wrapped in a connect function) listening to that state will re-render itself with the new state.
So what you want to do here is have a listener that listens to the onClick event of the column in your ArticleList component (dumb component), which fires a function to the parent component, the ArticleListApp (container, or smart component). That will result in the container firing off an action, say props.sortByColumn('AUTHOR', 'asc') for example. That action should be defined in your actions file and will basically be something like this
function sortByColumn(columnName, order) {
return {
type: 'SORT_BY_COLUMN',
columnName: columnName,
order: order
}
}
The Reducer will be listening to that action and will basically update your store with the articles to be listed in the new order.
when that update occurs, the container listening to that state will re-render itself with the new state. You can put click handlers on every column you want to call the parent with the column it wants to sort by.
Your Components will look something like this:
import React, { Component, PropTypes } from 'react';
import _ from 'lodash';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as ArticlesActions from '../../actions/ArticlesActions';
import { ArticleList } from '../../components';
class ArticleListApp extends Component {
static propTypes = {
articles: PropTypes.array.isRequired,
next: PropTypes.string.isRequired,
actions: PropTypes.object.isRequired
};
componentWillMount() {
this.props.actions.fetchArticles('57bde6d6e4b0dc55a4f018e0');
}
renderLoadMoreButton() {
if (!_.isEmpty(this.props.next)) {
return (
<button type="button" className="btn btn-link center-block" onClick={this.props.actions.fetchMoreArticles.bind(this, this.props.next)}>Load More</button>
);
}
return '';
}
render () {
return (
<div>
<ArticleList articles={this.props.articles}
handleSortByCol={this.props.sortByColumn(columnName, sortOrder)} />
<div className="row">
<div className="col-md-12 center-block">
{this.renderLoadMoreButton()}
</div>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
articles: state.articleList.articleList,
next: state.articleList.next
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(ArticlesActions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ArticleListApp);
And your other dumb component will be:
import React, { Component, PropTypes } from 'react';
export default class ArticleList extends Component {
static propTypes = {
articles: PropTypes.array.isRequired
}
constructor(props, context) {
super(props, context);
}
renderList() {
return this.props.articles.map((article) =>
<tr>
<td>{article.title}</td>
<td>Author</td>
<td>{article.words}</td>
<td>ago</td>
</tr>
);
}
sortArticles() {
this.props.handleSortByCol('author', 'asc');
}
render() {
return (
<table className="table table-hover">
<thead>
<tr>
<th>UNPUBLISHED ARTICLES ({this.props.articles.length})</th>
<th>AUTHOR</th>
<th>WORDS</th>
<th onClick={this.sortArticles.bind(this)}>SUBMITTED</th>
</tr>
</thead>
<tbody>
{this.renderList()}
</tbody>
</table>
);
}
I'm working on my first React/Redux project and I have a little question. I've read the documentation and watched the tutorials available at https://egghead.io/lessons/javascript-redux-generating-containers-with-connect-from-react-redux-visibletodolist.
But I still have one question. It's about a login page.
So I have a presentational component named LoginForm :
components/LoginForm.js
import { Component, PropTypes } from 'react'
class LoginForm extends Component {
render () {
return (
<div>
<form action="#" onSubmitLogin={(e) => this.handleSubmit(e)}>
<input type="text" ref={node => { this.login = node }} />
<input type="password" ref={node => { this.password = node }} />
<input type="submit" value="Login" />
</form>
</div>
)
}
handleSubmit(e) {
e.preventDefault();
this.props.onSubmitLogin(this.login.value, this.password.value);
}
}
LoginForm.propTypes = {
onSubmitLogin: PropTypes.func.isRequired
};
export default LoginForm;
And a container component named Login which pass data to my component. Using react-redux-router, I call this container (and not the presentationnal component) :
containers/Login.js
import { connect } from 'react-redux'
import { login } from '../actions/creators/userActionCreators'
import LoginForm from '../components/LoginForm'
const mapDispatchToProps = (dispatch) => {
return {
onSubmitLogin: (id, pass) => dispatch(login(id, pass))
}
};
export default connect(null, mapDispatchToProps)(LoginForm);
As you can see, I'm using the connect method provide by redux to create my container.
My question is the following one :
If I want my Login container to use multiple views (for example : LoginForm and errorList to display errors), I need to do it by hand (without connect because connect take only one argument). Something like :
class Login extends Component {
render() {
return (
<div>
<errorList />
<LoginForm onSubmitLogin={ (id, pass) => dispatch(login(id, pass)) } />
</div>
)
}
}
Is it a bad practice ? Is it better to create another presentational component (LoginPage) which use both errorList and LoginForm and create a container (Login) which connect to LoginPage ?
EDIT: If I create a third presentational component (LoginPage), I'll have to pass data twice. Like this : Container -> LoginPage -> LoginForm & ErrorList.
Even with context, it don't seems to be the way to go.
I think that what you have in your second example is very close. You can create just one container component that's connected and render multiple presentational components.
In your first example, there actually isn't a separate container component:
import { connect } from 'react-redux'
import { login } from '../actions/creators/userActionCreators'
import LoginForm from '../components/LoginForm'
const mapDispatchToProps = (dispatch) => {
return {
onSubmitLogin: (id, pass) => dispatch(login(id, pass))
}
};
// `LoginForm` is being passed, so it would be the "container"
// component in this scenario
export default connect(null, mapDispatchToProps)(LoginForm);
Even though it's in a separate module, what you're doing here is connecting your LoginForm directly.
Instead, what you can do is something like this:
containers/Login.js
import { connect } from 'react-redux'
import { login } from '../actions/creators/userActionCreators'
import LoginForm from '../components/LoginForm'
import ErrorList from '../components/ErrorList'
class Login extends Component {
render() {
const { onSubmitLogin, errors } = this.props;
return (
<div>
<ErrorList errors={errors} />
<LoginForm onSubmitLogin={onSubmitLogin} />
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onSubmitLogin: (id, pass) => dispatch(login(id, pass))
}
};
const mapStateToProps = (state) => {
return {
errors: state.errors
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Note that the Login component is now being passed to connect, making it the "container" component and then both the errorList and LoginForm can be presentational. All of their data can be passed via props by the Login container.
I truly believe that you need to build all your components as Presentational Components. At the moment you need a Container Component, you might use {connect} over one of the existing Presentational and convert into a Container one.
But, that is only my view with short experience in React so far.