I found this this Draftjs with Formik example on Codesandbox and I'm trying to implement in my NextJS project.
I don't understand what is the problem here.
This is my code:
Article.js
import { useState, useEffect } from 'react';
import { addArticle } from '../../store/actions/articleActions';
import { useDispatch, useSelector } from 'react-redux'
import * as Yup from 'yup'
import { Formik } from 'formik'
import dynamic from 'next/dynamic'
import {Editor, EditorState, convertToRaw, convertFromRaw } from 'draft-js';
import {stateToHTML} from 'draft-js-export-html';
import ArticleForm from './ArticleForm'
// reactstrap components
import {
Card,
CardBody,
Container,
Row,
Col
} from "reactstrap";
const Article = () => {
const initialArticle = {
title: '',
slug: '',
content: EditorState.createEmpty()
}
const validationSchema = Yup.object({
title: Yup.string()
.min(3, "Password must be more than 3 charachters!")
.max(20, "Password cant have more than 20 charachters"),
content: Yup.string()
.min(3, "Password must be more than 3 charachters!")
.max(20, "Password cant have more than 20 charachters")
})
const dispatch = useDispatch()
const handleSubmit = async (values) => {
const html = stateToHTML(values.content.getCurrentContent());
console.log("HTMLL", html)
const saveArticle = {
title: values.title,
slug: values.slug,
content: html
}
console.log("articleee", saveArticle)
dispatch(addArticle(saveArticle))
console.log("articleee", saveArticle)
}
return (
<main>
<section className="">
<Container className="pt-lg-md">
<Row className="justify-content-center">
<Col lg="12">
<Card className="bg-secondary shadow border-0">
<CardBody className="px-lg-5 py-lg-5">
<Formik
enableReinitialize={true}
component={ArticleForm}
initialValues={initialArticle}
validationSchema={validationSchema}
validateOnChange={false}
validateOnBlur={true}
onSubmit={(values) => {
handleSubmit(values)
}}
/>
</CardBody>
</Card>
</Col>
</Row>
</Container>
</section>
</main>
);
};
export default Article;
ArticleForm.js
import { Form } from 'formik'
import dynamic from 'next/dynamic'
import {Editor} from 'draft-js';
import { RichEditorExample } from '../../helpers/RichEditor';
// reactstrap components
import {
Button,
FormGroup,
Input,
InputGroupAddon,
InputGroupText,
InputGroup,
} from "reactstrap";
const ArticleForm = props => {
const {
values: {
title,
slug,
content
},
handleChange,
handleSubmit,
setFieldValue,
handleBlur
} = props
// Custom overrides for "code" style.
return (
<React.Fragment>
<Form onSubmit={(e) => {
e.preventDefault()
console.log('submitted')
handleSubmit()
}}
>
<FormGroup>
<InputGroup className="input-group-alternative mb-3">
<InputGroupAddon addonType="prepend">
<InputGroupText>
<i className="ni ni-lock-circle-open" />
</InputGroupText>
</InputGroupAddon>
<Input
placeholder="Title"
type='text'
name='title'
value={title}
onBlur={handleBlur}
onChange={handleChange}
/>
</InputGroup>
</FormGroup>
<FormGroup>
<InputGroup className="input-group-alternative mb-3">
<InputGroupAddon addonType="prepend">
<InputGroupText>
<i className="ni ni-lock-circle-open" />
</InputGroupText>
</InputGroupAddon>
<Input
placeholder="Slug"
type='text'
name='slug'
value={slug}
onBlur={handleBlur}
onChange={handleChange}
/>
</InputGroup>
</FormGroup>
<FormGroup>
<RichEditorExample
editorState={content}
onChange={value => setFieldValue("content", value)}
/>
</FormGroup>
<div className="text-center">
<Button
className="mt-4"
color="primary"
type="submit"
>
Create
</Button>
</div>
</Form>
</React.Fragment>
);
}
export default ArticleForm;
RichEditorExample.js
import React from "react";
import {
Editor,
EditorState,
RichUtils,
convertToRaw,
convertFromRaw
} from "draft-js";
export class RichEditorExample extends React.Component {
onChange = editorState => {
const raw = convertToRaw(editorState.getCurrentContent());
const inputValue = convertFromRaw(raw);
console.log("##raw", { raw, inputValue });
this.props.onChange("editorState", editorState);
};
focus = () => this.refs.editor.focus();
handleKeyCommand = command => {
const { editorState } = this.props;
const newState = RichUtils.handleKeyCommand(editorState, command);
if (newState) {
this.onChange(newState);
return true;
}
return false;
};
onTab = e => {
const maxDepth = 4;
this.onChange(RichUtils.onTab(e, this.props.editorState, maxDepth));
};
toggleBlockType = blockType => {
this.onChange(RichUtils.toggleBlockType(this.props.editorState, blockType));
};
toggleInlineStyle = inlineStyle => {
this.onChange(
RichUtils.toggleInlineStyle(this.props.editorState, inlineStyle)
);
};
render() {
const { editorState } = this.props;
// If the user changes block type before entering any text, we can
// either style the placeholder or hide it. Let's just hide it now.
let className = "RichEditor-editor";
var contentState = editorState.getCurrentContent() ;
console.log("contentState", contentState)
if (!contentState.hasText()) {
if (
contentState
.getBlockMap()
.first()
.getType() !== "unstyled"
) {
className += " RichEditor-hidePlaceholder";
}
}
return (
<div className="RichEditor-root">
<BlockStyleControls
editorState={editorState}
onToggle={this.toggleBlockType}
/>
<InlineStyleControls
editorState={editorState}
onToggle={this.toggleInlineStyle}
/>
<div className={className} onClick={this.focus}>
<Editor
blockStyleFn={getBlockStyle}
customStyleMap={styleMap}
editorState={editorState}
handleKeyCommand={this.handleKeyCommand}
onChange={this.onChange}
onTab={this.onTab}
placeholder=""
ref="editor"
spellCheck={true}
/>
</div>
</div>
);
}
}
// Custom overrides for "code" style.
const styleMap = {
CODE: {
backgroundColor: "rgba(0, 0, 0, 0.05)",
fontFamily: '"Inconsolata", "Menlo", "Consolas", monospace',
fontSize: 16,
padding: 2
}
};
function getBlockStyle(block) {
switch (block.getType()) {
case "blockquote":
return "RichEditor-blockquote";
default:
return null;
}
}
class StyleButton extends React.Component {
constructor() {
super();
this.onToggle = e => {
e.preventDefault();
this.props.onToggle(this.props.style);
};
}
render() {
let className = "RichEditor-styleButton";
if (this.props.active) {
className += " RichEditor-activeButton";
}
return (
<span className={className} onMouseDown={this.onToggle}>
{this.props.label}
</span>
);
}
}
const BLOCK_TYPES = [
{ label: "H1", style: "header-one" },
{ label: "H2", style: "header-two" },
{ label: "H3", style: "header-three" },
{ label: "H4", style: "header-four" },
{ label: "H5", style: "header-five" },
{ label: "H6", style: "header-six" },
{ label: "Blockquote", style: "blockquote" },
{ label: "UL", style: "unordered-list-item" },
{ label: "OL", style: "ordered-list-item" },
{ label: "Code Block", style: "code-block" }
];
const BlockStyleControls = props => {
const { editorState } = props;
const selection = editorState.getSelection();
const blockType = editorState
.getCurrentContent()
.getBlockForKey(selection.getStartKey())
.getType();
return (
<div className="RichEditor-controls">
{BLOCK_TYPES.map(type => (
<StyleButton
key={type.label}
active={type.style === blockType}
label={type.label}
onToggle={props.onToggle}
style={type.style}
/>
))}
</div>
);
};
var INLINE_STYLES = [
{ label: "Bold", style: "BOLD" },
{ label: "Italic", style: "ITALIC" },
{ label: "Underline", style: "UNDERLINE" },
{ label: "Monospace", style: "CODE" }
];
const InlineStyleControls = props => {
var currentStyle = props.editorState.getCurrentInlineStyle();
return (
<div className="RichEditor-controls">
{INLINE_STYLES.map(type => (
<StyleButton
key={type.label}
active={currentStyle.has(type.style)}
label={type.label}
onToggle={props.onToggle}
style={type.style}
/>
))}
</div>
);
};
Previosly, I'm trying to implement react-quill Editor on similar way but there is problem that i can't bold text and i can't save to database more than 2 words. So I decided to try with DraftJS.
If you can help me I will be very thankful!
Related
const [application, setApplication] = useState([])
const [app, setApp] = useState([
{
id: null,
code: null,
name: null
}
]);
useEffect(() => {
let ignore = false;
(async function load() {
let response = await getAllData();
if (!ignore) setApplication(response['data'])
})()
return () => ignore = true;
},[]);
{
label: (
<div className="flex items-center">
<label className="flex-1">Application</label>
<div className="text-right">
<ButtonGroup>
<IconButton icon={<Icon icon="plus" />} onClick={() => appendApp()} />
<IconButton onClick={() => removeApp()} size="md" icon={<Icon icon="minus" />} style={{ display: app.length > 1 ? 'inline-block' : 'none' }} />
</ButtonGroup>
</div>
</div>
),
name: 'applications',
renderer: (data) => {
const { control, register, errors } = useFormContext();
return (
<div className="flex flex-col w-full">
{
app.map((item, index) => (
<div key={index} className="flex flex-col pb-2 -items-center">
<div className="flex pb-2 w-full">
<SelectPicker
placeholder="Select Application"
data={application['data']}
labelKey="name"
valueKey="code"
style={{ width: '100%' }}
disabledItemValues={Array.isArray(control.getValues()['applications']) ? control.getValues()['applications'].map(x => x.id) : []}
onChange={(value) => control.setValue('applications', _setApp(value, index, 'code'))}
value={control.getValues()['applications']?.code}
/>
</div>
</div>
))
}
</div>
)
}
const appendApp = () => {
let i = 0;
for (i = 0; i < noOfApp; i++) {
setApp(arr => [...arr, {
id: null,
code: null,
name: null,
role: null
}]);
return app;
}
}
const removeAppRole = () => {
setApp([...app.slice(0, -1)]);
}
const _setApp = (value, idx, status) => {
app[idx][status] = value;
setApp(app);
return app;
}
How do I add a validation on the select? for example when the select field is empty it should validation that it is required to select. also for example when there's a existing data which is like this:
data = [{
id: 1,
name: 'IOS',
code: 'ios'
}]
and how do I display this data on the select field? cause I have a create and edit.
when I try to edit it doesn't display the value.
I do not use the register, I prefer to use Controller, for these cases it is more practical, in the v7 of react-hook-form, see this example:
My select component:
import { ErrorMessage } from "#hookform/error-message";
import { IonItem, IonLabel, IonSelect, IonSelectOption } from "#ionic/react";
import { FunctionComponent } from "react";
import { Controller } from "react-hook-form";
import React from "react";
const Select: FunctionComponent<Props> = ({
options,
control,
errors,
defaultValue,
name,
label,
rules
}) => {
return (
<>
<IonItem className="mb-4">
<IonLabel position="floating" color="primary">
{label}
</IonLabel>
<Controller
render={({ field: { onChange, onBlur, value } }) => (
<IonSelect
value={value}
onIonChange={onChange}
onIonBlur={onBlur}
interface="action-sheet"
className="mt-2"
>
{options ? options.map((opcion) => {
return (
<IonSelectOption value={opcion.value} key={opcion.value}>
{opcion.label}
</IonSelectOption>
);
}):""}
</IonSelect>
)}
control={control}
name={name}
defaultValue={defaultValue}
rules={rules}
/>
</IonItem>
<ErrorMessage
errors={errors}
name={name}
as={<div className="text-red-600 px-6" />}
/>
</>
);
};
export default Select;
Use the component in other component:
import Select from "components/Select/Select";
import { useForm } from "react-hook-form";
import Scaffold from "components/Scaffold/Scaffold";
import React from "react";
let defaultValues = {
subjectId: "1"
};
const options = [
{
label: "Option1",
value: "1",
},
{
label: "Option2",
value: "2",
},
];
const ContactUs: React.FC = () => {
const {
control,
handleSubmit,
formState: { isSubmitting, isValid, errors },
} = useForm({
defaultValues: defaultValues,
mode: "onChange",
});
const handlerSendButton = async (select) => {
console.log(select);
};
const rulesSubject = {
required: "this field is required",
};
return (
<Scaffold>
<Scaffold.Content>
<h6 className="text-2xl font-bold text-center">
Contact us
</h6>
<Select
control={control}
errors={errors}
defaultValue={defaultValues.subjectId}
options={options}
name="subjectId"
label={"Subject"}
rules={rulesSubject}
/>
</Scaffold.Content>
<Scaffold.Footer>
<Button
onClick={handleSubmit(handlerSendButton)}
disabled={!isValid || isSubmitting}
>
Save
</Button>
</Scaffold.Footer>
</Scaffold>
);
};
In this case i use Ionic for the UI but you can use MaterialUI, ReactSuite o other framework, its the same.
I hope it helps you, good luck.
EDIT
A repository : Ionic React Select Form Hook
A codeSandBox: Ionic React Select Form Hook
I am using react hooks mostly in my current app. I need the below code expressed as react hooks, without the this.state and this.props. My current app is expressed entirely as React Hooks. If you see it closely, the below code is writing out SQL query code with the click of a button. I need that functionality in my current app, but I don't know how to assimilate that in my app. Any ideas?
import React from 'react';
import { Row, Col, Tabs, Spin, Card, Alert, Tooltip, Icon, Button } from 'antd';
import cubejs from '#cubejs-client/core';
import { QueryRenderer } from '#cubejs-client/react';
import sqlFormatter from "sql-formatter";
import JSONPretty from 'react-json-pretty';
import Prism from "prismjs";
import "./css/prism.css";
const HACKER_NEWS_DATASET_API_KEY = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpIjozODU5NH0.5wEbQo-VG2DEjR2nBpRpoJeIcE_oJqnrm78yUo9lasw'
class PrismCode extends React.Component {
componentDidMount() {
Prism.highlightAll();
}
componentDidUpdate() {
Prism.highlightAll();
}
render() {
return (
<pre>
<code className='language-javascript'>
{ this.props.code }
</code>
</pre>
)
}
}
const tabList = [{
key: 'code',
tab: 'Code'
}, {
key: 'sqlQuery',
tab: 'Generated SQL'
}, {
key: 'response',
tab: 'Response'
}];
class CodeExample extends React.Component {
constructor(props) {
super(props);
this.state = { activeTabKey: 'code' };
}
onTabChange(key) {
this.setState({ activeTabKey: key });
}
render() {
const { codeExample, resultSet, sqlQuery } = this.props;
const contentList = {
code: <PrismCode code={codeExample} />,
response: <PrismCode code={JSON.stringify(resultSet, null, 2)} />,
sqlQuery: <PrismCode code={sqlQuery && sqlFormatter.format(sqlQuery.sql())} />
};
return (<Card
type="inner"
tabList={tabList}
activeTabKey={this.state.activeTabKey}
onTabChange={(key) => { this.onTabChange(key, 'key'); }}
>
{ contentList[this.state.activeTabKey] }
</Card>);
}
}
const Loader = () => (
<div style={{textAlign: 'center', marginTop: "50px" }}>
<Spin size="large" />
</div>
)
const TabPane = Tabs.TabPane;
class Example extends React.Component {
constructor(props) {
super(props);
this.state = { showCode: false };
}
render() {
const { query, codeExample, render, title } = this.props;
return (
<QueryRenderer
query={query}
cubejsApi={cubejs(HACKER_NEWS_DATASET_API_KEY)}
loadSql
render={ ({ resultSet, sqlQuery, error, loadingState }) => {
if (error) {
return <Alert
message="Error occured while loading your query"
description={error.message}
type="error"
/>
}
if (resultSet && !loadingState.isLoading) {
return (<Card
title={title || "Example"}
extra={<Button
onClick={() => this.setState({ showCode: !this.state.showCode })}
icon="code"
size="small"
type={this.state.showCode ? 'primary' : 'default'}
>{this.state.showCode ? 'Hide Code' : 'Show Code'}</Button>}
>
{render({ resultSet, error })}
{this.state.showCode && <CodeExample resultSet={resultSet} codeExample={codeExample} sqlQuery={sqlQuery}/>}
</Card>);
}
return <Loader />
}}
/>
);
}
};
export default Example;
It's quite easy to convert a class to a functional component.
Remember these steps:
class => const
// Class
export class Example
// FC
export const Example
componentLifeCycles => useEffect
// Class lifecycle
componentDidMount() {
// logic here
}
// FC
useEffect(() => {
// logic here
})
render => return
// Class
render () {
return (<Component/>)
}
// FC
return (<Component />)`
constructor => useState
// Class
constructor(props) {
this.state.val = props.val
}
// FC
const [val, setVal] = useState(props.val)
setState => second arg from useState
// Class
constructor() {
this.state.val = val // constructor
}
this.setState({ val }) // class
// FC
const[val, setVal] = useState(null)
setVal("someVal")
TLDR: Solution
import React, { useEffect, useState } from "react"
import { Tabs, Spin, Card, Alert, Button } from "antd"
import cubejs from "#cubejs-client/core"
import { QueryRenderer } from "#cubejs-client/react"
import sqlFormatter from "sql-formatter"
import Prism from "prismjs"
import "./css/prism.css"
const HACKER_NEWS_DATASET_API_KEY =
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpIjozODU5NH0.5wEbQo-VG2DEjR2nBpRpoJeIcE_oJqnrm78yUo9lasw"
const PrismCode: React.FC = ({ code }) => {
useEffect(() => {
Prism.highlightAll()
})
return (
<pre>
<code className="language-javascript">{code}</code>
</pre>
)
}
const TabList = [
{
key: "code",
tab: "Code",
},
{
key: "sqlQuery",
tab: "Generated SQL",
},
{
key: "response",
tab: "Response",
},
]
const CodeExample: React.FC = ( { codeExample, resultSet, sqlQuery } ) => {
const [activeTabKey, setActiveTab] = useState("code")
const onTabChange = (key) => setActiveTab(key)
const contentList = {
code: <PrismCode code={codeExample} />,
response: <PrismCode code={JSON.stringify(resultSet, null, 2)} />,
sqlQuery: (
<PrismCode code={sqlQuery && sqlFormatter.format(sqlQuery.sql())} />
),
}
return (
<Card
type="inner"
tabList={TabList}
activeTabKey={activeTabKey}
onTabChange={(key) => {
onTabChange(key)
}}
>
{contentList[activeTabKey]}
</Card>
)
}
const Loader = () => (
<div style={{ textAlign: "center", marginTop: "50px" }}>
<Spin size="large" />
</div>
)
const TabPane = Tabs.TabPane
const Example: React.FC = ({ query, codeExample, render, title }) => {
const [showCode, toggleCode] = useState(false)
return (
<QueryRenderer
query={query}
cubejsApi={cubejs(HACKER_NEWS_DATASET_API_KEY)}
loadSql
render={({ resultSet, sqlQuery, error, loadingState }) => {
if (error) {
return (
<Alert
message="Error occured while loading your query"
description={error.message}
type="error"
/>
)
}
if (resultSet && !loadingState.isLoading) {
return (
<Card
title={title || "Example"}
extra={
<Button
onClick={() =>
toggleCode(!this.state.showCode)
}
icon="code"
size="small"
type={showCode ? "primary" : "default"}
>
{showCode ? "Hide Code" : "Show Code"}
</Button>
}
>
{render({ resultSet, error })}
{showCode && (
<CodeExample
resultSet={resultSet}
codeExample={codeExample}
sqlQuery={sqlQuery}
/>
)}
</Card>
)
}
return <Loader />
}}
/>
)
}
export default Example
I'm trying to figure out how to use the DatePicker from antd with react-hook-form.
Currently, my attempt is:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import useForm from "react-hook-form";
import { withRouter } from "react-router-dom";
import { useStateMachine } from "little-state-machine";
import updateAction from "./updateAction";
import { Input as InputField, Form, Button, DatePicker, Divider, Layout, Typography, Skeleton, Switch, Card, Icon, Avatar } from 'antd';
import Select from "react-select";
const { Content } = Layout
const { Text, Paragraph } = Typography;
const { Meta } = Card;
const { MonthPicker, RangePicker, WeekPicker } = DatePicker;
const Team = props => {
const { register, handleSubmit, setValue, errors } = useForm();
const [ dueDate, setDate ] = useState(new Date());
const [indexes, setIndexes] = React.useState([]);
const [counter, setCounter] = React.useState(0);
const { action } = useStateMachine(updateAction);
const onSubit = data => {
action(data);
props.history.push("./ProposalBudget");
};
// const handleChange = dueDate => setDate(date);
const handleChange = (e) => {
setValue("dueDate", e.target.value);
}
const onSubmit = data => {
console.log(data);
};
const addMilestone = () => {
setIndexes(prevIndexes => [...prevIndexes, counter]);
setCounter(prevCounter => prevCounter + 1);
};
const removeMilestone = index => () => {
setIndexes(prevIndexes => [...prevIndexes.filter(item => item !== index)]);
};
const clearMilestones = () => {
setIndexes([]);
};
useEffect(() => {
register({ name: dueDate }); // custom register antd input
}, [register]);
Note: i have also tried name: {${fieldName}.dueDate - that doesn't work either.
return (
<div>
<HeaderBranding />
<Content
style={{
background: '#fff',
padding: 24,
margin: "auto",
minHeight: 280,
width: '70%'
}}
>
<form onSubmit={handleSubmit(onSubit)}>
{indexes.map(index => {
const fieldName = `milestones[${index}]`;
return (
<fieldset name={fieldName} key={fieldName}>
<label>
Title:
<input
type="text"
name={`${fieldName}.title`}
ref={register}
/>
</label>
<label>
Description:
<textarea
rows={12}
name={`${fieldName}.description`}
ref={register}
/>
</label>
<label>When do you expect to complete this milestone? <br />
<DatePicker
selected={ dueDate }
// ref={register}
InputField name={`${fieldName}.dueDate`}
onChange={handleChange(index)}
//onChange={ handleChange }
>
<input
type="date"
name={`${fieldName}.dueDate`}
inputRef={register}
/>
</DatePicker>
</label>
<Button type="danger" style={{ marginBottom: '20px', float: 'right'}} onClick={removeMilestone(index)}>
Remove this Milestone
</Button>
</fieldset>
);
})}
<Button type="primary" style={{ marginBottom: '20px'}} onClick={addMilestone}>
Add a Milestone
</Button>
<br />
<Button type="button" style={{ marginBottom: '20px'}} onClick={clearMilestones}>
Clear Milestones
</Button>
<input type="submit" value="next - budget" />
</form>
</Content>
</div>
);
};
export default withRouter(Team);
This generates an error that says: TypeError: Cannot read property 'value' of undefined
setValue is defined in handleChange.
I'm not clear on what steps are outstanding to get this datepicker functioning. Do I need a separate select function?
Has anyone figured out how to plug this datepicker in?
I have also tried:
const handleChange = (e) => {
setValue("dueDate", e.target.Date);
}
and I have tried:
const handleChange = (e) => {
setValue("dueDate", e.target.date);
}
but each of these generations the same error
I have built a wrapper component to work with external controlled component easier:
https://github.com/react-hook-form/react-hook-form-input
import React from 'react';
import useForm from 'react-hook-form';
import { RHFInput } from 'react-hook-form-input';
import Select from 'react-select';
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
function App() {
const { handleSubmit, register, setValue, reset } = useForm();
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<RHFInput
as={<Select options={options} />}
rules={{ required: true }}
name="reactSelect"
register={register}
setValue={setValue}
/>
<button
type="button"
onClick={() => {
reset({
reactSelect: '',
});
}}
>
Reset Form
</button>
<button>submit</button>
</form>
);
}
try this out, let me know if it makes your life easier with AntD.
/* eslint-disable react/prop-types */
import React, { useState } from 'react';
import { DatePicker } from 'antd';
import { Controller } from 'react-hook-form';
import color from '../../assets/theme/color';
import DatePickerContainer from './DatePickerContainer';
function DatePickerAntd(props) {
const { control, rules, required, title, ...childProps } = props;
const { name } = childProps;
const [focus, setFocus] = useState(false);
const style = {
backgroundColor: color.white,
borderColor: color.primary,
borderRadius: 5,
marginBottom: '1vh',
marginTop: '1vh',
};
let styleError;
if (!focus && props.error) {
styleError = { borderColor: color.red };
}
return (
<div>
<Controller
as={
<DatePicker
style={{ ...style, ...styleError }}
size="large"
format="DD-MM-YYYY"
placeholder={props.placeholder || ''}
onBlur={() => {
setFocus(false);
}}
onFocus={() => {
setFocus(true);
}}
name={name}
/>
}
name={name}
control={control}
rules={rules}
onChange={([selected]) => ({ value: selected })}
/>
</div>
);
}
export default DatePickerAntd;
my container parent use react-hooks-form
const { handleSubmit, control, errors, reset, getValues } = useForm({
mode: 'onChange',
validationSchema: schema,
});
<DatePickerAntd
name="deadline"
title={messages.deadline}
error={errors.deadline}
control={control}
required={isFieldRequired(schema, 'deadline')}
/>
like that, its working for me ;-)
Try this:
<DatePicker
selected={ dueDate }
// ref={register}
InputField name={`${fieldName}.dueDate`}
onChange={()=>handleChange(index)}
//onChange={ handleChange }
>
<input
type="date"
name={`${fieldName}.dueDate`}
inputRef={register}
/>
It looks like if you are using onChange={handleChange(index)} it does not pass a function instead you are passing an execution result of that function.
And if you are trying to access event inside handleChange, you should manually pass if from binding scope otherwise, it will be undefined.
onChange={()=>handleChange(index, event)}
I've got a form with some text inputs, and some custom components. I've got the Formik validation working on the text inputs but not the custom components. I am now trying to add Formik validation to my custom categoriesMultiselect component. This component holds its data in the redux store. I have handled the validation myself and added a value to my redux props:
const mapStateToProps = (
state: RecordOf<VepoState>,
ownProps: { rerenderKey: boolean }
) => ({...
isCategoriesValid: selectIsCategoriesValid(state),
...
})
. I just want to plug props.isCategoriesValid into my Formik form.
From reading the official documentation, I think I do that by adding validate={validateCategories} as a prop to the custom component and adding the function validateCategories to the component. So I have tried that like this:
//above React render()
validateCategories = () => {
let error
if (!this.props.selectIsCategoriesValid) {
error = 'please select a category'
}
return error
}
// Inside React render()
<CategoriesMultiselect.View
validate={this.validateCategories}
name={'categories'}
label={'Product Categories'}
categoryCodes={[CategoryEnums.CategoryCodes.Grocery]}
/>
validateCategories never gets run. That's why I have tested running it by adding validateField to one of my inputs onChange function:
<Input
label={'Product Brand'}
value={values.brand}
onTouch={setFieldTouched}
error={touched.brand && errors.brand}
placeholder="Enter Brand"
name="brand"
required
onChange={() => validateField('categories')}
deleteText={setFieldValue}
/>
When it tries to validate the field, I get console error:
Cannot read property 'validate' of undefined
on this line of code in Formik:
var validateField = useEventCallback(function (name) {
if (isFunction(fieldRegistry.current[name].validate)) {
I have at least plugged Formik into my Redux because I am at least successfully dispatching a Redux action when submitting the form. What am I doing wrong?
Code:
//#flow
import * as Yup from 'yup'
import { Formik, withFormik } from 'formik'
import { Container } from 'native-base'
import * as React from 'react'
import { ScrollView, View, Alert, Button } from 'react-native'
import { connect } from 'react-redux'
import { Category as CategoryEnums } from 'src/enums'
import type { VepoState } from 'src/components/model'
import type { RecordOf } from 'immutable'
import type { Product } from 'src/model'
import VepoHeader from 'src/components/formControls/header/view'
import { selectIsAddFormValid } from './selector'
import { selectProduct } from './selector'
// import { Button } from 'src/components/formControls'
import { ImagePicker } from 'src/components/formControls'
import LocationAutocomplete from 'src/components/formControls/locationAutocomplete/view'
import { uploadAddProduct, updateRerenderKey } from './action'
import { viewStyle } from './style'
import type { Dispatch } from 'redux'
import { updateAddProductImage } from './action'
import type { Place } from 'src/model/location'
import { Colors, Spacing } from 'src/styles'
import { Input } from 'src/components/formControls'
import { onPress } from './controller'
import { CategoriesMultiselect } from 'src/components/formControls'
import {
selectIsGrocerySelected,
selectIsCategoriesValid,
isLocationValid
} from 'src/components/product/add/groceryItem/selector'
const mapStateToProps = (
state: RecordOf<VepoState>,
ownProps: { rerenderKey: boolean }
) => ({
locationListDisplayed: state.formControls.root.locationListDisplayed,
isAddFormValid: selectIsAddFormValid(state),
// $FlowFixMe
product: selectProduct(state),
// $FlowFixMe
isGrocerySelected: selectIsGrocerySelected(state),
// $FlowFixMe
categories: state.formControls.categories,
isCategoriesValid: selectIsCategoriesValid(state),
image: state.product.add.image,
rerenderKey: ownProps.rerenderKey,
location: state.formControls.location,
isLocationValid: isLocationValid(state)
})
// eslint-disable-next-line flowtype/no-weak-types
const mapDispatchToProps = (dispatch: Dispatch<*>): Object => ({
updateAddProductImage: (value): void => {
dispatch(updateAddProductImage({ value }))
},
uploadAddProduct: (product: Product): void => {
dispatch(uploadAddProduct(product))
},
updateRerenderKey: () => {
dispatch(updateRerenderKey())
}
})
export const getLocationIsValid = (place: Place): boolean => {
return Object.keys(place).length > 0 ? true : false
}
type AddGroceryStoreState = {
name: string,
brand: string,
description: string,
price?: number
}
class AddGroceryItemView extends React.Component<any, AddGroceryStoreState> {
validateCategories = () => {
let error
if (!this.props.selectIsCategoriesValid) {
error = 'please select a category'
}
return error
}
render() {
const {
values,
handleSubmit,
setFieldValue,
errors,
touched,
setFieldTouched,
isValid,
isSubmitting,
validateField
} = this.props
return (
<Container>
<VepoHeader title={'Add Vegan Grocery Product'} />
<Container style={container}>
<ScrollView
keyboardShouldPersistTaps="always"
style={viewStyle(this.props.locationListDisplayed).scrollView}>
<View>
<LocationAutocomplete
label={'Grocery Store'}
placeHolder={'Enter Grocery Store'}
/>
</View>
<View style={viewStyle().detailsContainer}>
<ImagePicker
label={'Product Image (optional)'}
image={this.props.image.image}
updateAddProductImage={this.props.updateAddProductImage}
updateRerenderKey={this.props.updateRerenderKey}
/>
<Input
label={'Product Name'}
onTouch={setFieldTouched}
value={values.name}
placeholder="Enter Name"
name="name"
required
error={touched.name && errors.name}
deleteText={setFieldValue}
onChange={setFieldValue}
/>
<Input
label={'Product Brand'}
value={values.brand}
onTouch={setFieldTouched}
error={touched.brand && errors.brand}
placeholder="Enter Brand"
name="brand"
required
onChange={() => validateField('categories')}
deleteText={setFieldValue}
/>
<View>
<Input
label={'Product Description'}
value={values.description}
placeholder="Enter Description"
multiline={true}
required
onTouch={setFieldTouched}
error={touched.description && errors.description}
numberOfLines={4}
name="description"
deleteText={setFieldValue}
onChange={setFieldValue}
/>
<Input
isValid={true}
isPrice={true}
label={'Product Price'}
value={values.price}
onTouch={setFieldTouched}
error={touched.price && errors.price}
placeholder="Enter Price"
name="price"
deleteText={setFieldValue}
onChange={setFieldValue}
/>
<View>
<CategoriesMultiselect.View
validate={this.validateCategories}
name={'categories'}
label={'Product Categories'}
categoryCodes={[CategoryEnums.CategoryCodes.Grocery]}
/>
</View>
</View>
</View>
</ScrollView>
</Container>
<Button
title="submit"
onPress={handleSubmit}
disabled={!isValid || isSubmitting}
loading={isSubmitting}
/>
{/* <Button.View onSub={this._handleSubmit} onPress={this._handleSubmit} label={'GO!'} /> */}
</Container>
)
}
}
const container = {
flex: 1,
...Spacing.horizontalPaddingLarge,
backgroundColor: Colors.greyLight,
flexDirection: 'column'
}
const formikEnhancer = withFormik({
validationSchema: Yup.object().shape({
name: Yup.string().required(),
brand: Yup.string().required(),
categories: Yup.array(),
description: Yup.string()
.min(9)
.required(),
price: Yup.number()
.typeError('price must be a number')
.required()
}),
mapPropsToValues: () => ({
name: '',
brand: '',
description: '',
price: '',
categories: []
}),
handleSubmit: (values, { props }) => {
props.updateRerenderKey()
},
displayName: 'AddGroceryItemView'
})(AddGroceryItemView)
// $FlowFixMe
const AddGroceryItemViewComponent = connect(
mapStateToProps,
mapDispatchToProps
)(formikEnhancer)
export default AddGroceryItemViewComponent
As Rikin requested, here is the CategoriesMultiselect component:
//#flow
import type { Node } from 'react'
import { selectSelectedCategory } from 'src/components/product/add/groceryItem/selector'
import type { VepoState } from 'src/components/model'
import type { RecordOf } from 'immutable'
import { connect } from 'react-redux'
import * as React from 'react'
import { View } from 'react-native'
import {
List,
ListItem,
Text,
Left,
Body,
Right,
Button,
Container,
Label,
Title,
Content
} from 'native-base'
import Icon from 'react-native-vector-icons/FontAwesome'
import Eicon from 'react-native-vector-icons/EvilIcons'
import Modal from 'react-native-modal'
import SelectMultiple from 'react-native-select-multiple'
import {
updateAlertModalIsOpen,
updateAlertModalHasYesNo,
updateAlertModalMessage,
updateAlertModalTitle
} from 'src/components/formControls/alertModal/action'
import * as C from './model'
import type { Subcategory } from 'src/model/category'
import * as controller from './controller'
import { getIsCategoriesValid } from './controller'
import { styles } from 'src/components/style'
import {
Colors,
Corners,
Distances,
Modals,
Spacing,
Typography,
ZIndexes
} from 'src/styles'
import { Containers } from '../../../styles'
import {
toggleSubcategory,
setAllShowSubcategoriesToFalse,
toggleShowSubcategories
} from './action'
import type { Dispatch } from 'redux'
const mapStateToProps = (state: RecordOf<VepoState>) => ({
vepo: state,
// $FlowFixMe
selectedCategory: selectSelectedCategory(state),
categories: state.formControls.categories
})
// eslint-disable-next-line flowtype/no-weak-types
const mapDispatchToProps = (dispatch: Dispatch<*>): Object => ({
setAllShowSubcategoriesToFalse: (): void => {
dispatch(setAllShowSubcategoriesToFalse())
},
toggleSubcategory: (sc): void => {
return dispatch(toggleSubcategory(sc))
},
toggleShowSubcategories: (c): void => {
dispatch(toggleShowSubcategories(c))
},
updateAlertModalIsOpen: (isOpen: boolean): void => {
dispatch(updateAlertModalIsOpen(isOpen))
},
updateAlertModalMessage: (message: string): void => {
dispatch(updateAlertModalMessage(message))
},
updateAlertModalHasYesNo: (hasYesNo: boolean): void => {
dispatch(updateAlertModalHasYesNo(hasYesNo))
},
updateAlertModalTitle: (title: string): void => {
dispatch(updateAlertModalTitle(title))
}
})
const renderCategoryRow = (props: C.CategoriesViewProps, item: C.Category) => {
return (
<View>
<ListItem
style={listItem}
icon
onPress={() => controller.categoryClicked(props, item)}>
<Left>
<Icon
style={styles.icon}
name={item.icon}
size={20}
color={item.iconColor}
/>
</Left>
<Body style={[styles.formElementHeight, border(item)]}>
<Text style={Typography.brownLabel}>{item.label}</Text>
</Body>
<Right style={[styles.formElementHeight, border(item)]}>
<Eicon style={catStyle.arrow} name="chevron-right" size={30} />
</Right>
</ListItem>
</View>
)
}
const getCategoriesToDisplay = (props) => {
const y = props.categories.filter((x) => props.categoryCodes.includes(x.code))
return y
}
class CategoriesMultiselectView extends React.Component {
setFormCategories = () => {
if (this.props && this.props.setFieldValue) {
this.props.setFieldValue(
'categories',
controller.getSelectedSubcategories(this.props.categories)
)
}
}
render(): React.Node {
const categoriesToDisplay = getCategoriesToDisplay(this.props)
return (
<View>
<View style={{ ...Containers.fullWidthRow }}>
<Label disabled={false} style={Typography.formLabel}>
{this.props.label}
</Label>
<View style={{ ...Containers.fullWidthRow }} />
<Label disabled={false} style={Typography.formLabel}>
{controller.getNumberOfSelectedSubcategories(this.props.categories)}{' '}
Selected
</Label>
</View>
<View
style={catStyle.categoriesViewStyle(this.props, categoriesToDisplay)}>
{this.props.categories && this.props.categories.length > 0 && (
<List
listBorderColor={'white'}
style={categoriesListStyle}
dataArray={categoriesToDisplay}
renderRow={(item: C.Category) => {
return renderCategoryRow(this.props, item)
}}
/>
)}
<View style={catStyle.modalConatinerStyle} />
<Modal
style={catStyle.modal}
onModalHide={this.setFormCategories}
isVisible={
this.props.categories
? this.props.categories.some((cat: C.Category) =>
controller.showModal(cat)
)
: false
}>
<Container style={catStyle.modalView}>
<View style={Modals.modalHeader}>
<Title style={catStyle.categoriesTitleStyle}>
{controller.getDisplayedCategoryLabel(this.props.categories)}
</Title>
<Right>
<Button
transparent
icon
onPress={this.props.setAllShowSubcategoriesToFalse}>
<Eicon name="close-o" size={25} color="#FFFFFF" />
</Button>
</Right>
</View>
<Content style={catStyle.categoryStyle.modalContent}>
<SelectMultiple
checkboxSource={require('../../../images/unchecked.png')}
selectedCheckboxSource={require('../../../images/checked.png')}
labelStyle={[
styles.label,
styles.formElementHeight,
styles.modalListItem
]}
items={controller.getDisplayedSubcategories(
this.props.categories
)}
selectedItems={controller.getSelectedSubcategories(
this.props.categories
)}
onSelectionsChange={(selections, item: Subcategory) => {
this.props.toggleSubcategory({ subcategory: item }).the
}}
/>
</Content>
</Container>
</Modal>
</View>
{this.props.error && (
<Label
disabled={false}
style={[
Typography.formLabel,
{ color: 'red' },
{ marginBottom: Spacing.medium }
]}>
{this.props.error}
</Label>
)}
</View>
)
}
}
const catStyle = {
// eslint-disable-next-line no-undef
getBorderBottomWidth: (item: C.Category): number => {
if (item.icon === 'shopping-basket') {
return Spacing.none
}
return Spacing.none
},
// eslint-disable-next-line no-undef
categoriesViewStyle: (props: C.CategoriesViewProps, categoriesToDisplay) => {
return {
backgroundColor: Colors.borderLeftColor(
getIsCategoriesValid(props.categories)
),
...Corners.rounded,
paddingLeft: Spacing.medium,
height: Distances.FormElementHeights.Medium * categoriesToDisplay.length,
overflow: 'hidden',
borderBottomWidth: Spacing.none
}
},
arrow: {
color: Colors.brownDark,
borderBottomColor: Colors.brownDark
},
icon: { height: Distances.FormElementHeights.Medium },
// eslint-disable-next-line no-undef
categoriesTitleStyle: {
...styles.title,
...Typography.titleLeftAlign
},
categoryStyle: {
modalContent: {
...Corners.rounded
}
},
modal: {
flex: 0.7,
height: 20,
marginTop: Spacing.auto,
marginBottom: Spacing.auto
},
modalView: {
backgroundColor: Colors.white,
height: 500,
...Corners.rounded
},
modalConatinerStyle: {
marginBottom: Spacing.medium,
color: Colors.brownDark,
backgroundColor: Colors.brownLight,
position: 'absolute',
zIndex: ZIndexes.Layers.Negative,
right: Spacing.none,
height: Distances.Distances.Full,
width: Distances.Distances.Full,
...Corners.rounded
}
}
const categoriesListStyle = {
flex: Distances.FlexDistances.Full,
color: Colors.brownDark,
backgroundColor: Colors.brownLight,
height: Distances.FormElementHeights.Double,
...Corners.notRounded,
marginRight: Spacing.medium
}
const border = (item: C.Category) => {
return {
borderBottomWidth: catStyle.getBorderBottomWidth(item),
borderBottomColor: Colors.brownMedium
}
}
const listItem = {
height: Distances.FormElementHeights.Medium
}
// $FlowFixMe
const CategoriesMultiselect = connect(
mapStateToProps,
mapDispatchToProps
)(CategoriesMultiselectView)
export default CategoriesMultiselect
Example of form level validation usage by directly setting property in the form categories with error message.
...
...
...
const validateCategories = (values, props) => {
let error = {}
if (!props.selectIsCategoriesValid) {
error.categories = 'please select a category'
}
return error
}
class AddGroceryItemView extends React.Component<any, AddGroceryStoreState> {
render() {
const { ... } = this.props
return (
<Container>
<VepoHeader title={'Add Vegan Grocery Product'} />
<Container style={container}>
<ScrollView
keyboardShouldPersistTaps="always"
style={viewStyle(this.props.locationListDisplayed).scrollView}>
<View>
...
</View>
<View style={viewStyle().detailsContainer}>
...
<View>
...
<View>
<CategoriesMultiselect.View
// validate={this.validateCategories}
name={'categories'}
label={'Product Categories'}
categoryCodes={[CategoryEnums.CategoryCodes.Grocery]}
/>
</View>
</View>
</View>
</ScrollView>
</Container>
...
</Container>
)
}
}
...
const formikEnhancer = withFormik({
validationSchema: Yup.object().shape({
...
}),
mapPropsToValues: () => ({
...
}),
handleSubmit: (values, { props }) => {
...
},
displayName: 'AddGroceryItemView',
validate: validateCategories
})(AddGroceryItemView)
// $FlowFixMe
const AddGroceryItemViewComponent = connect(
mapStateToProps,
mapDispatchToProps
)(formikEnhancer)
export default AddGroceryItemViewComponent
Updated Field level validation using Formik's Field:
However personally I would go with Form level validation as first line of defense you are relying on validationSchema which should take care of field level validation at first and then second line of defense you should go with form-level where you can place custom messaging after validationSchema passes the test. If you put in field level then you may end up in possibly rabbit hole where it may lead to difficulty in maintaining components and customize it for individual scenarios in your app. To me form level validation is explicit enough at one convenient place for all additional field level validation. Alternatively you can also put in all your validationSchema and form-level validation function in a single file and then import it in your main component where you are going to wrap withFormik HOC.
Either way its upto you on how you want based on your requirements.
Here's the official docs link: https://jaredpalmer.com/formik/docs/guides/validation#field-level-validation
And as per that:
Note: The / components' validate function will only
be executed on mounted fields. That is to say, if any of your fields
unmount during the flow of your form (e.g. Material-UI's
unmounts the previous your user was on), those fields will not
be validated during form validation/submission.
//#flow
...
import SelectMultiple from 'react-native-select-multiple'
...
import {
toggleSubcategory,
setAllShowSubcategoriesToFalse,
toggleShowSubcategories
} from './action'
...
import { Field } from 'formik'
...
class CategoriesMultiselectView extends React.Component {
setFormCategories = () => {
if (this.props && this.props.setFieldValue) {
this.props.setFieldValue(
'categories',
controller.getSelectedSubcategories(this.props.categories)
)
}
}
render(): React.Node {
const categoriesToDisplay = getCategoriesToDisplay(this.props)
return (
<View>
<View style={{ ...Containers.fullWidthRow }}>
...
</View>
<View
style={catStyle.categoriesViewStyle(this.props, categoriesToDisplay)}>
{...}
<View style={catStyle.modalConatinerStyle} />
<Modal
style={catStyle.modal}
onModalHide={this.setFormCategories}
isVisible={
this.props.categories
? this.props.categories.some((cat: C.Category) =>
controller.showModal(cat)
)
: false
}>
<Container style={catStyle.modalView}>
<View style={Modals.modalHeader}>
...
</View>
<Content style={catStyle.categoryStyle.modalContent}>
<Field name="categories" validate={validate_Function_HERE_which_can_be_via_props_or_locally_defined} render={({field, form}) =>
<SelectMultiple
checkboxSource={require('../../../images/unchecked.png')}
selectedCheckboxSource={require('../../../images/checked.png')}
labelStyle={[
styles.label,
styles.formElementHeight,
styles.modalListItem
]}
items={controller.getDisplayedSubcategories(
this.props.categories
)}
selectedItems={controller.getSelectedSubcategories(
this.props.categories
)}
onSelectionsChange={(selections, item: Subcategory) => {
this.props.toggleSubcategory({ subcategory: item }).the
}}
/>}
/>
</Content>
</Container>
</Modal>
</View>
{this.props.error && (
<Label
disabled={false}
style={[
Typography.formLabel,
{ color: 'red' },
{ marginBottom: Spacing.medium }
]}>
{this.props.error}
</Label>
)}
</View>
)
}
}
...
// $FlowFixMe
const CategoriesMultiselect = connect(
mapStateToProps,
mapDispatchToProps
)(CategoriesMultiselectView)
export default CategoriesMultiselect
Anyone know how to change the fontSize of the TableHeaderRow in a DevExtreme React Grid?
Here's an example of code from the website (https://devexpress.github.io/devextreme-reactive/react/grid/demos/featured/data-editing/) that I have been working with
import * as React from 'react';
import {
SortingState, EditingState, PagingState,
IntegratedPaging, IntegratedSorting,
} from '#devexpress/dx-react-grid';
import {
Grid,
Table, TableHeaderRow, TableEditRow, TableEditColumn,
PagingPanel, DragDropProvider, TableColumnReordering,
} from '#devexpress/dx-react-grid-material-ui';
import Paper from '#material-ui/core/Paper';
import Dialog from '#material-ui/core/Dialog';
import DialogActions from '#material-ui/core/DialogActions';
import DialogContent from '#material-ui/core/DialogContent';
import DialogContentText from '#material-ui/core/DialogContentText';
import DialogTitle from '#material-ui/core/DialogTitle';
import Button from '#material-ui/core/Button';
import IconButton from '#material-ui/core/IconButton';
import Input from '#material-ui/core/Input';
import Select from '#material-ui/core/Select';
import MenuItem from '#material-ui/core/MenuItem';
import TableCell from '#material-ui/core/TableCell';
import DeleteIcon from '#material-ui/icons/Delete';
import EditIcon from '#material-ui/icons/Edit';
import SaveIcon from '#material-ui/icons/Save';
import CancelIcon from '#material-ui/icons/Cancel';
import { withStyles } from '#material-ui/core/styles';
import { ProgressBarCell } from '../../../theme-sources/material-ui/components/progress-bar-cell';
import { HighlightedCell } from '../../../theme-sources/material-ui/components/highlighted-cell';
import { CurrencyTypeProvider } from '../../../theme-sources/material-ui/components/currency-type-provider';
import { PercentTypeProvider } from '../../../theme-sources/material-ui/components/percent-type-provider';
import {
generateRows,
globalSalesValues,
} from '../../../demo-data/generator';
const styles = theme => ({
lookupEditCell: {
paddingTop: theme.spacing.unit * 0.875,
paddingRight: theme.spacing.unit,
paddingLeft: theme.spacing.unit,
},
dialog: {
width: 'calc(100% - 16px)',
},
inputRoot: {
width: '100%',
},
});
const AddButton = ({ onExecute }) => (
<div style={{ textAlign: 'center' }}>
<Button
color="primary"
onClick={onExecute}
title="Create new row"
>
New
</Button>
</div>
);
const EditButton = ({ onExecute }) => (
<IconButton onClick={onExecute} title="Edit row">
<EditIcon />
</IconButton>
);
const DeleteButton = ({ onExecute }) => (
<IconButton onClick={onExecute} title="Delete row">
<DeleteIcon />
</IconButton>
);
const CommitButton = ({ onExecute }) => (
<IconButton onClick={onExecute} title="Save changes">
<SaveIcon />
</IconButton>
);
const CancelButton = ({ onExecute }) => (
<IconButton color="secondary" onClick={onExecute} title="Cancel changes">
<CancelIcon />
</IconButton>
);
const commandComponents = {
add: AddButton,
edit: EditButton,
delete: DeleteButton,
commit: CommitButton,
cancel: CancelButton,
};
const Command = ({ id, onExecute }) => {
const CommandButton = commandComponents[id];
return (
<CommandButton
onExecute={onExecute}
/>
);
};
const availableValues = {
product: globalSalesValues.product,
region: globalSalesValues.region,
customer: globalSalesValues.customer,
};
const LookupEditCellBase = ({
availableColumnValues, value, onValueChange, classes,
}) => (
<TableCell
className={classes.lookupEditCell}
>
<Select
value={value}
onChange={event => onValueChange(event.target.value)}
input={(
<Input
classes={{ root: classes.inputRoot }}
/>
)}
>
{availableColumnValues.map(item => (
<MenuItem key={item} value={item}>
{item}
</MenuItem>
))}
</Select>
</TableCell>
);
export const LookupEditCell = withStyles(styles, { name: 'ControlledModeDemo' })(LookupEditCellBase);
const Cell = (props) => {
const { column } = props;
if (column.name === 'discount') {
return <ProgressBarCell {...props} />;
}
if (column.name === 'amount') {
return <HighlightedCell {...props} />;
}
return <Table.Cell {...props} />;
};
const EditCell = (props) => {
const { column } = props;
const availableColumnValues = availableValues[column.name];
if (availableColumnValues) {
return <LookupEditCell {...props} availableColumnValues={availableColumnValues} />;
}
return <TableEditRow.Cell {...props} />;
};
const getRowId = row => row.id;
class DemoBase extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
columns: [
{ name: 'product', title: 'Product' },
{ name: 'region', title: 'Region' },
{ name: 'amount', title: 'Sale Amount' },
{ name: 'discount', title: 'Discount' },
{ name: 'saleDate', title: 'Sale Date' },
{ name: 'customer', title: 'Customer' },
],
tableColumnExtensions: [
{ columnName: 'amount', align: 'right' },
],
rows: generateRows({
columnValues: { id: ({ index }) => index, ...globalSalesValues },
length: 12,
}),
sorting: [],
editingRowIds: [],
addedRows: [],
rowChanges: {},
currentPage: 0,
deletingRows: [],
pageSize: 0,
pageSizes: [5, 10, 0],
columnOrder: ['product', 'region', 'amount', 'discount', 'saleDate', 'customer'],
currencyColumns: ['amount'],
percentColumns: ['discount'],
};
const getStateDeletingRows = () => {
const { deletingRows } = this.state;
return deletingRows;
};
const getStateRows = () => {
const { rows } = this.state;
return rows;
};
this.changeSorting = sorting => this.setState({ sorting });
this.changeEditingRowIds = editingRowIds => this.setState({ editingRowIds });
this.changeAddedRows = addedRows => this.setState({
addedRows: addedRows.map(row => (Object.keys(row).length ? row : {
amount: 0,
discount: 0,
saleDate: new Date().toISOString().split('T')[0],
product: availableValues.product[0],
region: availableValues.region[0],
customer: availableValues.customer[0],
})),
});
this.changeRowChanges = rowChanges => this.setState({ rowChanges });
this.changeCurrentPage = currentPage => this.setState({ currentPage });
this.changePageSize = pageSize => this.setState({ pageSize });
this.commitChanges = ({ added, changed, deleted }) => {
let { rows } = this.state;
if (added) {
const startingAddedId = rows.length > 0 ? rows[rows.length - 1].id + 1 : 0;
rows = [
...rows,
...added.map((row, index) => ({
id: startingAddedId + index,
...row,
})),
];
}
if (changed) {
rows = rows.map(row => (changed[row.id] ? { ...row, ...changed[row.id] } : row));
}
this.setState({ rows, deletingRows: deleted || getStateDeletingRows() });
};
this.cancelDelete = () => this.setState({ deletingRows: [] });
this.deleteRows = () => {
const rows = getStateRows().slice();
getStateDeletingRows().forEach((rowId) => {
const index = rows.findIndex(row => row.id === rowId);
if (index > -1) {
rows.splice(index, 1);
}
});
this.setState({ rows, deletingRows: [] });
};
this.changeColumnOrder = (order) => {
this.setState({ columnOrder: order });
};
}
render() {
const {
classes,
} = this.props;
const {
rows,
columns,
tableColumnExtensions,
sorting,
editingRowIds,
addedRows,
rowChanges,
currentPage,
deletingRows,
pageSize,
pageSizes,
columnOrder,
currencyColumns,
percentColumns,
} = this.state;
return (
<Paper>
<Grid
rows={rows}
columns={columns}
getRowId={getRowId}
>
<SortingState
sorting={sorting}
onSortingChange={this.changeSorting}
/>
<PagingState
currentPage={currentPage}
onCurrentPageChange={this.changeCurrentPage}
pageSize={pageSize}
onPageSizeChange={this.changePageSize}
/>
<IntegratedSorting />
<IntegratedPaging />
<CurrencyTypeProvider for={currencyColumns} />
<PercentTypeProvider for={percentColumns} />
<EditingState
editingRowIds={editingRowIds}
onEditingRowIdsChange={this.changeEditingRowIds}
rowChanges={rowChanges}
onRowChangesChange={this.changeRowChanges}
addedRows={addedRows}
onAddedRowsChange={this.changeAddedRows}
onCommitChanges={this.commitChanges}
/>
<DragDropProvider />
<Table
columnExtensions={tableColumnExtensions}
cellComponent={Cell}
/>
<TableColumnReordering
order={columnOrder}
onOrderChange={this.changeColumnOrder}
/>
<TableHeaderRow showSortingControls />
<TableEditRow
cellComponent={EditCell}
/>
<TableEditColumn
width={120}
showAddCommand={!addedRows.length}
showEditCommand
showDeleteCommand
commandComponent={Command}
/>
<PagingPanel
pageSizes={pageSizes}
/>
</Grid>
<Dialog
open={!!deletingRows.length}
onClose={this.cancelDelete}
classes={{ paper: classes.dialog }}
>
<DialogTitle>
Delete Row
</DialogTitle>
<DialogContent>
<DialogContentText>
Are you sure to delete the following row?
</DialogContentText>
<Paper>
<Grid
rows={rows.filter(row => deletingRows.indexOf(row.id) > -1)}
columns={columns}
>
<CurrencyTypeProvider for={currencyColumns} />
<PercentTypeProvider for={percentColumns} />
<Table
columnExtensions={tableColumnExtensions}
cellComponent={Cell}
/>
<TableHeaderRow />
</Grid>
</Paper>
</DialogContent>
<DialogActions>
<Button onClick={this.cancelDelete} color="primary">
Cancel
</Button>
<Button onClick={this.deleteRows} color="secondary">
Delete
</Button>
</DialogActions>
</Dialog>
</Paper>
);
}
}
export default withStyles(styles, { name: 'ControlledModeDemo' })(DemoBase);
The font size of the text labelling the columns (e.g. product, region, amount) is fixed, and I see no parameters that can change it. Any ideas?
I think there are a few ways around this, the way I have used is having a fully controlled component.
Looks a little like this
<TableHeaderRow cellComponent={this.ExampleHeaderCell} />
Where ExampleHeaderCell is a component that would look something like this
ExampleHeaderCell = (props: any) => (<TableHeaderRow.Cell
className={exampleClass}
{...props}
key={column.name}
getMessage={() => column.title}
/>)
From there you can pass it a class as shown with exampleClass
You can take this further and have it customised for a particular column.
ExampleHeaderCells = (props: any) => {
const exampleClass = css({ backgroundColor: "blue" })
const { column } = props
if (column.name === "name") {
return (
<TableHeaderRow.Cell
className={exampleClass}
{...props}
key={column.name}
getMessage={() => column.title}
/>
)
}
return <TableHeaderRow.Cell {...props} key={column.name} getMessage={() => column.title} />
}
The example above is returning a specific cell with the exampleClass if the column name is equal to "name". Otherwise it just returns the regular TableHeaderRow.Cell