Building an App with SPFx, React, and TypeScript Part 2: Components

Introduction

In the previous post, we focused on Stateless Functional Components (SFC), which are functions that accept properties used to create elements that are returned by the function. Now we want to focus on Components which are classes where you can manage your application’s state and pass to an SFC as it’s properties.

We’ll create a few components. The main components will be the EventInfo, and Members to match the navigation menu that we created in the first part. We’re going to omit the About component.

Event Info

If you read the previous post, you’ll remember that we created a folder for each of our components and separated them further by differentiating whether they were class components or stateless functional components. We’ll start by creating an EventInfo folder and it’s class component inside. The shell of our code will include a class that extends React.Component and currently accepts any for it’s props and state. It contains a render function that will return a ReactElement and it also accepts any props but we won’t be using props here.

import * as React from 'react';

class EventInfo extends React.Component<any, any> {
    public render(): React.ReactElement<any> {
        return (
            <div>
                
            </div>
        );
    }
}

export default EventInfo;

Since we’ll be using State here, we’ll create an interface for state and put it in it’s own file named IEventInfoState.ts. Our EventInfo’s state will manage the name, location, organizers, and number of attendees.

export interface IEventInfoState {
    event: {
        name: string,
        location: string,
        organizers: string[],
        numOfAttendees: number
    }
}

Now we need to update the EventInfo.tsx to use the IEventInfoState state interface. The EventInfo class component will render an image and some information about the event that we will initialize with default values for now. We’ll come back in a future post and make it dynamic. I’m one of the organizers for the Tri-State Office 365 User Group and the information used in this example is the info from our meetup page.

import * as React from 'react';

import styles from './EventInfo.module.scss';
import { IEventInfoState } from './IEventInfoState';

const initialState: IEventInfoState = {
    event: {
        name: 'Tri-State Office 365 User Group',
        location: 'Malvern, PA',
        organizers: ['Jason', 'Michael'],
        numOfAttendees: 33
    }
}

class EventInfo extends React.Component<any, IEventInfoState> {

    readonly state: IEventInfoState = initialState;

    public render(): React.ReactElement<any> {
        return (
            <div className={styles.EventInfo}>
                <img src="https://secure.meetupstatic.com/photos/event/8/c/e/0/600_466836064.jpeg" alt="test" />
                <div className={styles.Details}>
                    <h2>{this.state.event.name}</h2>
                    <div>{this.state.event.location}</div>
                    <div>{'Organizers: ' + this.state.event.organizers.join(', ')}</div>
                    <div>{this.state.event.numOfAttendees + ' attendees'}</div>
                </div>
            </div>
        );
    }
}

export default EventInfo;

The class leverages some simple styles that we’ll skip over for now. We had a placeholder under our menu bar from the previous post. We now need to go back to our EventHub.tsx, import the EventInfo component, and add it to the JSX.

import * as React from 'react';
import styles from '../EventHub.module.scss';
import { IEventHubProps } from './IEventHubProps';
import { escape } from '@microsoft/sp-lodash-subset';

import Chrome from '../../statelessComponents/chrome/chrome';
import EventInfo from '../EventInfo/EventInfo';

export default class EventHub extends React.Component<IEventHubProps, {}> {
  public render(): React.ReactElement<IEventHubProps> {
    return (
      <Chrome>
        <EventInfo />
      </Chrome>
    );
  }
}

This is what our app currently looks like. If you remember, Chrome defines the menu bar which handles the logo and nav. Then there was a main element under the menu bar that contained {props.children} which indicates that we want to render anything that our component wraps. In this case, it’s the EventInfo component.

Members

The next component is Members which will display the profiles of each member. Same as before, we start by creating a Members directory under components and a Members.tsx within directory. There will be a slight difference between Members and EventInfo. EventInfo was fairly self contained but Members will use a stateless functional component to render the individual members. For now, let’s take a look at the Members.tsx starting point.

import * as React from 'react';

class Members extends React.Component<any, any> {
    public render(): React.ReactElement<any> {
        return (
            <div>Here is the members section!</div>
        );
    }
}

export default Members;

Next, we can wire Members up to the EventHub so that we can start seeing changes. We import Members and add the element under our EventInfo element.

import * as React from 'react';
import styles from '../EventHub.module.scss';
import { IEventHubProps } from './IEventHubProps';
import { escape } from '@microsoft/sp-lodash-subset';

import Chrome from '../../statelessComponents/chrome/chrome';
import EventInfo from '../EventInfo/EventInfo';
import Members from '../Members/Members';

export default class EventHub extends React.Component<IEventHubProps, {}> {
  public render(): React.ReactElement<IEventHubProps> {
    return (
      <Chrome>
        <EventInfo />
        <Members />
      </Chrome>
    );
  }
}

And when we take a look at our page, we can see the members component underneath the image.

So far so good. Next, I want the members section to be a list of people with at least an image and their name. I’m going to structure this one similar to how we did the navigation. We had navigation items (plural) which was our container and navigation item (singular) which represented our individual navigation items. This time, we’re going to have a Members class, which we already created, and a Member stateless functional component to define what each person object looks like.

The difference between Members and the Navigation Items is that members will be a class while navigation items was not. The reason I chose to do it this way is because navigation items had hard coded urls and members list will not have the people hard coded into the component… well, it will when we start, but we may circle back and change that. Members list should eventually pull the data from some repository and store it in state then pass the state values to the member SFC as properties.

I’ll start with the SFC by creating a Member folder and a Member.tsx inside of the statelessComponents folder. We’ll also use the Office Fabric UI persona. I’ll just pull some sample code from the page and hard code some values for now.

import React from 'react';

import { Persona, PersonaSize } from 'office-ui-fabric-react'

const member = () => (
    <div className="ms-PersonaExample">
        <Persona
            text="Jason Rivera"
            imageUrl={""}
            size={PersonaSize.small}
            secondaryText="SharePoint Architect"
        />
      </div>
);


export default member;

Let’s go back to the Members class and simply import this member SFC to confirm that it works. The updated Members.tsx looks like this:

import * as React from 'react';

import Member from '../../statelessComponents/Member/Member';

class Members extends React.Component<any, any> {
    public render(): React.ReactElement<any> {
        return (
            <div>
                <Member />
            </div>
        );
    }
}

export default Members;

Our app now looks like this:

Now let’s try to add more people to the list by storing the list in Members and updating the Member SFC to accept data. Our updated SFC will now accept props of any type and expects those props to have a name, imageUrl, and secondaryText. It may be a good idea to create an interface for the props that defines required fields and their type. I won’t do that here for this demo.

const member = (props:any) => (
    <div className="ms-PersonaExample">
        <Persona
            text={props.name}
            imageUrl={props.imageUrl}
            size={PersonaSize.small}
            secondaryText={props.secondaryText}
        />
    </div> 
);

Now we go back to Members and pass our data in. More baby steps. We’ll create a state object that will have our hard coded data and we will add a Member tag for each object. You’ll see that before the render function, we assign values to our state. State contains an array called person and it contains 3 items. Then we set up 3 member tags in the render function and assign the values of our array items to the props defined in Member (name, imageUrl, secondaryText).

import * as React from 'react';

import Member from '../../statelessComponents/Member/Member';

class Members extends React.Component<any, any> {
   state = {
        person: [
            {
                name: "Jason Rivera",
                imageUrl: "",
                secondaryText: "SharePoint Architect"
            },
            {
                name: "Adele Vance",
                imageUrl: "",
                secondaryText: "Business Analyst"
            },
            {
                name: "John Doe",
                imageUrl: "",
                secondaryText: "Manager"
            }
        ]
    }

 public render(): React.ReactElement<any> {

        return (
            <div>
                <Member 
                    name={this.state.person[0].name}
                    imageUrl={this.state.person[0].imageUrl}
                    secondaryText={this.state.person[0].secondaryText} />
                <Member 
                    name={this.state.person[1].name}
                    imageUrl={this.state.person[1].imageUrl}
                    secondaryText={this.state.person[1].secondaryText}/>
                <Member 
                    name={this.state.person[2].name}
                    imageUrl={this.state.person[2].imageUrl}
                    secondaryText={this.state.person[2].secondaryText}/>
            </div>
        );
    }
}

export default Members;

Here is what our app looks like after we add the 3 personas. We don’t want to have to hard code our Member tags so next we’ll work on making that part dynamic.

Back in our render method, we’ll create a membersList variable that will store an array of our Member tags. We’ll use the map function to return a Member tag for each person listed in our state. Then we’ll use the membersList in the render function’s return.

import * as React from 'react';

import Member from '../../statelessComponents/Member/Member';
import styles from './Members.module.scss';

class Members extends React.Component<any, any> {
   state = {
        person: [
            {
                name: "Jason Rivera",
                imageUrl: "",
                secondaryText: "SharePoint Architect"
            },
            {
                name: "Adele Vance",
                imageUrl: "",
                secondaryText: "Business Analyst"
            },
            {
                name: "John Doe",
                imageUrl: "",
                secondaryText: "Manager"
            }
        ]
    }

    public render(): React.ReactElement<any> {

        // create a member tag for each person in our state
        let membersList = (
            this.state.person.map(p =>{
                return (
                <Member 
                    name={p.name}
                    imageUrl={p.imageUrl}
                    secondaryText={p.secondaryText} />
                );
            })
        );

        return (
            <div className={styles.Members}>
                {membersList}
            </div>
        );
    }
}

export default Members;

I added some styles to clean it up a little and the current look, while not perfect, is as follows.

Conclusion

At this point, we have an app that is mostly comprised of stateless functional components. For the most part, they receive some data and define how that data is rendered. We have a couple of components that will manage state and feed that data to the functional components and for now, our data has been hard coded throughout.

In the next post, we’re going to switch gears from components and data and revisit the navigation.