I'm using material-ui and react-charts-js to build a page that fetches from backend and displays data.
I'm having some trouble in understanding how to correctly display MyGraph.
The page contains two Date Picker, a Toggle and a Raised Button.
I'd like to display in the second Tab the graph after the data is chosen and user's click on the button, then reset the data and repeat again.
These are some attemps but with this strategy, the MyGraph is correctly displayed but when it calls this.props.onCreated(), this.state.buttonClicked is false and the graph is hidden.
MyCard.js
import React from 'react';
import {Card, Tabs, Tab, FontIcon, DatePicker, Toggle, GridList, GridTile, SelectField, MenuItem, RaisedButton} from 'material-ui/';
import MyGraph from './MyGraph';
const styles = {
root: {
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-around',
margin: '0 auto'
},
gridList: {
width: 200,
height: 500,
overflowY: 'auto',
},
};
export default class MyCard extends React.Component {
constructor(props){
super(props)
this.state = {
queryNumber: 1,
startDate: null,
endDate: null,
sameRegion: true,
buttonDisabled: true,
buttonClicked: false
}
}
handleQueryNumberChange = (event, index, value) => {
this.setState({queryNumber: value});
}
check(){
if (this.state.startDate && this.state.endDate) {
this.setState({buttonDisabled: false})
}
}
handleStartDateChange = (event, date) => {
this.setState({
startDate: date,
}, this.check);
};
handleEndDateChange = (event, date) => {
this.setState({
endDate: date,
}, this.check);
};
handleSameRegionChange = (event, isInputChecked) =>{
this.setState({sameRegion: isInputChecked});
}
handleClick = (event) =>{
this.setState({buttonClicked: true})
}
resetForm = () =>{
this.setState({buttonClicked: false, startDate: null, endDate: null, buttonDisabled: true})
}
render() {
return (
<Card>
<Tabs>
<Tab icon={<FontIcon className="material-icons" >date_range</FontIcon>} label="Pick">
<div style={styles.root}>
<GridList
cols={1}
cellHeight={100}
padding={1}
style={styles.gridList}
>
<GridTile>
<SelectField
floatingLabelText="Query"
value={this.state.queryNumber}
onChange={this.handleQueryNumberChange}
>
<MenuItem value={1} primaryText="Date" />
<MenuItem value={2} primaryText="Query2" />
<MenuItem value={3} primaryText="Query3" />
<MenuItem value={4} primaryText="Query4" />
<MenuItem value={5} primaryText="Query5" />
</SelectField>
</GridTile>
<GridTile>
<DatePicker hintText="Select start date" value={this.state.startDate} onChange={this.handleStartDateChange}/>
</GridTile>
<GridTile>
<DatePicker hintText="Select end date" value={this.state.endDate} onChange={this.handleEndDateChange}/>
</GridTile>
<GridTile>
<Toggle label="Same Region" defaultToggled={true} onToggle={this.handleSameRegionChange}/>
</GridTile>
<GridTile>
<RaisedButton label="Send" secondary={true} disabled={this.state.buttonDisabled} onClick={this.handleClick}/>
</GridTile>
</GridList>
</div>
</Tab>
<Tab icon={<FontIcon className="material-icons" >pie_chart</FontIcon>} label="Graph">
<div>
{this.state.buttonClicked && <MyGraph values={this.state} onCreated={this.resetForm}/>}
</div>
</Tab>
</Tabs>
</Card>
);
}
}
MyGraph.js
import React from 'react';
import {Pie} from 'react-chartjs-2';
import moment from 'moment'
export default class MyGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
data: props,
labels: [],
datasets: [{
data: [],
backgroundColor: [
'#d50000',
'#2962ff',
'#00c853',
'#ffab00'
],
hoverBackgroundColor: [
'#ff5252',
'#448aff',
'#69f0ae',
'#ffd740'
]
}]
}
}
call(){
this.callApi()
.then(res => {
console.log(res)
let datasetsModified = this.state.datasets;
let labelsModified = this.state.labels;
datasetsModified[0].data.length = 0;
labelsModified.length = 0;
for(let resource of res){
datasetsModified[0].data.push(resource.avg)
labelsModified.push(resource._id)
}
this.setState({
labels: labelsModified,
datasets: datasetsModified,
})
})
.then(() =>{
this.props.onCreated()
})
.catch(err => console.log(err))
}
async callApi(){
var query = 'http://localhost:3100/api/pings/query/avgInDay?start='+moment(this.state.data.values.startDate).format('YYYY-MM-DD')+'&end='+moment(this.state.data.values.endDate).format('YYYY-MM-DD')+'&sameRegion='+((this.state.data.values.sameRegion === true) ? 0 : 1)
const response = await fetch(query);
const body = await response.json();
return body;
}
componentDidMount(){
this.call()
}
componentWillReceiveProps(nextProps) { // Successives rendering
console.log('will')
this.setState({data: nextProps}, this.call())
}
render() {
return (
<Pie data={this.state} width={500} height={500} options={{maintainAspectRatio: false}}/>
);
}
}
Can anyone illustrate the correct way to do this?
Thanks in advance :)
EDIT 1: I've tried to lift the state up, it's doing what I want but MyGraph is called every single modify before the click; is this ok?
And finally, my labels and data are updated, but not the slices
MyCard.js
import React from 'react';
import {Card, Tabs, Tab, FontIcon, DatePicker, Toggle, GridList, GridTile, SelectField, MenuItem, RaisedButton} from 'material-ui/';
import MyGraph from './MyGraph';
import moment from "moment/moment";
const styles = {
root: {
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-around',
margin: '0 auto'
},
gridList: {
width: 200,
height: 500,
overflowY: 'auto',
},
};
export default class MyCard extends React.Component {
constructor(props){
super(props)
this.state = {
queryNumber: 1,
startDate: null,
endDate: null,
sameRegion: true,
buttonDisabled: true,
buttonClicked: false,
graphData: {
data: props,
labels: [],
datasets: [{
data: [],
backgroundColor: [
'#d50000',
'#2962ff',
'#00c853',
'#ffab00'
],
hoverBackgroundColor: [
'#ff5252',
'#448aff',
'#69f0ae',
'#ffd740'
]
}]
}
}
}
handleQueryNumberChange = (event, index, value) => {
this.setState({queryNumber: value});
}
check(){
if (this.state.startDate && this.state.endDate) {
this.setState({buttonDisabled: false})
}
}
handleStartDateChange = (event, date) => {
this.setState({
startDate: date,
}, this.check);
};
handleEndDateChange = (event, date) => {
this.setState({
endDate: date,
}, this.check);
};
handleSameRegionChange = (event, isInputChecked) =>{
this.setState({sameRegion: isInputChecked});
}
handleClick = (event) =>{
this.callApi()
.then(res => {
console.log(res)
let graphDataModified = this.state.graphData;
let datasetsModified = graphDataModified.datasets;
let labelsModified = graphDataModified.labels;
datasetsModified[0].data.length = 0;
labelsModified.length = 0;
for(let resource of res){
datasetsModified[0].data.push(resource.avg)
labelsModified.push(resource._id)
}
this.setState({graphData: graphDataModified, buttonClicked: true})
})
.then(() =>{
this.resetForm()
})
.catch(err => console.log(err))
}
async callApi(){
var query = 'http://localhost:3100/api/pings/query/avgInDay?start='+moment(this.state.startDate).format('YYYY-MM-DD')+'&end='+moment(this.state.endDate).format('YYYY-MM-DD')+'&sameRegion='+((this.state.sameRegion === true) ? 0 : 1)
const response = await fetch(query);
const body = await response.json();
return body;
}
resetForm = () =>{
this.setState({startDate: null, endDate: null, buttonDisabled: true})
}
render() {
return (
<Card>
<Tabs>
<Tab icon={<FontIcon className="material-icons" >date_range</FontIcon>} label="Pick">
<div style={styles.root}>
<GridList
cols={1}
cellHeight={100}
padding={1}
style={styles.gridList}
>
<GridTile>
<SelectField
floatingLabelText="Query"
value={this.state.queryNumber}
onChange={this.handleQueryNumberChange}
>
<MenuItem value={1} primaryText="Date" />
<MenuItem value={2} primaryText="Query2" />
<MenuItem value={3} primaryText="Query3" />
<MenuItem value={4} primaryText="Query4" />
<MenuItem value={5} primaryText="Query5" />
</SelectField>
</GridTile>
<GridTile>
<DatePicker hintText="Select start date" value={this.state.startDate} onChange={this.handleStartDateChange}/>
</GridTile>
<GridTile>
<DatePicker hintText="Select end date" value={this.state.endDate} onChange={this.handleEndDateChange}/>
</GridTile>
<GridTile>
<Toggle label="Same Region" defaultToggled={true} onToggle={this.handleSameRegionChange}/>
</GridTile>
<GridTile>
<RaisedButton label="Send" secondary={true} disabled={this.state.buttonDisabled} onClick={this.handleClick}/>
</GridTile>
</GridList>
</div>
</Tab>
<Tab icon={<FontIcon className="material-icons" >pie_chart</FontIcon>} label="Graph">
<div>
{ this.state.buttonClicked ? <MyGraph data={this.state.graphData}/> : null }
</div>
</Tab>
</Tabs>
</Card>
);
}
}
MyGraph.js
import React from 'react';
import {Pie} from 'react-chartjs-2';
export default class MyGraph extends React.Component {
render() {
console.log(this.props.data)
return (
<Pie data={this.props.data} width={500} height={500} options={{maintainAspectRatio: false}}/>
);
}
}
Related
Here is a picture of the screen When I try to render my timestamp from firestore it shows an error
"Objects are not valid as a React child (found: object with keys {seconds, nanoseconds}. If you meant to render a collection of children, use an array instead." How do I format my code to display my timestamp from the Firestore. I am able to create the timestamp and read it in my firestore dashboard.
import React, { Component } from "react";
import {
StyleSheet,
ScrollView,
ActivityIndicator,
View,
Text,
} from "react-native";
import { List, NativeBaseProvider } from "native-base";
import { ListItem } from "react-native-elements";
import colors from "../config/colors";
import firebase from "../../firebase";
class SearchEntry extends Component {
constructor() {
super();
this.firestoreRef = firebase.firestore().collection("RepairDocuments");
this.state = {
isLoading: true,
entryArr: [],
};
}
componentDidMount() {
this.unsubscribe = this.firestoreRef.onSnapshot(this.getCollection);
}
componentWillUnmount() {
this.unsubscribe();
}
getCollection = (querySnapshot) => {
const entryArr = [];
querySnapshot.forEach((res) => {
const { unit, datetime, bio } = res.data();
entryArr.push({
key: res.id,
res,
unit,
datetime,
bio,
});
});
this.setState({
entryArr,
isLoading: false,
});
};
render() {
if (this.state.isLoading) {
return (
<View style={styles.preloader}>
<ActivityIndicator size="large" color="#9E9E9E" />
</View>
);
}
return (
<ScrollView style={styles.container}>
{this.state.entryArr.map((res, i) => {
return (
<ListItem
key={i}
onPress={() => {
this.props.navigation.navigate("Details", {
userkey: res.key,
});
}}
bottomDivider
>
<ListItem.Content>
<ListItem.Title>{res.unit}</ListItem.Title>
<ListItem.Subtitle>{new Date(res.datetime?.unixTime * 1000).toLocaleDateString(
""
)}</ListItem.Subtitle>
</ListItem.Content>
<ListItem.Chevron color="black" />
</ListItem>
);
})}
</ScrollView>
);
}
}
Here is the code that enters the time stamp to my firestore.
import React, { Component, useState } from "react";
import {
ImageBackground,
View,
StyleSheet,
TextInput,
TouchableOpacity,
Image,
ActivityIndicator,
Text,
} from "react-native";
import LargeButton from" .. / components /LargeButton";
import colors from "../config/colors";
import firebase from "../../firebase";
import DatePicker from "#react-native- community/datetimepicker";
class CreateEntry extends Component {
constructor() {
super();
this.dbRef = firebase.firestore().collection("RepairDocuments");
this.state = {
unit: "",
datetime: new Date(),
bio: "",
isLoading: false,
};
}
onChange = (event, selectedDate) => {
const showFlag = Platform.OS === "ios";
this.setState({ show: showFlag });
this.inputValueUpdate(selectedDate, "datetime");
};
inputValueUpdate = (val, prop) => {
const state = this.state;
state[prop] = val;
this.setState(state);
};
storeEntry() {
if (this.state.unit === "") {
alert("Please fill out unit #");
} else {
this.setState({
isLoading: true,
});
this.dbRef
.add({
unit: this.state.unit,
datetime: this.state.datetime,
bio: this.state.bio,
})
.then((res) => {
this.setState({
unit: "",
datetime: new Date(),
bio: "",
isLoading: false,
});
this.props.navigation.navigate("Home");
})
.catch((err) => {
console.error("Error found: ", err);
this.setState({
isLoading: false,
});
});
}
}
render() {
if (this.state.isLoading) {
return (
<View style={styles.preloader}>
<ActivityIndicator size="large" color="#9E9E9E" />
</View>
);
showMode = (currentMode) => {
this.setState({ show: true });
this.setState({ mode: currentMode });
};
showDatepicker = () => {
this.showMode("date");
};
}
return (
<ImageBackground
style={styles.background}
source={require("../assets/Login.jpeg")}
>
<View style={styles.container}>
<View style={styles.header}>
<Image
style={styles.headerLogo}
source={require("../assets/WordLogo.jpeg")}
/>
</View>
<View style={styles.inputGroup}>
<TextInput
placeholder={"Enter Unit #"}
value={this.state.unit}
onChangeText={(val) => this.inputValueUpdate(val, "unit")}
/>
</View>
<View style={styles.inputGroup}>
<Text>Select Date:</Text>
<View>
<DatePicker
testID="dateTimePicker"
value={this.state.datetime}
display="default"
onChange={this.onChange}
dateFormat="dd/MM/yyyy h:mm aa"
/>
</View>
</View>
<View style={styles.inputGroup}>
<TextInput
placeholder={"Enter Work Completed"}
value={this.state.bio}
onChangeText={(val) => this.inputValueUpdate(val, "bio")}
/>
</View>
<LargeButton
title="submit"
color="secondary"
onPress={() => this.storeEntry()}
/>
</View>
</ImageBackground>
);
}
}
I got a legacy project of Meteor and React and I try to make it work. But I got an error at one point. Can anyone help me to resolve following error?
Uncaught Error: Objects are not valid as a React child (found: object with keys {$$typeof, type, compare}). If you meant to render a collection of children, use an array instead.
in Unknown
in main (created by Basic)
in Basic (created by Context.Consumer)
in Content (created by PageLayout)
in section (created by BasicLayout)
in BasicLayout (created by Context.Consumer)
in Layout (created by PageLayout)
in section (created by BasicLayout)
in BasicLayout (created by Context.Consumer)
in Layout (created by PageLayout)
in section (created by BasicLayout)
in BasicLayout (created by Context.Consumer)
in Layout (created by PageLayout)
in LocaleProvider (created by Context.Consumer)
in LocaleReceiver (created by ConfigProvider)
in ConfigProvider (created by PageLayout)
in PageLayout (created by ForwardRef)
in ForwardRef
in ForwardRef
at throwOnInvalidObjectType (modules.js:64084)
at reconcileChildFibers (modules.js:64984)
at reconcileChildren (modules.js:67433)
at mountIndeterminateComponent (modules.js:68213)
at beginWork (modules.js:69267)
at HTMLUnknownElement.callCallback (modules.js:50859)
at Object.invokeGuardedCallbackDev (modules.js:50908)
at invokeGuardedCallback (modules.js:50963)
at beginWork$1 (modules.js:73874)
at performUnitOfWork (modules.js:72828)
Within the secure.js the PostCategories are mounted like this
ContentRouter.route('/content-creator', {
name: 'Content Creator',
icon: 'solution',
permission: 'main-navigation.view.module.posts',
className: 'posts',
visible: true,
action: () => {
mount(PageLayout, { content: <PostCategories /> });
},
});
The PageLayout looks like this :
import { Meteor } from 'meteor/meteor';
import { withTracker } from 'meteor/react-meteor-data';
import React, { Component } from 'react';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import Konami from 'react-konami-code';
import deDE from 'antd/lib/locale-provider/de_DE';
import moment from 'moment';
import 'moment/locale/de';
import {
ConfigProvider, Layout, Menu, Icon, Steps, Badge, Button, message,
} from 'antd';
import { Workspaces } from '../../../api/workspaces/collection';
import DoneStepper from './components/doneStepper';
import { DASHBOARD_VERSION } from '../../../helpers/Constants';
const {
Header, Content, Sider, Footer,
} = Layout;
moment.locale('de');
class PageLayout extends Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
logout = () => {
Meteor.logout(() => {
FlowRouter.go('/anmelden');
});
};
renderMenuItems = () => {
const groupName = FlowRouter.current().route.group.name;
const items = FlowRouter._routes.map((route) => {
const isSecure = route && route.group && route.group.parent && route.group.parent && route.group.parent.name === 'secure';
const isVisible = route && route.options && route.options.visible && route.options.visible === true;
const isActive = groupName === route.group.name;
let active = null;
if (isActive) {
active = 'ant-menu-item-selected';
}
return (
isVisible
&& isSecure && (
<Menu.Item key={route.path} style={route.options.style} className={[route.options.className, active]}>
<a href={route.path}>
<Icon type={route.options.icon} />
<span className="nav-text">{route.name}</span>
</a>
</Menu.Item>
)
);
});
return items;
};
easterEgg = () => {
message.success('Development Mode enabled');
};
handleResetWorkspace = () => {
const { user } = this.props;
Meteor.call('workspace.reset', user.profile.workspace, (error) => {
if (error) {
console.log(error);
message.error('error');
} else {
message.success('success');
}
});
};
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true, error: error };
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
console.log(error, errorInfo);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
console.log('ERROR IS:');
console.log(this.state.error);
return <h1>Something went wrong.</h1>;
}
const { ready, user, workspace, content } = this.props;
if (ready) {
if (workspace && workspace.appName && workspace.appName.name) {
document.title = `App - ${workspace.appName.name}`;
} else {
document.title = 'App';
}
}
return (
<ConfigProvider locale={deDE}>
<Layout className="manager-dashboard">
<Header className="header">
<div className="logo__area">
<a href="/">
<img className="logo" src="/logo.svg" alt="cms" />
</a>
</div>
<DoneStepper />
<Konami action={this.easterEgg}>
<Button type="primary" onClick={() => this.handleResetWorkspace()}>
<Icon type="delete" size="small" />
{'reset workspace'}
</Button>
</Konami>
<div className="member">
<a onClick={this.logout}>
{'Abmelden '}
<Icon type="logout" />
</a>
</div>
</Header>
<Layout>
<Sider width={200} style={{ background: '#fff' }}>
<div className="fixed-sidebar">
<Menu
defaultSelectedKeys={[FlowRouter.current().route.path]}
selectedKeys={[FlowRouter.current().route.path]}
>
{this.renderMenuItems()}
</Menu>
</div>
</Sider>
<Layout className="dashboard__content">
<Content
style={{
background: '#fff',
padding: 20,
minHeight: '100vh',
}}
>
{content}
</Content>
<Footer>{`App - Version ${DASHBOARD_VERSION}`}</Footer>
</Layout>
</Layout>
</Layout>
</ConfigProvider>
);
}
}
export default withTracker(() => {
const user = Meteor.user();
const handleWorkspace = Meteor.subscribe('workspaces.one', {
query: { _id: user.profile.workspace },
fields: { appName: 1 },
});
const ready = handleWorkspace.ready();
let workspace = null;
if (ready) {
workspace = Workspaces.findOne({ _id: user.profile.workspace }, { fields: { appName: 1 } });
}
return {
ready,
user,
workspace,
};
})(PageLayout);
And the PostCategories look like this:
/* eslint-disable jsx-a11y/click-events-have-key-events */
import { Meteor } from 'meteor/meteor';
import { withTracker } from 'meteor/react-meteor-data';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import { PostCategories } from '/imports/api/posts/collection';
import React, { Component } from 'react';
import Role from '/imports/helpers/Role';
import CreatePostCategory from '/imports/ui/secure/partials/forms/createPostCategory.js';
import {
Card, Button, message, Row, Col, Icon, Switch, Popover,
} from 'antd';
import Text from '../../components/Text.js';
import Preview from '../../components/preview/index.js';
import CustomSpinner from '../../components/custom-spinner';
const DragIndicator = () => (
<svg viewBox="0 0 14 5" height="15" width="15" xmlns="http://www.w3.org/2000/svg">
<path d="m0 0h14v.83823529h-14z" />
<path d="m0 2h14v.83823529h-14z" />
<path d="m0 4h14v.83823529h-14z" />
</svg>
);
const getItemStyle = (isDragging, draggableStyle) => ({
userSelect: 'none',
...draggableStyle,
});
const reorder = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
class Categories extends Component {
constructor(props) {
super(props);
this.state = {
categories: props.categories,
createCategory: false,
justReorder: false,
deletePopoverVisible: {},
};
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.ready && !prevState.justReorder) {
prevState.categories = nextProps.categories;
prevState.justReorder = false;
} else {
prevState.justReorder = false;
}
return prevState;
}
handleCategoryState = (id, checked) => {
Meteor.call('post.category.state', id, checked, (error) => {
if (error) {
console.log(error);
message.error('error');
} else {
message.success('success');
}
});
};
handleDeletePopoverVisibleChange = (change, id) => {
this.state.deletePopoverVisible[id] = change;
this.setState(this.state);
};
handleDelete = (e, id) => {
e.preventDefault();
Meteor.call('post.category.delete', id, (error) => {
if (error) {
console.log(error);
message.error('error');
} else {
message.success('success');
}
});
};
handleCreateCategory = (e) => {
e.preventDefault();
const form = this.createCategoryForm;
form.validateFieldsAndScroll((err, values) => {
if (err) {
return;
}
values.workspace = this.props.user.profile.workspace;
values.index = this.props.categories.length + 1;
Meteor.call('post.category.insert', values, (error) => {
if (error) {
console.log(error);
message.error('error');
} else {
message.success('success');
form.resetFields();
this.setState({ createCategory: false, categoryId: null });
}
});
});
};
onDragEnd = (data) => {
console.log(data);
const { source, destination, draggableId } = data;
if (!destination) {
return;
}
if (source.droppableId === destination.droppableId) {
this.state.categories = reorder(this.state.categories, data.source.index, data.destination.index);
this.state.categories.forEach((category, idx) => (category.index = idx));
this.state.justReorder = true;
this.setState(this.state, () => {
Meteor.call('post.category.reorder', { categories: this.state.categories }, (error, result) => {
if (error) {
console.log(error);
message.error('error.');
}
});
});
}
};
render() {
const { ready, categories } = this.props;
const { categories: stateCategories, deletePopoverVisible, createCategory } = this.state;
let content = <CustomSpinner />;
if (ready) {
content = (
<Col>
<Row gutter={40}>
<Col xs={24} lg={16} xxl={18}>
{!Array.isArray(categories) ? (
<Row gutter={40}>
<Col
xs={{ span: 24 }}
lg={{ span: 18, offset: 3 }}
xl={{ span: 12, offset: 6 }}
style={{ textAlign: 'center' }}
>
<img src="/emptystates/contentcreator.svg" alt="empty dashboard" />
</Col>
<Col
xs={{ span: 24 }}
lg={{ span: 18, offset: 3 }}
xl={{ span: 16, offset: 4 }}
style={{ textAlign: 'center', marginTop: '40px' }}
>
<h2>Head</h2>
<p>
Text
</p>
<Button
className="button--center"
size="large"
type="primary"
onClick={() => this.setState({ createCategory: true })}
>
Los geht's
<Icon type="right" />
</Button>
</Col>
</Row>
) : (
<div>
<div className="page-title">
<h1>Content Creator</h1>
<p>
Text
</p>
</div>
<Row gutter={40}>
<Col xs={24}>
<div className="shadow-wrapper">
<DragDropContext
onDragEnd={this.onDragEnd}
>
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<div {...provided.droppableProps} ref={provided.innerRef}>
{stateCategories.map((category, index) => (
<Draggable key={category._id} draggableId={category._id} index={index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(snapshot.isDragging, provided.draggableProps.style)}
>
<Card className="card--list-view">
<div>
<div className="card__title">
<span {...provided.dragHandleProps}>
<Icon
component={DragIndicator}
style={{
paddingRight: '20px',
paddingTop: '6px',
}}
/>
</span>
<a href={`/content-creator/kategorie/${category._id}`}>
{category.title}
</a>
</div>
<div className="card__edit">
<span>
<a
href={`/content-creator/neuen-artikel-anlegen?id=${category._id}`}
onClick={() => FlowRouter.go(
'/content-creator/neuen-artikel-anlegen',
{},
{ id: category._id },
)
}
>
<Icon type="folder-add" />
{' Artikel hinzufügen'}
</a>
</span>
<Text
label="Umbenennen der Kategorie"
required
message="Bitte tragen Sie einen Kategorietitel ein."
initialValue={category.title}
render={(
<span>
<Icon
style={{
marginRight: 5,
}}
type="edit"
/>
Umbenennen
</span>
)}
onValid={(result) => {
Meteor.call(
'post.category.edit',
category._id,
{ title: result },
(e) => {
if (e) {
console.log(e);
message.error('Bei dieser Aktion ist ein Fehler aufgetreten');
} else {
message.success('Kategorie Titel erfolgreich erstellt');
}
},
);
}}
/>
<Popover
content={(
<div className="manager-dashboard">
<div
className="page__actions"
style={{ marginBottom: 0, marginTop: 0 }}
>
<Button
type="danger"
onClick={() => this.handleDeletePopoverVisibleChange(false, category._id)
}
>
<Icon type="close" />
{' Abbrechen'}
</Button>
<Button
type="primary"
style={{ marginLeft: '10px' }}
onClick={event => this.handleDelete(event, category._id)}
>
{'Löschen '}
<Icon type="delete" />
</Button>
</div>
</div>
)}
title="Diese Kategorie wirklich löschen?"
trigger="click"
visible={deletePopoverVisible[category._id]}
onVisibleChange={change => this.handleDeletePopoverVisibleChange(change, category._id)
}
>
<Icon type="delete" />
{' Löschen'}
</Popover>
<Switch
style={{ float: 'right' }}
defaultChecked={category.state}
onChange={checked => this.handleCategoryState(category._id, checked)}
checkedChildren="Öffentlich"
unCheckedChildren="Entwurf"
/>
</div>
</div>
</Card>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
{Role.hasPermission('product.category.create') && (
<Card
className="card--list-view add-new-card"
onClick={() => this.setState({ createCategory: true })}
>
<div className="card__title">
<a>+ Neue Kategorie hinzufügen</a>
</div>
</Card>
)}
</div>
</Col>
</Row>
</div>
)}
{createCategory && (
<CreatePostCategory
ref={(form) => {
this.createCategoryForm = form;
}}
visible={createCategory}
onCancel={() => this.setState({ createCategory: false })}
onCreate={this.handleCreateCategory}
/>
)}
</Col>
<Col xs={24} sm={12} lg={8} xxl={6}>
<Preview type="categories" />
</Col>
</Row>
</Col>
);
}
return content;
}
}
export default withTracker(() => {
const user = Meteor.user();
const handleCategories = Meteor.subscribe('posts.categories.all', {
query: { workspace: user.profile.workspace },
sort: { index: 1, title: 1 },
fields: {
title: 1, state: 1, index: 1, workspace: 1,
},
});
const ready = handleCategories.ready();
let categories = null;
if (ready) {
categories = PostCategories.find(
{ workspace: user.profile.workspace },
{
sort: { index: 1, title: 1 },
fields: {
title: 1, state: 1, index: 1, workspace: 1,
},
},
).fetch();
}
return {
user,
ready,
categories,
};
}, Categories);
I am completly clueless what is going wrong, maybe somebody could help me out
React components are functions, and this error message comes about because you are passing in an object rather than a function.
That may be enough for you to find the problem now. It is helpful for us if you can do some narrowing down of the problem, rather than dumping a large amount of code - it becomes too expensive (time consuming) for us to help you if we have to pore through hundreds of lines of code.
I'm building an application, where there is a form presented with different steps. In all the steps but one, I manage to provide the necessary functions as props to make some operations such as 'handleNext', 'handleBack' or 'handleChange'.
Nevertheless, in the last step, represented in the class SuccessForm, when I try to execute the 'handleDownload' function, I get the following error:
TypeError: this.props.handleDownload is not a function
Here it is the SuccessForm.js class:
export class SuccessForm extends Component {
constructor() {
super();
}
download = e => {
e.preventDefault();
this.props.handleDownload();
}
render() {
return (
<React.Fragment>
<Grid container>
<Grid item xs={12} sm={2}>
<DirectionsWalkIcon fontSize="large" style={{
fill: "orange", width: 65,
height: 65
}} />
</Grid>
<Grid>
<Grid item xs={12} sm={6}>
<Typography variant="h5" gutterBottom>
Route created
</Typography>
</Grid>
<Grid item xs={12}>
<Typography variant="subtitle1">
Your new track was succesfully created and saved
</Typography>
</Grid>
</Grid>
<Tooltip title="Download" arrow>
<IconButton
variant="contained"
color="primary"
style={{
marginLeft: 'auto',
// marginRight: '2vh'
}}
onClick={this.download}
>
<GetAppIcon fontSize="large" style={{ fill: "orange" }} />
</IconButton>
</Tooltip>
</Grid>
</React.Fragment>
)
}
}
The entire NewRouteForm.js:
import React, { Component } from 'react'
import { makeStyles, MuiThemeProvider } from '#material-ui/core/styles';
import Paper from '#material-ui/core/Paper';
import Stepper from '#material-ui/core/Stepper';
import Step from '#material-ui/core/Step';
import StepLabel from '#material-ui/core/StepLabel';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import DataForm from '../stepper/dataform/DataForm';
import ReviewForm from '../stepper/reviewform/ReviewForm';
import MapForm from '../stepper/mapform/MapForm';
import NavBar from '../../graphic interface/NavBar';
import DirectionsWalkIcon from '#material-ui/icons/DirectionsWalk';
import Avatar from '#material-ui/core/Avatar';
import CheckCircleOutlineOutlinedIcon from '#material- ui/icons/CheckCircleOutlineOutlined';
import FilterHdrIcon from '#material-ui/icons/FilterHdr';
import Grid from '#material-ui/core/Grid';
import SuccessForm from '../stepper/success/SuccessForm';
import { withStyles } from '#material-ui/styles';
export class NewRouteForm extends Component {
state = {
activeStep: 0,
name: '',
description: '',
date: new Date(),
photos: [],
videos: [],
points: []
};
handleNext = () => {
const { activeStep } = this.state;
this.setState({ activeStep: activeStep + 1 });
};
handleBack = () => {
const { activeStep } = this.state;
this.setState({ activeStep: activeStep - 1 });
};
handleChange = input => e => {
this.setState({ [input]: e.target.value });
}
handleDateChange = date => {
this.setState({ date: date });
}
handleMediaChange = (selectorFiles: FileList, code) => { // this is not an error, is TypeScript
switch (code) {
case 0: // photos
this.setState({ photos: selectorFiles });
break;
case 1: // videos
this.setState({ videos: selectorFiles });
break;
default:
alert('Invalid media code!!');
console.log(code)
break;
}
}
handleMapPoints = points => {
this.setState({ points: points })
}
// ###########################
// Download and Upload methods
// ###########################
handleDownload = () => {
// download route
console.log("DOWNLOAD")
alert("DOWNLOAD");
}
upload = () => {
// upload route
}
render() {
const { activeStep } = this.state;
const { name, description, date, photos, videos, points } = this.state;
const values = { activeStep, name, description, date, photos, videos, points };
const { classes } = this.props;
return (
<MuiThemeProvider>
<React.Fragment>
<NavBar />
<main className={classes.layout}>
<Paper className={classes.paper}>
<Avatar className={classes.avatar}>
<FilterHdrIcon fontSize="large" />
</Avatar>
<Typography component="h1" variant="h4" align="center">
Create your own route
</Typography>
<Stepper activeStep={activeStep} className={classes.stepper}>
{steps.map((label) => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
<React.Fragment>
{activeStep === steps.length ? (
<SuccessForm />
) : (
<React.Fragment>
{getStepContent(activeStep,
values,
this.handleNext,
this.handleBack,
this.handleChange,
this.handleDateChange,
this.handleMediaChange,
this.handleMapPoints,
this.handleDownload
)}
</React.Fragment>
)}
</React.Fragment>
</Paper>
</main>
</React.Fragment>
</MuiThemeProvider>
)
}
}
const steps = ['Basic data', 'Map', 'Review your route'];
function getStepContent(step,
values,
handleNext,
handleBack,
handleChange,
handleDateChange,
handleMediaChange,
handleMapPoints,
handleDownload) {
switch (step) {
case 0:
return <DataForm
handleNext={handleNext}
handleChange={handleChange}
handleDateChange={handleDateChange}
handleMediaChange={handleMediaChange}
values={values}
/>;
case 1:
return <MapForm
handleNext={handleNext}
handleBack={handleBack}
handleMapPoints={handleMapPoints}
values={values}
/>;
case 2:
return <ReviewForm
handleNext={handleNext}
handleBack={handleBack}
values={values}
/>;
case 3:
return <SuccessForm
handleDownload={handleDownload}
/>;
default:
throw new Error('Unknown step');
}
}
const useStyles = theme => ({
layout: {
width: 'auto',
marginLeft: theme.spacing(2),
marginRight: theme.spacing(2),
[theme.breakpoints.up(600 + theme.spacing(2) * 2)]: {
width: 600,
marginLeft: 'auto',
marginRight: 'auto',
},
},
paper: {
marginTop: theme.spacing(3),
marginBottom: theme.spacing(3),
padding: theme.spacing(2),
[theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
marginTop: theme.spacing(6),
marginBottom: theme.spacing(6),
padding: theme.spacing(3),
},
},
stepper: {
padding: theme.spacing(3, 0, 5),
},
buttons: {
display: 'flex',
justifyContent: 'flex-end',
},
button: {
marginTop: theme.spacing(3),
marginLeft: theme.spacing(1),
},
avatar: {
marginLeft: 'auto',
marginRight: 'auto',
backgroundColor: theme.palette.warning.main,
},
icon: {
width: 65,
height: 65,
},
grid: {
marginLeft: theme.spacing(2),
}
});
export default withStyles(useStyles)(NewRouteForm);
Try calling super(props) in the constructor:
constructor(props) {
super(props);
}
and passing function with this instance (this.handleDownload) as it is a class property:
<SuccessForm handleDownload={this.handleDownload} />
Update:
You have a bug on the last step when you not passing a property:
activeStep === steps.length ? <SuccessForm handleDownload={this.handleDownload}/>
Assuming that you have a class in your parent Component, what you're missing is the this keyword in the function reference...
case 3:
return <SuccessForm
handleDownload={this.handleDownload}
/>;
I am using 'react-form-validator-core' package and trying to create a custom form validator that implements 'mui-downshift', a Material UI implementation of PayPal's downshift. This question is mostly about 'react-form-validator-core' package itself. The problem is that the form itself does not register the validator component I've created. Here is my full code of the custom component and the form itself. I've exhausted my debugging skills, but what I noticed is that there's something wrong with the this.context in the form...
Validator component:
import React from 'react';
import PropTypes from 'prop-types';
import MuiDownshift from 'mui-downshift';
import { ValidatorComponent } from 'react-form-validator-core';
class AutocompleteValidator extends ValidatorComponent {
constructor(props) {
debugger;
super(props);
this.originalItems = props.items.map(({key, name}) => ({ text: name, value: key }));
this.handleStateChange = this.handleStateChange.bind(this);
this.errorText = this.errorText.bind(this);
}
componentWillMount() {
if (!this.filteredItems) {
this.setState({filteredItems: this.originalItems});
}
if (!!this.props.value) {
const selectedItem = this.originalItems.filter(
item => item.value.toLowerCase().includes(this.props.value.toLowerCase())
)[0];
this.setState({ selectedItem })
} else {
this.setState({ selectedItem: null})
}
}
componentWillReceiveProps(nextProps) {
// If no filteredItems in sate, get the whole list:
if (!nextProps.value) {
this.setState({ isValid: false })
}
}
handleStateChange(changes) {
// If searching
if (changes.hasOwnProperty('inputValue')) {
const filteredItems = this.originalItems.filter(
item => item.text.toLowerCase().includes(changes.inputValue.toLowerCase())
);
this.setState({ filteredItems })
}
// If something is selected
if (changes.hasOwnProperty('selectedItem')) {
!!changes.selectedItem ? this.setState({isValid: true}) : this.setState({isValid: false})
// If we get undefined, change to '' as a fallback to default state
changes.selectedItem = changes.selectedItem ? changes.selectedItem : '';
this.props.onStateChange(changes);
}
}
errorText() {
const { isValid } = this.state;
if (isValid) {
return null;
}
return (
<div style={{ color: 'red' }}>
{this.getErrorMessage()}
</div>
);
}
render() {
return (
<div>
<MuiDownshift
{...this.props}
items={this.state.filteredItems}
onStateChange={this.handleStateChange}
ref={(r) => { this.input = r; }}
defaultSelectedItem={this.state.selectedItem}
/>
{this.errorText()}
</div>
);
}
}
AutocompleteValidator.childContextTypes = {
form: PropTypes.object
};
export default AutocompleteValidator;
A component where it's used:
render() {
return (
<ValidatorForm
ref='form'
onSubmit={() => {
this.context.router.history.push(this.props.config.urls['step5']);
}}
onError={errors => console.log(errors)}
>
<Row>
<Col md={12}>
<AutocompleteValidator
validators={['required']}
errorMessages={['Cette information doit être renseignée']}
isRequired={true}
name='bankId'
items={this.props.config.list.bank}
onStateChange={(changes) => {
this.props.loansUpdate('bankId', changes.selectedItem.value);
}}
value={!!this.props.loans.bankId ? this.props.loans.bankId : false}
/>
</Col>
</Row>
<Row>
<Col md={12} style={{ marginTop: '15px' }}>
<Checkbox
label={<Translate value='step4.insuranceProvidedByBank' />}
labelStyle={{ 'top': '0px' }}
name='insuranceProvidedByBank'
value={this.props.loans.insuranceProvidedByBank}
checked={this.props.loans.insuranceProvidedByBank}
onCheck={(event, value) => {
this.props.loansUpdate('insuranceProvidedByBank', value);
}}
/>
</Col>
</Row>
<Row>
<Col sm={6} md={5}>
<Button
fullWidth
style={{ marginTop: '50px', marginBottom: '20px' }}
type='submit'
icon={<i className='fa fa-angle-right' style={{ marginLeft: '10px', display: 'inline-block' }} />}
><Translate value='iContinue' /></Button>
</Col>
</Row>
</ValidatorForm >
)
};
};
you get this problem because you override default componentWillMount and componentWillReceiveProps methods from ValidatorComponent. So, to solve this case you should do something like this
componentWillMount() {
// should call parent method before
super.componentWillMount();
// your code
}
I have simple to-do app and trying to do validation for adding new task mainly I want to prevent user to add a blank task to the list - my React skills are very poor so please forgive this silly question.
if you have any idea how to solve my problem please let me know thanks!
import React, { Component } from 'react';
import AppBar from 'material-ui/AppBar';
import Toolbar from 'material-ui/Toolbar';
import Typography from 'material-ui/Typography';
import Paper from 'material-ui/Paper';
import Grid from 'material-ui/Grid';
import TextField from 'material-ui/TextField';
import List from './List';
import '../App.css';
class App extends Component {
state = {
query: '',
inputValue: '',
todos: [
{ value: 'Naumiej się Reacta', done: false },
{ value: 'Pucuj trzewiki ', done: true },
],
direction: 'row',
justify: 'left',
alignItems: 'left',
}
handleChange = (evt) => {
this.setState({ inputValue: evt.target.value });
}
removeMe = (index) => {
this.setState({
todos: this.state.todos.filter((_, i) => i !== index)
})
}
searchChanged = (evt) => {
this.setState({ query: evt.target.value })
}
handleSubmit = (evt) => {
if (evt.keyCode === 13) {
const newTodo = {
value: this.state.inputValue,
done: false
};
const todos = this.state.todos.concat(newTodo);
this.setState({ todos: todos, inputValue: '' })
}
}
render() {
return (
<Grid item xs={12} style={{ padding: 30, display: 'flex' }}>
<div className="App">
<Typography type="body1'" color="inherit" text-align='left'>
<AppBar position="static" color="default" style={{ flexDirection: 'center' }}>
<Toolbar>
<TextField
style={{ float: 'left', paddingRight: 40, }}
placeholder="Add Task ..."
onChange={this.handleChange}
inputValue={this.state.inputValue}
onKeyDown={this.handleSubmit}
>
</TextField>
<TextField ype="text" placeholder="Search..." onChange={this.searchChanged} />
</Toolbar>
</AppBar>
</Typography>
<Paper>
<List style={{ marginTop: 90 }}
removeMe={this.removeMe}
todos={this.state.todos}
query={this.state.query}
/>
</Paper>
</div>
</Grid>
);
}
}
export default App;
I think you did correctly. Just return before submit or check for value with enter key.
handleSubmit = (evt) => {
if (evt.keyCode === 13 && this.state.inputValue) {
const newTodo = {
value: this.state.inputValue,
done: false
};
const todos = this.state.todos.concat(newTodo);
this.setState({todos: todos, inputValue: ''})
}
}