Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Forms with React

Forms with React

Tutorial on how to handle forms with React, presented at React.Sofia

Radoslav Stankov

March 07, 2017
Tweet

More Decks by Radoslav Stankov

Other Decks in Technology

Transcript

  1. Forms with React
    Radoslav Stankov 07/03/2017

    View Slide

  2. Radoslav Stankov
    @rstankov

    http://rstankov.com

    http://github.com/rstankov

    View Slide

  3. View Slide

  4. View Slide

  5. ! https://github.com/tannerlinsley/react-form
    " https://github.com/prometheusresearch/react-forms
    # https://github.com/codecks-io/react-reform
    $ https://github.com/erikras/redux-form
    % https://github.com/Semantic-Org/Semantic-UI-React
    Libraries

    View Slide

  6. View Slide

  7. View Slide

  8. View Slide

  9. View Slide

  10. View Slide

  11. View Slide

  12. View Slide

  13. View Slide

  14. View Slide













  15. View Slide













  16. View Slide













  17. View Slide













  18. View Slide













  19. View Slide













  20. View Slide













  21. View Slide













  22. View Slide













  23. View Slide













  24. View Slide













  25. View Slide

  26. export default class SubmissionForm extends React.Component {
    render() {
    return (

    Speaker

    Name:



    Email:




    );
    }
    }

    View Slide

  27. export default class SubmissionForm extends React.Component {
    render() {
    return (

    Speaker

    Name:



    Email:




    );
    }
    handleSubmit = (e) => {
    e.preventDefault();
    const allValues = Array.from(e.target.elements).reduce((data, input) => {
    console.log(input);
    data[input.name] = input.value;
    return data;
    }, {});
    remoteCall({
    speakerName: allValues.speakerName,
    speakerEmail: allValues.speakerEmail,
    });
    };
    }

    View Slide

  28. export default class SubmissionForm extends React.Component {
    render() {
    return (

    Speaker

    Name:
    this.speakerName = ref} type="text" id="speakerName" name="speakerName" defaultV


    Email:
    this.speakerEmail = ref} type="email" id="speakerEmail" name="speakerEmail" defa



    );
    }
    handleSubmit = (e) => {
    e.preventDefault();
    remoteCall({
    speakerName: this.speakerName.value,
    speakerEmail: this.speakerEmail.value,
    });
    };
    }

    View Slide

  29. export default class SubmissionForm extends React.Component {
    render() {
    return (

    Speaker

    Name:
    this.speakerName = ref} type="text" id="speakerName" name="speakerName" defaultV


    Email:
    this.speakerEmail = ref} type="email" id="speakerEmail" name="speakerEmail" defa



    );
    }
    handleSubmit = (e) => {
    e.preventDefault();
    remoteCall({
    speakerName: this.speakerName.value,
    speakerEmail: this.speakerEmail.value,
    });
    };
    }

    View Slide

  30. export default class SubmissionForm extends React.Component {
    render() {
    return (

    Speaker

    Name:
    this.speakerName = ref} type="text" id="speakerName" name="speakerName" defaultV


    Email:
    this.speakerEmail = ref} type="email" id="speakerEmail" name="speakerEmail" defa



    );
    }
    handleSubmit = (e) => {
    e.preventDefault();
    remoteCall({
    speakerName: this.speakerName.value,
    speakerEmail: this.speakerEmail.value,
    });
    };
    }
    &

    View Slide

  31. Uncontrolled Components
    • fast
    • simple
    • integration with external libraries
    • no much control
    • gets messy quite easy

    View Slide

  32. Controlled Components
    • you have more control
    • easier to reason about
    • needed for complex form interactions
    • recommend by react documentation

    View Slide

  33. class ExampleForm extends React.Component {
    state = { value: '' }
    handleChange = (event) => {
    this.setState({value: event.target.value});
    }
    handleSubmit = (event) => {
    e.preventDefault();
    remoteCall(this.target.value)/
    };
    render() {
    return (

    value={this.state.value}
    onChange={this.handleChange} />


    );
    }
    }

    View Slide

  34. class ExampleForm extends React.Component {
    state = { value: '' }
    handleChange = (event) => {
    this.setState({value: event.target.value});
    }
    handleSubmit = (event) => {
    e.preventDefault();
    remoteCall(this.target.value)/
    };
    render() {
    return (

    value={this.state.value}
    onChange={this.handleChange} />


    );
    }
    }
    Input change

    View Slide

  35. class ExampleForm extends React.Component {
    state = { value: '' }
    handleChange = (event) => {
    this.setState({value: event.target.value});
    }
    handleSubmit = (event) => {
    e.preventDefault();
    remoteCall(this.target.value)/
    };
    render() {
    return (

    value={this.state.value}
    onChange={this.handleChange} />


    );
    }
    }
    Input change

    handleChange
    setState

    View Slide

  36. class ExampleForm extends React.Component {
    state = { value: '' }
    handleChange = (event) => {
    this.setState({value: event.target.value});
    }
    handleSubmit = (event) => {
    e.preventDefault();
    remoteCall(this.target.value)/
    };
    render() {
    return (

    value={this.state.value}
    onChange={this.handleChange} />


    );
    }
    }
    Input change

    handleChange
    setState

    render
    New value

    View Slide













  37. View Slide

  38. export default class SubmissionForm extends React.Component {
    state = {
    fields: {
    speakerName: '',
    speakerEmail: '',
    },
    };
    render() {
    return (

    Speaker

    Name:
    Email:


    );
    }
    handleChange = (e) => {
    const name = e.target.name;
    const value = e.target.value;
    this.setState({
    fields: {

    ...this.state.fields,
    [name]: value
    }
    });
    };
    handleSubmit = (e) => {
    e.preventDefault();
    remoteCall(this.state.fields);
    };
    }

    View Slide

  39. View Slide

  40. View Slide

  41. export default class SubmissionForm extends React.Component {
    state = {
    fields: {
    speakerName: '',
    speakerEmail: '',
    },
    isSumitting: false,
    };
    render() {
    return (

    Speaker

    Name:
    Email:


    );
    }
    handleChange = (e) => { /* … */ };
    handleSubmit = async (e) => {
    e.preventDefault();
    if (this.state.isSubmitting) { return; }
    this.setState({ isSubmitting: true });
    await remoteCall(this.state.fields);
    };
    }

    View Slide

  42. export default class SubmissionForm extends React.Component {
    state = {
    fields: {
    speakerName: '',
    speakerEmail: '',
    },
    isSumitting: false,
    };
    render() {
    return (

    Speaker

    Name:
    Email:


    );
    }
    handleChange = (e) => { /* … */ };
    handleSubmit = async (e) => {
    e.preventDefault();
    if (this.state.isSubmitting) { return; }
    this.setState({ isSubmitting: true });
    await remoteCall(this.state.fields);
    };
    }

    View Slide


















  43. View Slide


















  44. View Slide


















  45. View Slide


















  46. View Slide


















  47. View Slide


















  48. View Slide


















  49. View Slide

  50. export default class SubmissionForm extends React.Component {
    state = {
    fields: {
    speakerName: '',
    speakerEmail: '',
    talkTitle: '',
    },
    isSumitting: false,
    };
    render() {
    return (

    Speaker

    Name:
    Email:
    Title:



    );
    }
    handleChange = (e) => { /* … */ };
    handleSubmit = (e) => { /* … */ };
    }

    View Slide

  51. export default class SubmissionForm extends React.Component {
    state = {
    fields: {
    speakerName: '',
    speakerEmail: '',
    talkTitle: '',
    },
    isSumitting: false,
    };
    render() {
    return (

    Speaker

    Name:
    Email:
    Title:



    );
    }
    handleChange = (e) => { /* … */ };
    handleSubmit = (e) => { /* … */ };
    }

    View Slide

  52. export default class SubmissionForm extends React.Component {
    state = {
    fields: {
    speakerName: '',
    speakerEmail: '',
    talkTitle: '',
    },
    isSumitting: false,
    };
    render() {
    return (

    Speaker

    Name:
    Email:
    Title:



    );
    }
    handleChange = (e) => { /* … */ };
    handleSubmit = (e) => { /* … */ };
    }

    View Slide

  53. let Field = ({ name, type, label, ...props }) => {
    return (

    {label}:


    );
    };

    View Slide

  54. export default class SubmissionForm extends React.Component {
    state = { /* … */ };
    render() {
    return (

    Speaker
    Talk


    );
    }
    handleChange = (e) => { /* … */ };
    handleSubmit = (e) => { /* … */ };
    }

    View Slide

  55. export default class SubmissionForm extends React.Component {
    state = { /* … */ };
    render() {
    return (

    Speaker
    Talk


    );
    }
    handleChange = (e) => { /* … */ };
    handleSubmit = (e) => { /* … */ };
    }

    View Slide









  56. View Slide










  57. View Slide










  58. View Slide










  59. '

    View Slide










  60. (

    View Slide

  61. let Field = ({ name, input, label, ...props }) => {
    if (input === 'textarea') {
    return (

    {label}:


    );
    }
    return (

    {label}:


    );
    };

    View Slide

  62. export default class SubmissionForm extends React.Component {
    state = {
    fields: {
    speakerName: '',
    speakerEmail: '',
    talkTitle: '',
    talkDescription: '',
    },
    isSumitting: false,
    };
    render() {
    return (

    Speaker
    Talk


    );
    }
    handleChange = (e) => { /* … */ };
    handleSubmit = (e) => { /* … */ };
    }

    View Slide










  63. View Slide











  64. View Slide











  65. View Slide

  66. const LENGTH_OPTIONS = [
    {value: 15, label: '15 minutes'},
    {value: 30, label: '30 minutes'},
    {value: 45, label: '45 minutes'},
    ];

    View Slide

  67. name="talkLength"
    label="Length"
    value={this.state.fields.talkLength}
    onChange={this.handleChange}
    input="select"
    options={LENGTH_OPTIONS} />

    View Slide

  68. let Select = ({ options, ...props }) => (

    {options.map(({ label, value }, i) => (
    {label || value}
    ))}

    );

    View Slide

  69. let Field = ({ name, input, label, ...props }) => {
    if (input === 'textarea') {
    return (

    {label}:


    );
    }
    if (input === 'select') {
    return (

    {label}:


    );
    }
    return (

    {label}:


    );
    };

    View Slide

  70. let Input = ({ ...props }) => (

    );

    View Slide

  71. const INPUTS = {
    'textarea': Textarea,
    'select': Select,
    'text': Input,
    };
    let Field = ({ name, input = 'text', label, ...props }) => {
    const Component = INPUTS[input] || Input;
    const inputProps = Component === Input ? { type: input, ...props } : props;
    return (

    {label}:


    );
    };

    View Slide

  72. const INPUTS = {
    'textarea': Textarea,
    'select': Select,
    'text': Input,
    };
    let Field = ({ name, input = 'text', label, ...props }) => {
    const Component = typeof input === 'function' ? input : INPUTS[input] || Input;
    const inputProps = Component === Input ? { type: input, ...props } : props;
    return (

    {label}:


    );
    };

    View Slide

  73. const INPUTS = {
    'textarea': Textarea,
    'select': Select,
    'text': Input,
    };
    let Field = ({ name, input = 'text', label, ...props }) => {
    const Component = typeof input === 'function' ? input : INPUTS[input] || Input;
    const inputProps = Component === Input ? { type: input, ...props } : props;
    return (

    {label}:


    );
    };

    View Slide


  74. View Slide

  75. View Slide











  76. View Slide












  77. View Slide












  78. View Slide

  79. let RadioGroup = ({ value: fValue, options, name, id, ...props }) => (

    {options.map(({ label, value }, i) => (



    {label || value}


    ))}

    );

    View Slide

  80. const INPUTS = {
    'textarea': Textarea,
    'select': Select,
    'text': Input,
    'radioGroup': RadioGroup,
    };

    View Slide


  81. Speaker
    Talk


    View Slide


  82. Speaker
    Talk


    View Slide


  83. Speaker
    Talk


    View Slide


  84. Speaker


    Talk






    View Slide


  85. Component

    View Slide


  86. Component
    Child Component

    View Slide


  87. Component
    Child Component
    Child Child Component

    View Slide


  88. Component
    Child Component
    Child Child Component

    Child Child … Child Component

    View Slide


  89. Component
    Child Component
    Child Child Component

    Child Child … Child Component
    Context

    View Slide


  90. Component
    Child Component
    Child Child Component

    Child Child … Child Component
    Context
    Context

    View Slide

  91. export default class SubmissionForm extends React.Component {
    state = {
    fields: {
    speakerName: '',
    speakerEmail: '',
    talkTitle: '',
    talkDescription: '',
    talkLength: '15',
    },
    isSumitting: false,
    };
    static childContextTypes = {
    formState: React.PropTypes.object.isRequired,
    formHandleChange: React.PropTypes.func.isRequired,
    };
    getChildContext() {
    return {
    formState: this.state,
    formHandleChange: this.handleChange,
    };
    }
    render() { /* … */ };
    handleChange = (e) => { /* … */ };
    handleSubmit = (e) => { /* … */ };
    }

    View Slide

  92. let Field = ({ name, input, label, ...props }, { formState: { fields }, formHandleChange }) => {
    const value = fields[name];
    const Component = typeof input === 'function' ? input : INPUTS[input] || Input;
    const inputProps = Component === Input ? { type: input, ...props } : props;
    return (

    {label}:


    );
    };
    Field.contextTypes = {
    formState: React.PropTypes.object.isRequired,
    formHandleChange: React.PropTypes.func.isRequired,
    };

    View Slide

  93. let Field = ({ name, input, label, ...props }, { formState: { fields }, formHandleChange }) => {
    const value = fields[name];
    const Component = typeof input === 'function' ? input : INPUTS[input] || Input;
    const inputProps = Component === Input ? { type: input, ...props } : props;
    return (

    {label}:


    );
    };
    Field.contextTypes = {
    formState: React.PropTypes.object.isRequired,
    formHandleChange: React.PropTypes.func.isRequired,
    };

    View Slide


  94. Speaker


    Talk






    View Slide


  95. Speaker


    Talk






    View Slide

  96. let SubmitButton = ({ children }, { formState: { isSubmitting }}) => (

    );
    SubmitButton.contextTypes = {
    formState: React.PropTypes.object.isRequired,
    };

    View Slide


  97. Speaker


    Talk




    Submit

    View Slide

  98. export default class SubmissionForm extends React.Component {
    state = {
    fields: {
    speakerName: '',
    speakerEmail: '',
    talkTitle: '',
    talkDescription: '',
    talkLength: '15',
    },
    isSumitting: false,
    };
    static childContextTypes = {
    formState: React.PropTypes.object.isRequired,
    formHandleChange: React.PropTypes.func.isRequired,
    };
    getChildContext() {
    return {
    formState: this.state,
    formHandleChange: this.handleChange,
    };
    }
    render() {
    return (

    Speaker


    Talk




    Submit

    View Slide


  99. Speaker


    Talk




    Submit

    );
    }
    handleChange = (e) => {
    const name = e.target.name;
    const value = e.target.value;
    const fields = {
    ...this.state.fields,
    [name]: value
    };
    this.setState({ fields });
    };
    handleSubmit = async (e) => {
    e.preventDefault();
    if (this.state.isSubmitting) {
    return;
    }
    this.setState({ isSubmitting: true });
    await remoteCall(this.state.fields);
    };
    }

    View Slide

  100. export default class SubmissionForm extends React.Component {
    state = {
    fields: {
    speakerName: '',
    speakerEmail: '',
    talkTitle: '',
    talkDescription: '',
    talkLength: '15',
    },
    isSumitting: false,
    };
    static childContextTypes = {
    formState: React.PropTypes.object.isRequired,
    formHandleChange: React.PropTypes.func.isRequired,
    };
    getChildContext() {
    return {
    formState: this.state,
    formHandleChange: this.handleChange,
    };
    }
    render() {
    return (

    Speaker


    Talk




    Submit

    );
    }
    handleChange = (e) => {
    const name = e.target.name;
    const value = e.target.value;
    const fields = {
    ...this.state.fields,
    [name]: value
    };
    this.setState({ fields });
    };
    handleSubmit = async (e) => {
    e.preventDefault();
    if (this.state.isSubmitting) {
    return;
    }
    this.setState({ isSubmitting: true });
    await remoteCall(this.state.fields);
    };
    }

    View Slide

  101. export default class SubmissionForm extends React.Component {
    state = {
    fields: {
    speakerName: '',
    speakerEmail: '',
    talkTitle: '',
    talkDescription: '',
    talkLength: '15',
    },
    isSumitting: false,
    };
    static childContextTypes = {
    formState: React.PropTypes.object.isRequired,
    formHandleChange: React.PropTypes.func.isRequired,
    };
    getChildContext() {
    return {
    formState: this.state,
    formHandleChange: this.handleChange,
    };
    }
    render() {
    return (

    Speaker


    Talk




    Submit

    );
    }
    handleChange = (e) => {
    const name = e.target.name;
    const value = e.target.value;
    const fields = {
    ...this.state.fields,
    [name]: value
    };
    this.setState({ fields });
    };
    handleSubmit = async (e) => {
    e.preventDefault();
    if (this.state.isSubmitting) {
    return;
    }
    this.setState({ isSubmitting: true });
    await remoteCall(this.state.fields);
    };
    }

    View Slide

  102. class Form extends React.Component {
    state = {
    fields: this.props.fields,
    isSumitting: false,
    };
    static childContextTypes = {
    formState: React.PropTypes.object.isRequired,
    formHandleChange: React.PropTypes.func.isRequired,
    };
    getChildContext() {
    return {
    formState: this.state,
    formHandleChange: this.handleChange,
    };
    }
    render() {
    return (

    {this.props.children}

    );
    }
    handleInputChange = (e) => {
    const name = e.target.name;
    const value = e.target.value;
    const fields = {
    ...this.state.fields,
    [name]: value
    };
    this.setState({ fields });
    };

    View Slide

  103. return {
    formState: this.state,
    formHandleChange: this.handleChange,
    };
    }
    render() {
    return (

    {this.props.children}

    );
    }
    handleInputChange = (e) => {
    const name = e.target.name;
    const value = e.target.value;
    const fields = {
    ...this.state.fields,
    [name]: value
    };
    this.setState({ fields });
    };
    handleSubmit = async (e) => {
    e.preventDefault();
    if (this.state.isSubmitting) {
    return;
    }
    this.setState({ isSubmitting: true });
    await this.props.onSubmit(this.state.fields);
    };
    }

    View Slide

  104. const FIELDS = {
    speakerName: '',
    speakerEmail: '',
    talkTitle: '',
    talkDescription: '',
    talkLength: '15',
    };
    export default class SubmissionForm extends React.Component {
    render() {
    return (

    Speaker


    Talk




    Submit

    );
    }
    }

    View Slide

  105. const FIELDS = {
    speakerName: '',
    speakerEmail: '',
    talkTitle: '',
    talkDescription: '',
    talkLength: '15',
    };
    let SubmissionForm = () => (

    Speaker


    Talk




    Submit

    );

    View Slide

  106. View Slide

  107. View Slide

  108. View Slide

  109. Submit

    View Slide

  110. Submit Server

    View Slide

  111. Submit Server
    Success

    View Slide

  112. Submit Server
    Success
    Errors

    View Slide

  113. remoteCall Server
    { result: 'ok' }
    { errors: {…} }

    View Slide

  114. remoteCall Server
    { result: 'ok' }
    { 

    errors: {
    field1: [ 'error1', 'error2
    field2: [ 'error1']

    }

    }

    View Slide

  115. { 

    errors: {
    field1: [ 'error1', 'error2' ],

    field2: [ 'error1']

    }

    }

    View Slide

  116. class Form extends React.Component {
    state = {
    fields: this.props.fields,
    isSumitting: false,
    };
    static childContextTypes = { /* … */ };
    getChildContext() = { /* … */ }
    render() = { /* … */ }
    handleInputChange = (e) => { /* … */ };
    handleSubmit = async (e) => {
    e.preventDefault();
    if (this.state.isSubmitting) {
    return;
    }
    await this.props.onSubmit(this.state.fields);
    };
    }

    View Slide

  117. class Form extends React.Component {
    state = {
    fields: this.props.fields,
    errors: {},
    isSumitting: false,
    };
    static childContextTypes = { /* … */ };
    getChildContext() = { /* … */ }
    render() = { /* … */ }
    handleInputChange = (e) => { /* … */ };
    handleSubmit = async (e) => {
    e.preventDefault();
    if (this.state.isSubmitting) {
    return;
    }
    this.setState({ isSubmitting: true, errors: {} });
    const { errors } = await this.props.onSubmit(this.state.fields);
    this.setState({ isSubmitting: false, errors: errors || {} });
    };
    }

    View Slide

  118. class Form extends React.Component {
    state = {
    fields: this.props.fields,
    errors: {},
    isSumitting: false,
    };
    static childContextTypes = { /* … */ };
    getChildContext() = { /* … */ }
    render() = { /* … */ }
    handleInputChange = (e) => { /* … */ };
    handleSubmit = async (e) => {
    e.preventDefault();
    if (this.state.isSubmitting) {
    return;
    }
    this.setState({ isSubmitting: true, errors: {} });
    const { errors } = await this.props.onSubmit(this.state.fields);
    this.setState({ isSubmitting: false, errors: errors || {} });
    };
    }

    View Slide

  119. let Field = ({ name, input, label, ...props }, { formState: { fields, errors }, formHandleInputChange })
    const value = fields[name];
    const error = errors[name] && errors[name][0];
    const Component = typeof input === 'function' ? input : INPUTS[input] || Input;
    const inputProps = Component === Input ? { type: input, ...props } : props;
    return (

    {label}:

    { error && {error}}

    );
    };

    View Slide

  120. let Field = ({ name, input, label, ...props }, { formState: { fields, errors }, formHandleInputChange })
    const value = fields[name];
    const error = errors[name] && errors[name][0];
    const Component = typeof input === 'function' ? input : INPUTS[input] || Input;
    const inputProps = Component === Input ? { type: input, ...props } : props;
    return (

    {label}:

    { error && {error}}

    );
    };

    View Slide

  121. class Form extends React.Component {
    state = { /* … */ };
    static childContextTypes = { /* … */ };
    getChildContext() = { /* … */ }
    render() = { /* … */ }
    handleInputChange = (e) => { /* … */ };
    handleSubmit = async (e) => {
    e.preventDefault();
    if (this.state.isSubmitting) {
    return;
    }
    this.setState({ isSubmitting: true, errors: {} });
    const { errors } = await this.props.onSubmit(this.state.fields);
    this.setState({ isSubmitting: false, errors: errors || {} });
    };
    }

    View Slide

  122. class Form extends React.Component {
    state = { /* … */ };
    static childContextTypes = { /* … */ };
    getChildContext() = { /* … */ }
    isUnmounted: boolean = false;
    componentWillUnmount() {
    this.isUnmounted = true;
    }
    render() = { /* … */ }
    handleInputChange = (e) => { /* … */ };
    handleSubmit = async (e) => {
    e.preventDefault();
    if (this.state.isSubmitting) {
    return;
    }
    this.setState({ isSubmitting: true, errors: {} });
    const { errors } = await this.props.onSubmit(this.state.fields);
    if (!this.isUnmounted) {
    this.setState({ isSubmitting: false, errors: errors || {} });
    }
    };
    }

    View Slide

  123. class Form extends React.Component {
    state = { /* … */ };
    static childContextTypes = { /* … */ };
    getChildContext() { /* … */ }
    isUnmounted: boolean = false;
    componentWillUnmount() {
    this.isUnmounted = true;
    }
    render() { /* … */ }
    handleChange = (e) => { /* … */ };
    handleSubmit = async (e) => {
    e.preventDefault();
    if (this.state.isSubmitting) {
    return;
    }
    this.setState({ isSubmitting: true, errors: {} });
    const { errors } = await this.props.onSubmit(this.state.fields);
    if (!this.isUnmounted) {
    this.setState({ isSubmitting: false, errors: errors || {} });
    }
    };
    }

    View Slide

  124. View Slide


  125. • simple form interface
    • extensible fields
    • protection for double submit
    • standardized form layout
    • standardized server responses

    View Slide

  126. View Slide

  127. What is missing?
    • client side validations
    • theming
    • handling of deep nested forms
    • tests!
    • fun features ¯\_(ϑ)_/¯

    View Slide

  128. ! https://github.com/tannerlinsley/react-form
    " https://github.com/prometheusresearch/react-forms
    # https://github.com/codecks-io/react-reform
    $ https://github.com/erikras/redux-form
    % https://github.com/Semantic-Org/Semantic-UI-React
    Libraries

    View Slide

  129. View Slide

  130. https://github.com/RStankov/talks-code

    View Slide

  131. https://speakerdeck.com/rstankov/forms-with-react

    View Slide

  132. https://www.meetup.com/React-Sofia/

    View Slide

  133. Thanks )

    View Slide

  134. View Slide