4

Beginner React/Typescript learner here, I am trying to improve this class I have:

import * as React from 'react';
import {Form, IFields, isEmail, required} from "../../components/Form";
import {Field} from "../../components/Field";

const API = '/api/getmonth';

export interface Props {
}

interface State {
  data: string[],
  isLoading: boolean,
  error: any,
}

class monthForm extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props);

    this.state = {
      data: [],
      isLoading: false,
      error: null,
    };
  }


  componentDidMount() {
    this.setState({isLoading: true});

    fetch(API)
      .then(response => {
        if (response.ok) {
          return response.json();
        } else {
          throw new Error('Error getting month list');
        }
      })
      .then(content => this.setState({data: content, isLoading: false}))
      .catch(error => this.setState({error, isLoading: false}));
  }

  render() {
    const {data, isLoading, error} = this.state;

    if (error) {
      return <p>{error.message}</p>;
    }
    if (isLoading) {
      return (
        <p>Loading ...</p>
      )
    }

    const fields: IFields = {
      jan: {
        id: "jan",
        label: "Jan",
        editor: "dropdown",
        options: data,
        value: "hello",
        validation: {rule: required}
      },
      feb: {
        id: "feb",
        label: "Feb",
        editor: "dropdown",
        options: data,
        value: "hello",
        validation: {rule: required}
      },
      mar: {
        id: "mar",
        label: "Mar",
        editor: "dropdown",
        options: data,
        value: "hello",
        validation: {rule: required}
      },
      apr: {
        id: "apr",
        label: "Apr",
        editor: "dropdown",
        options: data,
        value: "hello",
        validation: {rule: required}
      },
      may: {
        id: "may",
        label: "May",
        editor: "dropdown",
        options: data,
        value: "hello",
        validation: {rule: required}
      },
      jun: {
        id: "jun",
        label: "Jun",
        editor: "dropdown",
        options: data,
        value: "hello",
        validation: {rule: required}
      },
      jul: {
        id: "jul",
        label: "Jul",
        editor: "dropdown",
        options: data,
        value: "hello",
        validation: {rule: required}
      },
      aug: {
        id: "aug",
        label: "Aug",
        editor: "dropdown",
        options: data,
        value: "hello",
        validation: {rule: required}
      },
      sep: {
        id: "sep",
        label: "Sep",
        editor: "dropdown",
        options: data,
        value: "hello",
        validation: {rule: required}
      },
      oct: {
        id: "oct",
        label: "Oct",
        editor: "dropdown",
        options: data,
        value: "hello",
        validation: {rule: required}
      },
      nov: {
        id: "nov",
        label: "Nov",
        editor: "dropdown",
        options: data,
        value: "hello",
        validation: {rule: required}
      },
      dec: {
        id: "dec",
        label: "Dec",
        editor: "dropdown",
        options: data,
        value: "hello",
        validation: {rule: required}
      },
    };
    return (
      <Form
        action="/react/test/form"
        fields={fields}
        render={() => (
          <React.Fragment>
            <div className="alert alert-info" role="alert">
              Select Projection for each month
            </div>
            <div className="container">
              <div className="row">
                <div className="col-md-3">
                  <Field {...fields.jan}/>
                  <Field {...fields.feb}/>
                  <Field {...fields.mar}/>
                  <Field {...fields.apr}/>
                  <Field {...fields.may}/>
                  <Field {...fields.jun}/>
                </div>
                <div className="col-md-3">
                  <Field {...fields.jul}/>
                  <Field {...fields.aug}/>
                  <Field {...fields.sep}/>
                  <Field {...fields.oct}/>
                  <Field {...fields.nov}/>
                  <Field {...fields.dec}/>
                </div>
              </div>
            </div>
          </React.Fragment>
        )}
      />
    );
  }
}

export default monthForm;

particularly this part:

      jan: {
        id: "jan",
        label: "Jan",
        editor: "dropdown",
        options: data,
        value: "hello",
        validation: {rule: required}
      },
      feb: {
        id: "feb",
        label: "Feb",
        editor: "dropdown",
        options: data,
        value: "hello",
        validation: {rule: required}
      },
      mar: {
        id: "mar",
        label: "Mar",
        editor: "dropdown",
        options: data,
        value: "hello",
        validation: {rule: required}
      },
      apr: {
        id: "apr",
        label: "Apr",
        editor: "dropdown",
        options: data,
        value: "hello",
        validation: {rule: required}
      },
      may: {
        id: "may",
        label: "May",
        editor: "dropdown",
        options: data,
        value: "hello",
        validation: {rule: required}
      },

This section seems like it could easily be looped over a const [] with month names. But I can't seem to find any reference on how to achieve it.

Any help or point to a reference example would be greatly appreciated!

Vencovsky
  • 28,550
  • 17
  • 109
  • 176
tom
  • 445
  • 4
  • 20

2 Answers2

5

You can use Object.values, Object.keys or Object.entries.

Object.values(fields).map(month => <Field {...month}/>)

If you want to separate the months, you can split the array in half (Object.values(fields)) and render both separately.

render(){
    const months = Object.values(fields)
    const halfwayThrough = Math.floor(months.length / 2)
    const monthFirstHalf = months.slice(0, halfwayThrough);
    const monthSecondHalf = months.slice(halfwayThrough, months.length);

    ...

    return (
        ...
        <div className="col-md-3">
            {monthFirstHalf.map(month => <Field {...month}/>)}
        </div>
        <div className="col-md-3">
            {monthSecondHalf.map(month => <Field {...month}/>)}
        </div>
        ...
    )

}

Edit:

Instead of having that huge object and supposing all the properties are the same except for the name, here is something you can do with .reduce (you could also do with a .forEach)

const months = ['Jan', 'Feb', 'Mar', /* ...rest */] 
const fields = months.reduce((monthObject, monthName) => {
    let monthId = monthName.toLowerCase()
    monthObject[monthId] = {
        id: monthId,
        label: monthName,
        editor: "dropdown",
        options: data,
        value: "hello",
        validation: {rule: required}
    }
    return monthObject
}, {})

And with this, you will have created that huge object

Combining both thins, here is what you can do

const months = ['Jan', 'Feb', 'Mar', /* ...rest */] 

return (
   ...
   {months.map(month => <Field 
           id={month}
           label={month}
           editor: "dropdown",
           options: data,
           value: "hello",
           validation: {rule: required}
       />
   )}
   ...
)
Vencovsky
  • 28,550
  • 17
  • 109
  • 176
  • 1
    Or [`Object.entries`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) (for completeness) – Soc Dec 06 '19 at 18:45
  • thanks, do you have any tips on how to loop the `const fields: IFields = {...}` part (second code block)? if I understand correctly that is just an array but I want to insert to it in a loop rather than declare the whole thing that is mostly duplicates with different key, thanks! – tom Dec 06 '19 at 18:56
  • @tom I didn't understand what you want. You want something like having an array with the month name and create that huge fields object? – Vencovsky Dec 06 '19 at 19:00
  • @tom check my edit, maybe that is what you need, please tell me if my assumptions are right or not. – Vencovsky Dec 06 '19 at 19:09
  • @Vencovsky yes exactly, I will have a `let month = ["jan", "feb", "mar"]` and do a `month.map(...)` over it, ideally i would be able to create that huge `fields` object within this loop – tom Dec 06 '19 at 19:13
  • thanks so much everything works now! time to understand the map.reduce part! – tom Dec 06 '19 at 19:20
1

If I understand correctly, you are thinking about refactoring the creation of a list of objects by looping over a list of month names. Vencovsky shows how to use reduce() to do this. I would go a step further and create the <Field> components directly by using map():

const months = ['Jan', 'Feb', 'Mar', /* ...rest */] 
const monthFields = months.map(m => 
   <Field id={m}
      label={m}
      editor="dropdown"
      options={data}
      value="hello"
      validation={{rule: required}} />
);
Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268