I am making a file sharing web app using a MERN development stack.
While connecting my file sharing screen to my updating screen, I got stuck.
ComponentsWillReceiveProps() is not working in the current version of React. I tried to find an alternative and it showed either set
UNSAFE_ComponentsWillReceiveProps() or use the function static getDerivedStateFromProps(nextProps, prevState), but I don't know how to define prevState.
My code is:
import React,{Component} from 'react'
import _ from "lodash"
import PropTypes from 'prop-types'
class HomeUploading extends Component{
constructor(props) {
super(props);
this.state = {
data: null,
event: null,
loaded: 0,
total: 0,
percentage: 10,
}
}
componentDidMount() {
const {data} = this.props;
this.setState({
data: data
});
}
static getDerivedStateFromProps(nextProps,prevState){
const {event} = nextProps;
console.log("Getting an event of uploading", event,prevState);
switch (_.get(event, 'type')) {
case 'onUploadProgress' :
const loaded = _.get(event, 'payload.loaded', 0);
const total = _.get(event, 'payload.total', 0);
const percentage = (total !== 0) ? ((loaded / total) * 100) : 0;
this.setState ({
loaded: loaded,
total: total,
percentage: percentage
});
break;
default:
break;
}
this.setState({
event:event,
}) ;
}
render() {
const {percentage, data, total, loaded} = this.state;
const totalFiles = _.get(data, 'files', []).length;
return (
<div className={'app-card app-card-uploading'}>
<div className={'app-card-content'}>
<div className={'app-card-content-inner'}>
<div className={'app-home-uploading'}>
<div className={'app-home-uploading-icon'}>
<i className={'icon-cloud-upload'}/>
<h2>Sending...</h2>
</div>
<div className={'app-upload-files-total'}>Uploading {totalFiles} files</div>
<div className={'app-progress'}>
<span style={{width: `${percentage}%`}} className={'app-progress-bar'}/>
</div>
<div className={'app-upload-stats'}>
<div className={'app-upload-stats-left'}>{loaded}Bytes/{total}Bytes</div>
<div className={'app-upload-stats-right'}>456K/s</div>
</div>
<div className={'app-form-actions'}>
<button className={'app-upload-cancel-button app-button'} type={'button'}>Cancel
</button>
</div>
</div>
</div>
</div>
</div>
)
}
}
HomeUploading.propTypes={
data: PropTypes.object,
event: PropTypes.object
}
export default HomeUploading;
Just glancing at the code, I'd refactor this a bit to keep this component as simple as possible (dumb/presentational component) that just displays new props as they're fed in.
Here's a description :https://medium.com/#thejasonfile/dumb-components-and-smart-components-e7b33a698d43
I'd suggest lifting the state up a level and doing the data transformation in the parent.
Something like this for the HomeUploading:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
const HomeUploading = ({
data,
event,
loaded,
total,
percentage,
}) => (
<div className={'app-card app-card-uploading'}>
<div className={'app-card-content'}>
<div className={'app-card-content-inner'}>
<div className={'app-home-uploading'}>
<div className={'app-home-uploading-icon'}>
<i className={'icon-cloud-upload'} />
<h2>Sending...</h2>
</div>
<div className={'app-upload-files-total'}>Uploading {data.length} files</div>
<div className={'app-progress'}>
<span style={{ width: `${percentage}%` }} className={'app-progress-bar'} />
</div>
<div className={'app-upload-stats'}>
<div className={'app-upload-stats-left'}>
{loaded}Bytes/{total}Bytes
</div>
<div className={'app-upload-stats-right'}>456K/s</div>
</div>
<div className={'app-form-actions'}>
<button className={'app-upload-cancel-button app-button'} type={'button'}>
Cancel
</button>
</div>
</div>
</div>
</div>
</div>
);
HomeUploading.propTypes = {
data: PropTypes.object,
event: PropTypes.object,
};
export default HomeUploading;
And then in the parent, you could have a the logic for transforming the data in a function:
import React, { Component } from 'react';
class Parent extends Component {
state = {
data: null,
event: null,
loaded: 0,
total: 0,
percentage: 0,
}
calcPercentage(loaded, total) {
return (total !== 0) ? ((loaded / total) * 100) : 0
}
render() {
const {
data,
event,
loaded,
total
} from this.state;
return (
<HomeUploading
data={data}
event={event}
loaded={loaded}
total={total}
percentage={this.calcPercentage(loaded, total)}
/>
);
}
}
export default Parent;
This approach will give you the props updating without relying on componentWillReceiveProps.
Related
Hi i want to create a dropDown in react with each item having an icon. As i tried react-select but its not showing the icon ,and also the selected value .When i remove value prop from react-select component than the label is showing. I want to create the dropdown like the this.
//USerInfo.js
import React from "react";
import { connect } from "react-redux";
import FontAwesome from "react-fontawesome";
import Select from "react-select";
import { setPresence } from "../../ducks/user";
import "./UserInfo.css";
class UserInfo extends React.Component {
// constructor(props) {
// super(props);
// this.state = {
// currentPresence: "available",
// };
// }
presenceOpts = [
{ value: "available", label: "Available" },
{ value: "dnd", label: "Busy" },
{ value: "away", label: "Away" },
];
setPresenceFun(presence) {
this.props.setPresence(presence);
}
renderPresenceOption(option) {
return (
<div className="presenceOption">
<FontAwesome name="circle" className={"presenceIcon " + option.value} />
{option.label}
</div>
);
}
renderPresenceValue(presence) {
return (
<div className="currentPresence">
<FontAwesome
name="circle"
className={"presenceIcon " + presence.value}
/>
</div>
);
}
render() {
return (
<div className="UserInfo">
{this.props.client.authenticated && (
<div className="authenticated">
<div className="user">
<div className="presenceControl">
<Select
name="presence"
value={this.props.user.presence.value}
options={this.presenceOpts}
onChange={this.setPresenceFun.bind(this)}
clearable={false}
optionRenderer={this.renderPresenceOption}
valueRenderer={this.renderPresenceValue}
/>
</div>
<div className="username">
<p>{this.props.client.jid.local}</p>
</div>
</div>
</div>
)}
</div>
);
}
}
const mapStateToProps = (state, props) => ({
client: state.client,
user: state.user,
});
const mapDispatchToProps = (dispatch, props) => {
return {
setPresence: (presence) => dispatch(setPresence(presence)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserInfo);
You can customize the option for dropdown
This may assist you in resolving the custom styling issue with react select.
https://codesandbox.io/s/react-select-add-custom-option-forked-u1iee?file=/src/index.js
I am trying to implement pagination in my react application using this guide I created the Pagination.js file as instructed in the guide, but I am not able to see that on my UI, here is the screenshot of the application
Here is my Search Results Page where I am implementing pagination, basically this will show the results fetched from the server based on user entered keyword and hence I want to show as paginated results. My js file corresponding to the above screenshot is:
import React from 'react';
import NavigationBar from './NavigationBar';
import SearchPageResultsStyle from "../assets/css/SearchResultsPage.css"
import Pagination from './Pagination';
class SearchResultsPage extends React.Component{
constructor(props) {
super(props);
console.log("Printing in the results component: this.props.location.state.data.keyword")
console.log(this.props.location.state.data.keyword)
this.state = {
results: this.props.location.state.data.results,
keyword: this.props.location.state.data.keyword,
pageOfItems: []
};
this.onChangePage = this.onChangePage.bind(this);
}
onChangePage(pageOfItems) {
// update local state with new page of items
this.setState({pageOfItems});
}
render() {
return(
<div>
<NavigationBar/>
<h4 style={{textAlign:'center', color:'#1a0dab'}}>Showing search results for <span style={{fontWeight:'bold', fontStyle:'Italic'}}>'{this.state.keyword}'</span></h4>
<hr/>
<div className={'wrap'} style={SearchPageResultsStyle}>
<div className={'fleft'}>left column</div>
<div className={'fcenter'}>
<h3 style={{color:'#1a0dab'}}>Tweeter tweets text will be displayed here!!!</h3>
<a href={'https://google.com'}>Tweet urls will be displayed here</a>
<br/>
<div style={{display:'inline'}}>
<p><span style={{fontWeight:'bold', textColor:'#6a6a6a'}}>topic: </span>crime</p>
<p><span style={{fontWeight:'bold', textColor:'#6a6a6a'}}>city: </span>delhi</p>
<p><span style={{fontWeight:'bold', textColor:'#6a6a6a'}}>lang: </span>Hindi</p>
<p><span style={{fontWeight:'bold', textColor:'#6a6a6a'}}>Hashtags: </span></p>
<hr/>
<Pagination items={this.state.results} onChangePage={this.onChangePage}/>
</div>
</div>
<div className={'fright'}>right column</div>
</div>
</div>
)
}
}
export default SearchResultsPage;
My pagination.js file
import React from 'react';
import PropTypes from 'prop-types';
const propTypes = {
items: PropTypes.array.isRequired,
onChangePage: PropTypes.func.isRequired,
initialPage: PropTypes.number,
pageSize: PropTypes.number
};
const defaultProps = {
initialPage: 1,
pageSize: 10
};
class Pagination extends React.Component{
constructor(props){
super(props);
this.state = {
pager: {}
};
// set page if items array isn't empty
if (this.props.items && this.props.items.length) {
this.setPage(this.props.initialPage);
}
}
componentDidUpdate(prevProps, prevState) {
// reset page if items array has changed
if (this.props.items !== prevProps.items) {
this.setPage(this.props.initialPage);
}
}
setPage(page) {
var { items, pageSize } = this.props;
var pager = this.state.pager;
if (page < 1 || page > pager.totalPages) {
return;
}
// get new pager object for specified page
pager = this.getPager(items.length, page, pageSize);
// get new page of items from items array
var pageOfItems = items.slice(pager.startIndex, pager.endIndex + 1);
// update state
this.setState({ pager: pager });
// call change page function in parent component
this.props.onChangePage(pageOfItems);
}
getPager(totalItems, currentPage, pageSize) {
// default to first page
currentPage = currentPage || 1;
// default page size is 10
pageSize = pageSize || 10;
// calculate total pages
var totalPages = Math.ceil(totalItems / pageSize);
var startPage, endPage;
if (totalPages <= 10) {
// less than 10 total pages so show all
startPage = 1;
endPage = totalPages;
} else {
// more than 10 total pages so calculate start and end pages
if (currentPage <= 6) {
startPage = 1;
endPage = 10;
} else if (currentPage + 4 >= totalPages) {
startPage = totalPages - 9;
endPage = totalPages;
} else {
startPage = currentPage - 5;
endPage = currentPage + 4;
}
}
// calculate start and end item indexes
var startIndex = (currentPage - 1) * pageSize;
var endIndex = Math.min(startIndex + pageSize - 1, totalItems - 1);
// create an array of pages to ng-repeat in the pager control
var pages = [...Array((endPage + 1) - startPage).keys()].map(i => startPage + i);
// return object with all pager properties required by the view
return {
totalItems: totalItems,
currentPage: currentPage,
pageSize: pageSize,
totalPages: totalPages,
startPage: startPage,
endPage: endPage,
startIndex: startIndex,
endIndex: endIndex,
pages: pages
};
}
render() {
var pager = this.state.pager;
if (!pager.pages || pager.pages.length <= 1) {
// don't display pager if there is only 1 page
return null;
}
return (
<div>
<ul className="pagination">
<li className={pager.currentPage === 1 ? 'disabled' : ''}>
<button onClick={() => this.setPage(1)}>First</button>
</li>
<li className={pager.currentPage === 1 ? 'disabled' : ''}>
<button onClick={() => this.setPage(pager.currentPage - 1)}>Previous</button>
</li>
{pager.pages.map((page, index) =>
<li key={index} className={pager.currentPage === page ? 'active' : ''}>
<button onClick={() => this.setPage(page)}>{page}</button>
</li>
)}
<li className={pager.currentPage === pager.totalPages ? 'disabled' : ''}>
<button onClick={() => this.setPage(pager.currentPage + 1)}>Next</button>
</li>
<li className={pager.currentPage === pager.totalPages ? 'disabled' : ''}>
<button onClick={() => this.setPage(pager.totalPages)}>Last</button>
</li>
</ul>
</div>
);
}
}
Pagination.propTypes = propTypes;
Pagination.defaultProps = defaultProps;
export default Pagination;
I do not understand that why my list of items in Pagination.js file is not getting rendered.
Can anybody point out what exactly is it that I am missing?
Your issue is a misplaced if statement in the constructor. So this:
class Pagination extends React.Component{
constructor(props){
super(props);
this.state = {
pager: {}
};
// set page if items array isn't empty
if (this.props.items && this.props.items.length) {
this.setPage(this.props.initialPage);
}
}
Should be:
class Pagination extends React.Component{
constructor(props){
super(props);
this.state = {
pager: {}
}
}
componentWillMount() {
if (this.props.items && this.props.items.length) {
this.setPage(this.props.initialPage);
}
}
This is how I implemented the above library, it's really cool library. I have to add css to it, to make it look good. It is working for me now.
import React from 'react';
import NavigationBar from './NavigationBar';
import SearchPageResultsStyle from "../assets/css/SearchResultsPage.css"
import Pagination from './Pagination';
class SearchResultsPage extends React.Component{
constructor(props) {
super(props);
console.log("Printing in the results component: this.props.location.state.data.results")
console.log(this.props.location.state.data.results)
this.state = {
results: this.props.location.state.data.results,
keyword: this.props.location.state.data.keyword,
pageOfItems: []
};
this.onChangePage = this.onChangePage.bind(this);
}
onChangePage(pageOfItems) {
// update local state with new page of items
this.setState({pageOfItems});
}
render() {
const renderItems = this.state.pageOfItems.map((item, index) => {
return (
<div>
<h3 style={{color: '#1a0dab'}} key={index}>{item.text}</h3>
<a href={'https://google.com'} key={index}>{item.tweetUrl}</a>
<br/>
<p><span style={{fontWeight:'bold', textColor:'#6a6a6a'}} key={index}>topic: </span>{item.topic}</p>
<p><span style={{fontWeight:'bold', textColor:'#6a6a6a'}} key={index}>city: </span>{item.city}</p>
<p><span style={{fontWeight:'bold', textColor:'#6a6a6a'}} key={index}>lang: </span>{item.lang}</p>
<p><span style={{fontWeight:'bold', textColor:'#6a6a6a'}} key={index}>Hashtags: </span></p>
<hr/>
</div>
)
})
return (
<div>
<NavigationBar/>
<h4 style={{textAlign:'center', color:'#1a0dab'}}>Showing search results for <span style={{fontWeight:'bold', fontStyle:'Italic'}}>'{this.state.keyword}'</span></h4>
<hr/>
<div className={'wrap'} style={SearchPageResultsStyle}>
<div className={'fleft'}>left column</div>
<div className={'fcenter'}>
{renderItems}
<Pagination items={this.state.results} onChangePage={this.onChangePage}/>
</div>
</div>
<div className={'fright'}></div>
</div>
)
}
}
export default SearchResultsPage;
I'm still new to React.
I'm making a guessing game. On page load, everything loads properly (on Chrome and Safari, at least). The cat buttons are assigned a random number and when clicked, they send the corresponding value to the game logic. When the target number is met or exceeded, the target number resets.
That's what I want, but I also want the buttons to get new values. I want the Buttons component to reload and assign the buttons new values. I've tried using the updating methods found here: https://reactjs.org/docs/react-component.html#updating. I don't know what to do next.
App.js
import React, { Component } from 'react';
import './App.css';
import Buttons from "./components/Buttons/Buttons";
class App extends Component {
targetNumber = (min, max) => {
const targetNum = Math.floor(Math.random()*(max-min+1)+min);
console.log(`Target number = ${targetNum}`);
return targetNum
};
state = {
targetNumber: this.targetNumber(19, 120),
currentValue: 0,
gamesWon: 0,
};
handleClick = (event) => {
event.preventDefault();
const currentValue = this.state.currentValue;
const newValue = parseInt(event.target.getAttribute("value"));
this.setState(
{currentValue: currentValue + newValue}
)
// console.log(newValue);
}
componentDidUpdate() {
if (this.state.currentValue === this.state.targetNumber) {
this.setState(
{
targetNumber: this.targetNumber(19, 120),
currentValue: 0,
gamesWon: this.state.gamesWon + 1
}
)
}
else {
if (this.state.currentValue >= this.state.targetNumber) {
this.setState(
{
targetNumber: this.targetNumber(19, 120),
currentValue: 0,
gamesWon: this.state.gamesWon,
}
);
}
}
}
render() {
return (
<div className="App">
<img src={require("./images/frame.png")} alt="frame" id="instructFrame" />
<div className="resultsDiv">
<div className="targetNumber">
Target number = {this.state.targetNumber}
</div>
<div className="currentValue">
Current value = {this.state.currentValue}
</div>
<div className="gamesWon">
Games won = {this.state.gamesWon}
</div>
</div>
<div className="buttonGrid">
<Buttons
onClick={this.handleClick}
/>
</div>
</div>
);
}
}
export default App;
Buttons.js
import React, { Component } from "react";
import Button from "../Button/Button";
import black from "../Button/images/black_cat.png";
import brown from "../Button/images/brown_cat.png";
import gray from "../Button/images/gray_cat.png";
import yellow from "../Button/images/yellow_cat.png";
class Buttons extends Component {
generateNumber = (min, max) => {
const rndNumBtn = Math.floor(Math.random()*(max-min+1)+min);
console.log(rndNumBtn);
return rndNumBtn
};
state = {
buttons: [
{
id: "black",
src: black,
alt: "blackBtn",
value: this.generateNumber(1, 12)
},
{
id: "brown",
src: brown,
alt: "brownBtn",
value: this.generateNumber(1, 12)
},
{
id: "gray",
src: gray,
alt: "grayBtn",
value: this.generateNumber(1, 12)
},
{
id: "yellow",
src: yellow,
alt: "yellowBtn",
value: this.generateNumber(1, 12)
}
]
};
render() {
return (
<div>
{this.state.buttons.map(button => {
return (
<Button
className={button.id}
key={button.id}
src={button.src}
alt={button.alt}
value={button.value}
onClick={this.props.onClick.bind(this)}
/>
)
})}
</div>
)
}
}
export default Buttons;
Here's the GitHub repo. https://github.com/irene-rojas/numberguess-react
You can add a key to the Button component linking to the variable targetNumber. That way, React would rerender the Button whenever targetNumber changes.
<div className="buttonGrid">
<Buttons
key={this.state.targetNumber}
onClick={this.handleClick}
/>
</div>
I am trying to use this package to use scroll lock on a react component.
When I run a build I get the following error:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in.
I'm importing the package as suggested, I've checked the source file of the package to make sure it's not named something else and as it is exported:
exports.default = ScrollLock;
How can I resolve this?
Here is my component code:
import React from 'react';
import PropTypes from 'prop-types';
import ReactSVG from 'react-svg';
import Slider from 'react-slick';
import ScrollLock from 'react-scroll-lock-component';
import MobileSVG from '../../../assets/svg/icons/Mobile_Icon_Option2.svg';
import TabletSVG from '../../../assets/svg/icons/Tablet_Icon_Option2.svg';
import DesktopSVG from '../../../assets/svg/icons/Desktop_Icon_Option2.svg';
import styles from './styles.css';
const deviceIcons = {'mobile': MobileSVG, 'tablet': TabletSVG, 'desktop': DesktopSVG};
import BackToTopButton from '../BackToTopButton';
export default class ProductComponent extends React.Component {
constructor(props) {
super(props);
this.scroll = this.scroll.bind(this);
}
scroll(y){
y > 0 ? (
this.slider.slickNext()
) : (
this.slider.slickPrev()
)
}
componentWillMount(){
window.addEventListener('wheel', function(e){
//this.scroll(e.wheelDelta);
})
}
render() {
const {productData} = this.props
//Slider settings
const settings = {
dots: true,
infinite: false,
speed: 500,
fade: true,
arrows: false,
centerMode: true,
slidesToShow: 1,
slidesToScroll: 1
}
//Slider items
const sliderItems = productData.map((obj, i) => {
return (
<div className="product-component row" key={i}>
<div className="product-component-image-wrap col-xs-12 col-sm-8">
<span className="product-heading">{obj.category}</span>
<div className="product-detail-wrap">
<img className="product-component-image" src={`${process.env.DB_URL}${obj.image}`} />
<ul className="list-device-support">
{obj.categoryDeviceSupport.map((obj, i) => {
return (<li key={i}>
<span className="svg-icon">
<ReactSVG path={deviceIcons[obj.value]} />
</span>
<span className="product-label">{obj.label}</span>
</li>)
})}
</ul>
</div>
</div>
<div className="product-component-info col-xs-12 col-sm-3">
<span className="align-bottom">{obj.title}</span>
<p className="align-bottom">{obj.categoryBrief}</p>
</div>
</div>
)
});
return (
<div className="product-component-wrap col-xs-12">
<ScrollLock>
<Slider {...settings} ref={slider => this.slider = slider}>
{sliderItems}
</Slider>
<BackToTopButton scrollStepInPx="50" delayInMs="7" />
</ScrollLock>
</div>
)
}
}
ProductComponent.propTypes = {
productData: PropTypes.array
};
ProductComponent.defaultProps = {
productData: []
};
Using the example of initializingFromState within Redux-Form, I am trying to set this up dynamically. This is to edit a particular book in a list of books, and is using a simple api set up in express.js.
The full container is below. I somehow need to pass in initialValues, within the mapStateToProps function. In the example, it is done via a static object, but I can't work out how to use the information I have pulled in via fetchBook, and pass it to initialValues.
Container:
import React, { Component, PropTypes } from 'react';
import { reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import { fetchBook, editBook } from '../actions/index';
class BookEdit extends Component {
componentWillMount() {
this.props.fetchBook(this.props.params.id);
}
static contextTypes = {
router: PropTypes.object
}
onSubmit(props) {
this.props.editBook(this.props.book.id, props)
.then(() => {
this.context.router.push('/');
});
}
const data = {
title: {this.props.book.title},
description: {this.props.author}
}
render() {
const { fields: { title, author }, handleSubmit } = this.props;
const { book } = this.props;
if (!book) {
return (
<div>
<p>Loading...</p>
</div>
)
}
return (
<div>
<Link to="/">Back</Link>
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<h2>Add a new book</h2>
<label>Title</label>
<input type="text" {...title} />
<div className="text-help">{title.touched ? title.error : ''}</div>
<label>Author</label>
<input type="text" {...author} />
<div className="text-help">{author.touched ? author.error : ''}</div>
<button type="submit">Add</button>
<Link to="/" className="button">Go back</Link>
</form>
</div>
);
}
}
function mapStateToProps(state) {
return {
book: state.books.book,
initialValues: // how do I pass in the books here?
};
}
export default reduxForm({
form: 'EditBookForm',
fields: ['title', 'author']
}, mapStateToProps, { fetchBook, editBook })(BookEdit);
Thank you.
Your form values aren't what's in state.books.book? I think this is all you're looking for:
function mapStateToProps(state) {
return {
book: state.books.book,
initialValues: state.books.book
};
}
Since you're only really looking at this.props.book to know if it's loaded or not, it might be more explicit to do something like:
function mapStateToProps(state) {
return {
loaded: !!state.books.book,
initialValues: state.books.book
};
}
Hope that helps.
In related to above question, Erik. I have following form and not sure why it is not Validating on submit. It loads the data into fields but when I hit submit the validation fails.
Form_Bayan.js
import React, {Component, PropTypes} from "react";
import {browserHistory} from "react-router";
import {reduxForm, Field} from "redux-form";
import {MyCustomInput, MySimpleInput, MyCustomSelect} from "./__form_field_components";
import {connect} from "react-redux";
import {bindActionCreators} from "redux";
import {
ADMIN_FETCH_AUTOSUGGESTS_Lbl,
adminFetchAutoSuggestCats_act,
ADMIN_GENERATESLUG_Lbl,
adminGenerateSlug_act,
ADMIN_GETCATID_BYNAME_Lbl,
adminGetCatIdByName_act,
ADMIN_ADDNEWBAYAAN_Lbl,
adminAddNewBayaan_act,
adminFetchArticlesByCat_act,
adminUpdateBayaan_act
} from "../../actions/adminActionCreators";
import _ from "lodash";
class NewBayanForm extends Component {
constructor(props) {
super(props); // this component inherits "toggleViewFunction" function through props for redirection
this.generateSlug = this.generateSlug.bind(this);
this.state = {
submitButtonMeta: {
btnTitle: "Save",
btnClass: "btn btn-default",
btnIcon: null,
disabled: false
},
globalMessage: { // set when an action is performed by ActionCreation+Reducer and a message is returned
message: "",
className: ""
},
tempData: {
//the_bayaansMainCat_id : 1, // '1' refers to the 'Bayaans' parent category in admin , this ID is used here for different sort of lookups i.e. fetch available subcats for autosuggest, fetch cat ID by name under parent catID
the_bayaansMainCat_id: this.props.associatedMainCatId, // being passed from parent component to avoide redundent declaration
the_autoSuggestCatList: [],
slug: "",
the_catId: null
}
};
}
resetMessageState() {
var noMsg = {message: "", className: ""};
this.setState({globalMessage: noMsg});
}
componentDidMount() {
console.log("<NewBayanForm> (componentDidMount)");
this.props.adminFetchAutoSuggestCats_act(this.state.tempData.the_bayaansMainCat_id);
}
doSubmit(props) {
//console.log("----- submitting form -----");
//console.log(props);
this.disableSubmitButton();
// prepare data for submit request
// item_title, item_slug, content, picture, attachment, media_path, reference, tag_keywords, author_name, cat_id, date_created
var newBayanObj = {
item_title: props.titleTxt,
item_slug: this.state.tempData.slug,
content: props.videoIdTxt,
picture: "",
attachment: "",
media_path: "https://www.youtube.com/watch?v=" + props.videoIdTxt,
reference: "",
tag_keywords: props.keywordsTxt,
author_name: props.authorTxt,
cat_id: this.state.tempData.the_catId
};
this.props.adminUpdateBayaan_act(newBayaanObj)
.then(() => {
console.log("%c <NewBayanForm> (doSubmit) Updated bayaan, refetching updated bayaans list...", "color:blue;font-weight:bold;");
this.props.adminFetchArticlesByCat_act(this.props.associatedMainCatId)
.then(() => {
console.log("%c <NewBayanForm> (doSubmit) Redirecting to Gallery after update...", "color:blue;font-weight:bold;");
this.props.toggleViewFunction(); // comming from Parent Class (bayaansPage)
});
});
}
disableSubmitButton() {
console.log("<NewBayanForm> (disableSubmitButton)");
// Ref: http://stackoverflow.com/questions/18933985/this-setstate-isnt-merging-states-as-i-would-expect
var newButtonState = {
btnTitle: "Please wait... ",
btnClass: "btn btn-disabled",
btnIcon: null,
disabled: true
};
this.setState({submitButtonMeta: newButtonState});
this.resetMessageState(); // Need to reset message state when retrying for form submit after 1st failure
}
enableSubmitButton() {
console.log("<NewBayanForm> (enableSubmitButton)");
// Ref: http://stackoverflow.com/questions/18933985/this-setstate-isnt-merging-states-as-i-would-expect
var newButtonState = {btnTitle: "Save", btnClass: "btn btn-default", btnIcon: null, disabled: false};
this.setState({submitButtonMeta: newButtonState});
}
fetchCategoryId(value) {
console.log('<NewBayanForm> (fetchCategoryId) input-Value:', value); // make API call to fetch / generate category ID for this post
this.props.adminGetCatIdByName_act(value, this.state.tempData.the_bayaansMainCat_id); // '1': refers to look up under 'Bayaans' parent category for the specified category name
}
// will always receive and triggers when there are 'new props' and not old/same props
componentWillReceiveProps(nextProps) { // required when props are passed/changed from parent source. And we want to do some operation as props are changed (Ref: http://stackoverflow.com/questions/32414308/updating-state-on-props-change-in-react-form)
console.log("<NewBayanForm> (componentWillReceiveProps) nextProps: ", nextProps); // OK
//console.log("this.props : ", this.props); // OK
//console.log("nextProps.siteEssentials.actionsResult : ", nextProps.siteEssentials.actionsResult); // OK
if (nextProps.hasOwnProperty("siteEssentials")) { // if action status appeared as Done!
if (nextProps.siteEssentials.hasOwnProperty("actionsResult")) { // if action status appeared as Done!
if (nextProps.siteEssentials.actionsResult[ADMIN_GETCATID_BYNAME_Lbl] !== "FAILED") {
var clonedState = this.state.tempData;
clonedState.the_catId = nextProps.siteEssentials.actionsResult[ADMIN_GETCATID_BYNAME_Lbl];
//var newTempState = {slug: this.state.tempData.slug, the_catId: nextProps.siteEssentials.actionsResult[ADMIN_GETCATID_BYNAME_Lbl] };
this.setState({tempData: clonedState});
}
if (nextProps.siteEssentials.actionsResult[ADMIN_FETCH_AUTOSUGGESTS_Lbl] !== "FAILED") {
var clonedState = this.state.tempData;
clonedState.the_autoSuggestCatList = nextProps.siteEssentials.actionsResult[ADMIN_FETCH_AUTOSUGGESTS_Lbl];
this.setState({tempData: clonedState});
}
console.log("<NewBayanForm> (componentWillReceiveProps) new-State:", this.state);
}
}
}
render() { // rendering Edit form
const {handleSubmit} = this.props;
console.log('<NewBayanForm> (render_editForm) this.props:', this.props);
return (
<div className="adminForm">
<form onSubmit={handleSubmit(this.doSubmit.bind(this))}>
<div className="col-sm-6">
<div className="row">
<div className="col-sm-5"><label>Title:</label></div>
<div className="col-sm-7"><Field name="titleTxt" component={MySimpleInput}
defaultValue={this.props.name} type="text"
placeholder="Enter Title"/></div>
</div>
<div className="row">
<div className="col-sm-5"><label>Slug:</label></div>
<div className="col-sm-7">{this.state.tempData.slug || this.props.slug} <input
type="hidden" name="slugTxt" value={this.state.tempData.slug}/></div>
</div>
<div className="row">
<div className="col-sm-5"><label>Select Category:</label></div>
<div className="col-sm-7"><Field name="catTxt" component={MyCustomSelect}
defaultValue={this.props.category_name} type="text"
placeholder="Select or Type a New"
selectableOptionsList={this.state.tempData.the_autoSuggestCatList}
onSelectionDone={ this.fetchCategoryId.bind(this) }/>
<input type="hidden" name="catIdTxt"
value={this.state.tempData.the_catId || this.props.category_id}/>
</div>
</div>
</div>
<div className="col-sm-6">
<div className="row">
<div className="col-sm-5"><label>Youtube Video ID:</label></div>
<div className="col-sm-7"><Field name="videoIdTxt" component={MySimpleInput}
defaultValue={this.props.content} type="text"
placeholder="TsQs9aDKwrw"/></div>
<div className="col-sm-12 hint"><b>Hint: </b> https://www.youtube.com/watch?v=<span
className="highlight">TsQs9aDKwrw</span></div>
</div>
<div className="row">
<div className="col-sm-5"><label>Author/Speaker:</label></div>
<div className="col-sm-7"><Field name="authorTxt" component={MySimpleInput}
defaultValue={this.props.author} type="text"/></div>
</div>
<div className="row">
<div className="col-sm-5"><label>Tags/Keywords:</label></div>
<div className="col-sm-7"><Field name="keywordsTxt" component={MySimpleInput}
defaultValue={this.props.tag_keywords} type="text"/>
</div>
</div>
</div>
<div className="row">
<div className={this.state.globalMessage.className}>{this.state.globalMessage.message}</div>
</div>
<div className="buttonControls">
<a className="cancelBtn" onClick={this.props.toggleViewFunction}>Cancel</a>
<button className={this.state.submitButtonMeta.btnClass}
disabled={this.state.submitButtonMeta.disabled}>
{this.state.submitButtonMeta.btnTitle}</button>
</div>
</form>
</div>
);
}
}
function validate(values) { // Validate function being called on Blur
const errors = {};
if (!values.titleTxt)
errors.titleTxt = "Enter Title";
if (!values.catTxt)
errors.catTxt = "Select/Enter a Category";
if (!values.videoIdTxt)
errors.videoIdTxt = "Enter youtube video ID (follow the provided hint)";
if (!values.keywordsTxt)
errors.keywordsTxt = "Enter keywords (will help in search)";
return errors;
}
// ReduxForm decorator
const newBayanFormAdmin_reduxformObj = reduxForm({
form: "newBayanFormAdmin", // any unique name of our form
validate // totally equivelent to--> validate: validate
});
function mapStateToProps({siteEssentials}, ownProps) {
console.log("<NewBayanForm> (mapStateToProps) siteEssentials:", siteEssentials);
// 1st param is related to our Redux State, 2nd param relates to our own component props
var initialValues = {
titleTxt: ownProps.name,
slugTxt: ownProps.slug,
catTxt: ownProps.category_name,
catIdTxt: ownProps.category_id,
videoIdTxt: ownProps.content,
authorTxt: ownProps.author,
keywordsTxt: ownProps.tag_keywords
};
console.log("<NewBayanForm> (mapStateToProps) initialValues: ", initialValues);
return ({siteEssentials}, initialValues);
};
function mapDispatchToProps(dispatch) {
return bindActionCreators({
adminFetchAutoSuggestCats_act,
adminGenerateSlug_act,
adminGetCatIdByName_act,
adminAddNewBayaan_act,
adminFetchArticlesByCat_act
}, dispatch);
};
NewBayanForm = connect(mapStateToProps, mapDispatchToProps) (newBayanFormAdmin_reduxformObj(NewBayanForm));
export default NewBayanForm;