I am trying to push/redirect to /home after deleting poll. But i get Cannot read property 'push' of undefined
import React, { Component } from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { vote, deletePoll } from "../store/actions/polls";
import { Pie } from "react-chartjs-2";
class Poll extends Component {
constructor(props) {
super(props);
this.handlePollDelete = this.handlePollDelete.bind(this);
}
handlePollDelete() {
const { deletePoll, id, history } = this.props;
deletePoll(id);
history.push("/home");
}
render() {
const { poll, vote, auth } = this.props;
console.log(auth.user.id);
const userIdPoll = poll.user && poll.user._id;
const userIdAuth = auth.user.id;
const answers =
poll.question &&
poll.options.map(option => (
<button
className="vote-btn"
onClick={() => vote(poll._id, { answer: option.title })}
key={option._id}
>
{option.title}
</button>
));
const data = poll.options && {
labels: poll.options.map(option => option.title),
datasets: [
{
label: poll.question,
backgroundColor: poll.options.map(option => color()),
borderColor: "#323643",
data: poll.options.map(option => option.votes)
}
]
};
return (
<div className="flex-basic-column" style={{ margin: "35px 0 0 0" }}>
<h3>{poll.question}</h3>
{answers}
{userIdAuth === userIdPoll ? (
<button
className="vote-btn delete-btn"
onClick={this.handlePollDelete}
>
delete
</button>
) : null}
{poll.options && <Pie data={data} />}
</div>
);
}
}
export default connect(
state => ({
poll: state.currentPoll,
auth: state.auth
}),
{ vote, deletePoll }
)(Poll);
You want to use the withRouter HOC on your connected component so that the history prop is given to your component.
import { withRouter } from 'react-router-dom';
export default withRouter(connect(
state => ({
poll: state.currentPoll,
auth: state.auth
}),
{ vote, deletePoll }
)(Poll));
Related
I have 2 components OptinPage (parent) and TermsOfServices (child). Optin Page is only used for rendering the TermsOfServices component, which can be reused elsewhere in the application. I want to use my onSucceeded () function from my child component to my parent component. I don't see how to do it at all. Currently the result is such that when I click on the button that validates the TermsOfServices it seems to be an infinite loop, it goes on and on without closing my popup. Before I split my TermsOfServices component into a reusable component it worked fine. Before, all content was gathered in OptinPage. Any ideas? Thanks in advance
my TermsOfServices component:
import API from 'api';
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import {
Block,
BlockTitle,
Col,
Fab,
Icon,
Preloader,
} from 'framework7-react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-refetch';
import ReactHtmlParser from 'react-html-parser';
class TermsOfServices extends PureComponent {
static propTypes = {
agreeTosFunc: PropTypes.func.isRequired,
agreeTos: PropTypes.object,
onSucceeded: PropTypes.func,
tos: PropTypes.object.isRequired,
};
static contextTypes = {
apiURL: PropTypes.string,
loginToken: PropTypes.string,
userId: PropTypes.string,
};
static defaultProps = {
agreeTos: {},
onSucceeded: () => {},
};
state = {
currentTos: -1,
};
componentDidUpdate(prevProps) {
const {
agreeTos,
onSucceeded,
tos,
} = this.props;
const { currentTos } = this.state;
/* Prepare for first tos after receiving all of them */
if (
prevProps.tos.pending &&
tos.fulfilled &&
tos.value.length &&
currentTos < 0
) {
this.setState({ currentTos: 0 });
}
/* When sending ToS agreement is done */
if (
prevProps.agreeTos.pending &&
agreeTos.fulfilled
) {
onSucceeded();
}
}
handleNext = () => {
const { agreeTosFunc, tos } = this.props;
const { currentTos: currentTosId } = this.state;
const termsOfServices = tos.value;
const done = currentTosId + 1 === termsOfServices.length;
this.setState({ currentTos: currentTosId + 1 });
if (done) {
agreeTosFunc(termsOfServices.map((v) => v._id));
}
};
render() {
const { tos } = this.props;
const { currentTos: currentTosId } = this.state;
const termsOfServices = tos.value;
const currentTermsOfServices = termsOfServices && termsOfServices[currentTosId];
const loaded = termsOfServices && !tos.pending && tos.fulfilled;
const htmlTransformCallback = (node) => {
if (node.type === 'tag' && node.name === 'a') {
// eslint-disable-next-line no-param-reassign
node.attribs.class = 'external';
}
return undefined;
};
return (
<div>
{ (!loaded || !currentTermsOfServices) && (
<div id="
optin_page_content" className="text-align-center">
<Block className="row align-items-stretch text-align-center">
<Col><Preloader size={50} /></Col>
</Block>
</div>
)}
{ loaded && currentTermsOfServices && (
<div id="optin_page_content" className="text-align-center">
<h1>
<FormattedMessage id="press_yui_tos_subtitle" values={{ from: currentTosId + 1, to: termsOfServices.length }} />
</h1>
<BlockTitle>
{ReactHtmlParser(
currentTermsOfServices.title,
{ transform: htmlTransformCallback },
)}
</BlockTitle>
<Block strong inset>
<div className="tos_content">
{ReactHtmlParser(
currentTermsOfServices.html,
{ transform: htmlTransformCallback },
)}
</div>
</Block>
<Fab position="right-bottom" slot="fixed" color="pink" onClick={() => this.handleNext()}>
{currentTosId + 1 === termsOfServices.length &&
<Icon ios="f7:check" aurora="f7:check" md="material:check" />}
{currentTosId !== termsOfServices.length &&
<Icon ios="f7:chevron_right" aurora="f7:chevron_right" md="material:chevron_right" />}
</Fab>
{currentTosId > 0 && (
<Fab position="left-bottom" slot="fixed" color="pink" onClick={() => this.setState({ currentTos: currentTosId - 1 })}>
<Icon ios="f7:chevron_left" aurora="f7:chevron_left" md="material:chevron_left" />
</Fab>
)}
</div>
)}
</div>
);
}
}
export default connect.defaults(new API())((props, context) => {
const { apiURL, userId } = context;
return {
tos: {
url: new URL(`${apiURL}/tos?outdated=false&required=true`),
},
agreeTosFunc: (tos) => ({
agreeTos: {
body: JSON.stringify({ optIn: tos }),
context,
force: true,
method: 'PUT',
url: new URL(`${apiURL}/users/${userId}/optin`),
},
}),
};
})(TermsOfServices);
My OptIn Page :
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import {
Link,
NavRight,
Navbar,
Page,
Popup,
} from 'framework7-react';
import { FormattedMessage, intlShape } from 'react-intl';
import './OptInPage.scss';
import TermsOfServices from '../components/TermsOfServices';
class OptinPage extends PureComponent {
static propTypes = {
logout: PropTypes.func.isRequired,
opened: PropTypes.bool.isRequired,
};
static contextTypes = {
intl: intlShape,
logout: PropTypes.func,
};
render() {
const { opened, logout } = this.props;
const { intl } = this.context;
const { formatMessage } = intl;
return (
<Popup opened={opened} className="demo-popup-swipe" tabletFullscreen>
<Page id="optin_page">
<Navbar title={formatMessage({ id: 'press_yui_tos_title' })}>
<NavRight>
<Link onClick={() => logout()}>
<FormattedMessage id="press_yui_comments_popup_edit_close" />
</Link>
</NavRight>
</Navbar>
</Page>
<TermsOfServices onSucceeded={this.onSuceeded} />
</Popup>
);
}
}
export default OptinPage;
Just add the data you want the parent to be supplied with in the child component (when it is hit) and then handle the data passed to the parent in the function that you pass in onSuccess.
This will roughly look like this:
const {useState, useEffect} = React;
function App(){
return <Child onSuccess={(data)=>{console.log(data)}}/>;
}
function Child({onSuccess}){
return <div>
<button
onClick={()=>onSuccess("this is the data from the child component")}>
Click to pass data to parent
</button>
</div>;
}
ReactDOM.render(<App/>,document.getElementById('app'));
#element {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id='app'></div>
<div id="element">
<div>node 1</div>
<div>node 2</div>
</div>
to access to parent method or attribute you should use super,
for call to the parent constructor
super([arguments]);
for call parent method
super.parentMethod(arguments);
I recommend create a method on child class and then call the parent method, not directly
for more information take a look on this
https://www.w3schools.com/jsref/jsref_class_super.asp
App.js
import React, { Component } from "react";
import "./App.css";
import Router from "./Router";
class App extends Component {
render() {
return (
<div className="App">
<div>
<h1>React-Redux Store</h1>
<h2>Welcome to the React Store</h2>
</div>
<Router />
</div>
);
}
}
export default App;
index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import "bootstrap/dist/css/bootstrap.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { Provider } from "react-redux";
import { createStore } from "redux";
import reducer from "./reducer";
import "../node_modules/font-awesome/css/font-awesome.min.css";
const store = createStore(reducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
ShopHome.js
when i open the page, it appears TypeError: Cannot read property 'items' of undefined, I guess its something wrong with mapStateToProps and cannot define the state. i Wonder if i did something wrong in the reducer
import React, { Component } from "react";
import { NavLink } from "react-router-dom";
import { connect } from "react-redux";
import { addToCart } from "./action_type";
class ShopHome extends Component {
handleClick = id => {
this.props.addToCart(id);
};
render() {
return (
<div>
<table className="table">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Price</th>
<th>
<NavLink to="/myCart" exact activeStyle={{ color: "green" }}>
my cart
</NavLink>
</th>
</tr>
</thead>
<tbody>
{this.props.items.map(item => {
return (
<tr key={item.id}>
<td>{item.name}</td>
<td>{item.description}</td>
<td>${item.price}</td>
<button to="/" onClick={() => this.handleClick(item.id)}>
add to cart
</button>
</tr>
);
})}
</tbody>
</table>
</div>
);
}
}
const mapStateToProps = state => {
return {
items: state.items
};
};
const mapDispatchToProps = dispatch => {
return {
addToCart: id => {
dispatch(addToCart(id));
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(ShopHome);
ShopCart.js
when i add the add quantity functionality it all works fine, but however after i added subtract quantity functionality it turns out says state is not defined(
TypeError: Cannot read property 'items' of undefined
Function.mapStateToProps [as mapToProps]
src/shopHome.js:47
44 | }
45 | const mapStateToProps = state => {
46 | return {
> 47 | items: state.items
48 | };
49 | };
50 | )
ShopCart.js
import React, { Component } from "react";
import { NavLink } from "react-router-dom";
import { connect } from "react-redux";
import { addQuantity } from "./action_type";
import { subtractQuantity } from "./action_type";
class ShopCart extends Component {
handleAddQuantity = id => {
this.props.addQuantity(id);
};
handleSubtractQuantity = id => {
this.props.subtractQuantity(id);
};
render() {
let addedItems = this.props.items.map(item => {
return (
<tr key={item.id}>
<td>{item.name}</td>
<td>
<NavLink to="/myCart">
<span>
<i
className="fas fa-plus-circle"
onClick={() => {
this.handleAddQuantity(item.id);
}}
></i>
</span>
</NavLink>
{item.quantity}
<NavLink to="/myCart">
<span>
<i
className="fas fa-minus-circle"
onClick={() => {
this.handleSubtractQuantity(item.id);
}}
></i>
</span>
</NavLink>
</td>
<td>${item.price}</td>
</tr>
);
});
return (
<div>
<table className="table">
<thead>
<tr>
<th>Item</th>
<th>Quantity</th>
<th>Price</th>
<th>
<NavLink to="/" exact activeStyle={{ color: "green" }}>
back to store
</NavLink>
</th>
</tr>
</thead>
<tbody>{addedItems}</tbody>
</table>
</div>
);
}
}
const mapStateToProps = state => {
return {
items: state.addedItems
};
};
const mapDispatchToProps = dispatch => {
return {
addQuantity: id => dispatch(addQuantity(id)),
subtractQuantity: id => dispatch(subtractQuantity(id))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(ShopCart);
reducer.js
import { ADD_TO_CART, ADD_QUANTITY, SUBTRACT_QUANTITY } from "./action.js";
const initialState = {
items: [
{
id: 1,
name: "apple",
description: "Eat One Every Day, may keep the doctor away",
price: 12
},
{
id: 2,
name: "grape",
description: "Wine is great, but grapes is better",
price: 11
},
{
id: 3,
name: "pineapple",
description: "enjoy but don`t forget to peer first",
price: 8
}
],
addedItems: []
};
const reducer = (state = initialState, action) => {
if (action.type === ADD_TO_CART) {
let addedItem = state.items.find(item => item.id === action.id);
let existed_item = state.addedItems.find(item => item.id === action.id);
if (existed_item) {
addedItem.quantity += 1;
return {
...state
};
} else {
addedItem.quantity = 1;
return {
...state,
addedItems: [...state.addedItems, addedItem]
};
}
}
if (action.type === ADD_QUANTITY) {
let addedItem = state.items.find(item => item.id === action.id);
addedItem.quantity += 1;
return {
...state
};
}
if (action.type === SUBTRACT_QUANTITY) {
let addedItem = state.items.find(item => item.id === action.id);
if (addedItem.quantity === 1) {
let newItem = state.addedItems.filter(item => item.id !== action.id);
return {
...state,
addedItems: newItem
};
} else {
addedItem.quantity -= 1;
return {
...state
};
}
}
};
export default reducer;
action_type.js
import { ADD_TO_CART, ADD_QUANTITY, SUBTRACT_QUANTITY } from "./action";
export const addToCart = id => {
return {
type: ADD_TO_CART,
id
};
};
export const addQuantity = id => {
return {
type: ADD_QUANTITY,
id
};
};
export const subtractQuantity = id => {
return {
type: SUBTRACT_QUANTITY,
id
};
};
action.js
export const ADD_TO_CART = "ADD_TO_CART";
export const ADD_QUANTITY = "ADD_QUANTITY";
export const SUBTRACT_QUANTITY = "SUBTRACT_QUANTITY";
Hi everyone, I am new to react-redux, when i open the page, the page keep telling me that TypeError: Cannot read property 'items' of undefined in the ShopHome.js, i supppose its something wrong with declaring the state in the mapStateToProps function. can someone give me a hand?
I am newbie to react, redux-saga, I have a dropdown in page Display, when I select it move to respective component (eg. policy, score), In Component Pages, I have a button Add New, on clicking it will navigate to a page as mentioned in link url , which is a page having cancel button, on cancelling it returns to the Display.js but no dropdown selected,
I would like to keep the state articleMode, when navigating to a page and returning back to same page,
articleMode returns to state -1 instead of selected component Policy or Score
actions.js
export const updateArticleMode = data => {
console.log(data.body);
return {
type: CONSTANTS.UPDATE_ARTICLE_MODE,
data: data.body
};
};
queryReducer.js
import * as CONSTANTS from "../constants/constants";
const initialState = {
articleMode: ""
}
case CONSTANTS.UPDATE_ARTICLE_MODE: {
return {
...state,
articleMode: data.mode
};
}
export default queryReducer;
constants.js
export const UPDATE_ARTICLE_MODE = "UPDATE_ARTICLE_MODE";
Display.js
import React from "react";
import { connect } from "react-redux";
import Policy from "../policy";
import Score from "./../score";
import { updateArticleMode } from "../../../../actions/actions";
const articleMode = [
{ name: "Select", id: "-1" },
{ name: "Score", id: "Score" },
{ name: "Policy", id: "Policy" }
]
class Display extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
articleMode: "-1"
};
}
componentWillMount = () => {
this.setState({ articleMode: this.props.queryData.articleMode || "-1" });
};
_getComponent = () => {
const { articleMode } = this.state;
if (articleMode === "Policy") {
return <DisplayPolicy></DisplayPolicy>;
}
if (articleMode === "Score") {
return <DisplayScore></DisplayScore>;
}
}
render() {
return (
<React.Fragment>
<select name="example"
className="simpleSearchSelect1"
value={this.state.articleMode}
onChange={event => {
this.setState({ articleMode: event.target.value });
this.props.dispatch(
updateArticleMode({ body: { mode: event.target.value } })
);
}}
style={{ marginLeft: "2px" }}
>
{articleMode.length != 0 &&
articleMode.map((option, index) => {
const { name, id } = option;
return (
<option key={index} value={id}>
{name}
</option>
);
})}
</select>
{this.state.articleMode === "-1"
? this._renderNoData()
: this._getComponent()}
</React.Fragment>
)}
const mapStateToProps = state => {
return {
queryData: state.queryData
};
};
export default connect(mapStateToProps)(Display);
}
DisplayPolicy.js
import React from "react";
class DisplayPrivacyPolicy extends React.Component {
constructor(props) {
super(props);
}<Link
to={{
pathname: "/ui/privacy-policy/addNew",
state: {
language: "en"
}
}}
>
<button className="page-header-btn icon_btn display-inline">
<img
title="edit"
className="tableImage"
src={`${process.env.PUBLIC_URL}/assets/icons/ic_addstore.svg`}
/>
{`Add New`}
</button>
</Link>
AddNew.js
<Link
to =pathname: "/ui/display",
className="btn btn-themes btn-rounded btn-sec link-sec-btn"
>
Cancel
</Link>
I have this component that I split for easy management. Before splitting everything worked as expected, after splitting I am getting an error when I click on an icon which calls the createReactionsIcon. Error says
TypeError: updateReaction is not a function
onClick
./components/home/components/SingleUpload.jsx:26
23 |
24 | return icons.map(({ key, text, type }) => (
25 | <IconText
> 26 | onClick={() => updateReaction(item.id, key)}
| ^ 27 | key={key}
28 | type={type}
29 | text={text}
How can I access this correctly from my Home component where updateReaction is returning updateReaction from the redux store.
SubComponent
import PropTypes from 'prop-types';
import React from 'react';
import { Avatar, Card, Icon, List } from 'antd';
import { LIST_TEXTS, STYLES } from '../constants';
const { AVATAR, CARD_CONTAINER, CARD_LIST, ICON, USER_LIST } = STYLES;
const { INNER, MORE, UPLOAD, VERTICAL } = LIST_TEXTS;
const IconText = ({ type, text, onClick }) => (
<span>
<Icon type={type} style={ICON} onClick={onClick} />
{text}
</span>
);
function createReactionsIcon(item, updateReaction) {
const { like, dislike, maybe } = item.reactions;
const icons = [
{ key: 'like', text: `${like.count}`, type: 'heart' },
{ key: 'dislike', text: `${dislike.count}`, type: 'dislike' },
{ key: 'maybe', text: `${maybe.count}`, type: 'meh' },
];
return icons.map(({ key, text, type }) => (
<IconText
onClick={() => updateReaction(item.id, key)}
key={key}
type={type}
text={text}
/>
));
}
export default class SingleUpload extends React.Component {
render() {
const { values } = this.props;
return (
<div style={CARD_CONTAINER}>
<List
itemLayout={VERTICAL}
dataSource={values}
renderItem={item => {
const { avatar, description, id, uploader: { image, name } } = item;
return (
<List.Item style={USER_LIST}>
<Card
actions={createReactionsIcon(item, this.updateReaction)}
cover={<img alt={UPLOAD} src={image} />}
extra={<Icon type={MORE} />}
hoverable
key={id}
title={(
<a href="/">
<Avatar src={avatar} style={AVATAR} />
{name}
</a>
)}
type={INNER}
style={CARD_LIST}
>
{description}
</Card>
</List.Item>
);
}}
/>
</div>
);
}
}
Home.js
import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import SingleUpload from './SingleUpload';
import ComparisonUpload from './ComparisonUpload';
import { STYLES } from '../constants';
import * as actions from '../actions';
import { getUploads } from '../selectors';
const { CARD_CONTAINER } = STYLES;
class Home extends React.Component {
componentDidMount() {
const { actions: { requestUploadList } } = this.props;
requestUploadList();
}
updateReaction = (id, reaction) => {
const { actions: { updateReaction } } = this.props;
const payload = { id, reaction };
updateReaction(payload);
}
render() {
const { uploads } = this.props;
return (
<div style={CARD_CONTAINER}>
<SingleUpload values={[...uploads.values()]} />
<ComparisonUpload values={[...uploads.values()]} />
</div>
);
}
}
Home.propTypes = {
actions: PropTypes.objectOf(PropTypes.object),
uploads: PropTypes.instanceOf(Map),
};
const mapStateToProps = state => ({
uploads: getUploads(state),
});
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(actions, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Pass your function to component as props,
<SingleUpload values={[...uploads.values()]} updateReaction = {this.updateReaction}/>
Now you can use this in your child component,
<IconText onClick={() => this.props.updateReaction(item.id, key)}
You can pass the updateReaction from your parent to child as a callback
<SingleUpload values={[...uploads.values()]} hanldeReaction={this.updateReaction} />
And you can access it in the child using props.hanldeReaction
<Card actions={createReactionsIcon(item, this.props.hanldeReaction)}
You have to pass down the updateReaction() event-handler you defined in Home as a prop to SingleUpload. Then you can access that prop from anywhere inside your component.
Which means we can cleanup the actions prop inside the Card since we only need to pass the item now.
<Card actions={createReactionsIcon(item)}
As well as createReactionsIcon, now we just call that prop directly inside the function
function createReactionsIcon(item) {
const { like, dislike, maybe } = item.reactions;
const icons = [
{ key: 'like', text: `${like.count}`, type: 'heart' },
{ key: 'dislike', text: `${dislike.count}`, type: 'dislike' },
{ key: 'maybe', text: `${maybe.count}`, type: 'meh' },
];
return icons.map(({ key, text, type }) => (
<IconText
onClick={() => this.props.updateReaction(item.id, key)}
key={key}
type={type}
text={text}
/>
));
}
Less redundant code overall which sounds like what you are trying to achieve.
I have a array and I want to render the this array into a few redux forms. I found out that all the forms are rerendered. the code looks like the following:
Form.jsx
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Link, Events, scrollSpy } from 'react-scroll';
import styles from './Form.css';
import MultipleForm from './MultipleForm';
class Form extends Component {
constructor(props) {
super(props);
const {
workflows,
} = this.props;
this.state = {
curTab: workflows.length > 0 ? workflows[0] : '',
curForm: '',
};
}
componentDidMount() {
Events.scrollEvent.register('begin');
Events.scrollEvent.register('end');
scrollSpy.update();
}
componentWillReceiveProps(nextProps) {
const {
workflows,
} = nextProps;
if (workflows && workflows.length > this.props.workflows) {
this.setState({
curTab: workflows[0],
});
}
}
componentWillUnmount() {
Events.scrollEvent.remove('begin');
Events.scrollEvent.remove('end');
}
handleChangeTab = (value) => {
this.setState({
curTab: value,
});
}
handleActiveTab = (workflow) => {
console.log(workflow);
}
render() {
const {
workflows,
schemaNames,
...rest
} = this.props;
return (
<div className={styles.container}>
<header>
<PerspectiveBar
value={this.state.curTab}
onChange={this.handleChangeTab}
style={{
position: 'fixed',
left: '0',
top: '48px',
width: '100vw',
zIndex: '1380',
}}
>
{workflows.map(wf => (
<PerspectiveTab
key={wf}
label={wf}
value={wf}
onActive={() => this.handleActiveTab(wf)}
/>
))}
</PerspectiveBar>
</header>
<div className={styles.formContainer}>
<Paper className={styles.paperContainer}>
<MultipleForm
workflow={this.state.curTab}
schemaNames={schemaNames}
{...rest}
/>
</Paper>
</div>
<Drawer className={styles.drawer} containerStyle={{ height: 'calc(100% - 104px)', top: '104px' }}>
<div className={styles.drawerContainer}>
{schemaNames.map(schemaName => (
<Link
onSetActive={(to) => {
this.setState({
curForm: to,
});
}}
to={schemaName}
duration={500}
offset={-104}
spy
smooth
>
<MenuItem
checked={this.state.curForm === schemaName}
>
{schemaName}
</MenuItem>
</Link>
))}
</div>
</Drawer>
</div>
);
}
}
Form.propTypes = {
schemaNames: PropTypes.arrayOf(PropTypes.string),
workflows: PropTypes.arrayOf(PropTypes.string),
fetchSchemaNames: PropTypes.func.isRequired,
};
Form.defaultProps = {
schemaNames: [],
workflows: [],
};
export default Form;
MultipleForm.jsx
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import FlatButton from 'material-ui/FlatButton';
import { Element } from 'react-scroll';
import SchemaForm from './SchemaForm';
class MultipleForm extends Component {
componentDidMount() {
console.log('MultipleForm Mounted');
const {
workflow,
fetchSchemaNames,
} = this.props;
if (workflow) fetchSchemaNames(workflow);
}
componentWillReceiveProps(nextProps) {
const {
workflow,
fetchSchemaNames,
} = nextProps;
if (workflow && this.props.workflow !== workflow) fetchSchemaNames(workflow);
}
componentDidUpdate() {
const {
schemaNames,
schemas,
initialValues,
fetchSchemas,
fetchInitialValues,
} = this.props;
const schemasNeedToFetch = this.remainingSchemas(schemaNames, schemas);
if (schemasNeedToFetch.length !== 0) fetchSchemas(schemasNeedToFetch);
const initialValuesNeedToFetch = this.remainingInitialValues(schemaNames, initialValues);
if (initialValuesNeedToFetch.lenght !== 0) fetchInitialValues(initialValuesNeedToFetch, 1);
}
remainingSchemas = (schemaNames, schemas) =>
schemaNames.filter(schemaName => schemaName in schemas === false).sort();
remainingInitialValues = (schemaNames, initialValues) =>
schemaNames.filter(schemaName => schemaName in initialValues === false).sort();
handleSubmitAll = (event) => {
event.preventDefault();
const {
submit,
schemas,
schemaNames,
} = this.props;
schemaNames
.map(schemaName => schemas[schemaName].title)
.forEach((title) => {
submit(title);
});
}
render() {
const {
schemaNames,
schemas,
initialValues,
postForm,
} = this.props;
schemaNames.sort((a, b) => a.localeCompare(b));
return (
<div>
{schemaNames.map(schemaName => (
<Element name={schemaName}>
<SchemaForm
key={schemaName}
schema={schemas[schemaName]}
initialValue={initialValues[schemaName]}
schemaName={schemaName}
postForm={postForm}
/>
</Element>
))}
<div>
<FlatButton
label="Submit"
/>
<FlatButton label="Deploy" />
</div>
</div>);
}
}
MultipleForm.propTypes = {
workflow: PropTypes.string.isRequired,
submit: PropTypes.func.isRequired,
fetchSchemaNames: PropTypes.func.isRequired,
schemas: PropTypes.object,
schemaNames: PropTypes.arrayOf(PropTypes.string),
initialValues: PropTypes.object,
fetchSchemas: PropTypes.func.isRequired,
fetchInitialValues: PropTypes.func.isRequired,
postForm: PropTypes.func.isRequired,
};
MultipleForm.defaultProps = {
schemaNames: [],
};
export default MultipleForm;
SchemaForm
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Liform from 'liform-react';
import theme from './NokiaTheme';
import styles from './Form.css';
class SchemaForm extends Component {
componentDidMount() {
console.log('schema mounted');
}
shouldComponentUpdate() {
return false;
}
handleSubmit = (value) => {
const {
postForm,
schemaName,
} = this.props;
postForm(value, schemaName, 1);
}
render() {
const {
schema,
initialValue,
} = this.props;
console.log('props', this.props);
return (
<div>
<h3 id={schema.$id} className={styles.formTitle}>
{schema.title}
</h3>
<Liform
schema={schema}
onSubmit={value => this.handleSubmit(value)}
destroyOnUnmount={false}
theme={theme}
initialValues={initialValue}
/>
</div>
);
}
}
SchemaForm.propTypes = {
schema: PropTypes.shape({
$id: PropTypes.string,
}),
initialValue: PropTypes.object,
schemaName: PropTypes.string,
postForm: PropTypes.func.isRequired,
};
SchemaForm.defaultProps = {
schema: {},
initialValue: null,
schemaName: '',
};
export default SchemaForm;
the schemaNames will be changed only by adding or deleting some element. for example: the schemaNames will change from ['A', 'B', 'C'] to ['A', 'B', 'D']. I get the schemaNames from the redux. which I fetch online.
But when I check the ConnectedReduxForm, when I change the schemaNames, the SchemaForm will be unmounted and the react will mount the form again. I have tried with setting the ConnectedReduxForm to be PureComponent. It is not helpful.
Could someone help me with that? I have spent a lot of time of this and nothing helps.
Update: I have found the problem, the reason of it is that for each time that I for each time I update the workflow, I need to fetch the schemaNames from the server. But I still do not know why this happended. Could someone explain that?