Authoring Components

Authoring Components

Creating a Component

Components should be written as functional components as opposed to class based components whenever possible. Functional components have a few advantages over class based components:

• They are simple, plain old javascript and are therefore often easier to reason about

• They require no boilerplate often resulting in less code

• There is no setState(). Using state requires that we explicitly introduce it. Consequently, we are required to be more thoughtful about when a component should have it's own state or shared state.

So, what does a basic functional component look like?

import React from 'react';
export const HelloShift = () => {
return (
<div>Hello Shift!</div>
)
}

Styling a Component

We use CSS Modules to style our components. CSS Modules allows us to import CSS as JavaScript objects into our components. We can then use those objects to apply styles to our JSX. Let's use the HelloShift component from above. Here's a basic CSS file as well as an example of how we can use the CSS to style our HelloShift component.

Our CSS:

// style.css
.hello {
color: pink
}

Our HelloShift component:

import React from 'react';
import style from './style.css'
export const HelloShift = () => {
return (
<div className={style.hello}>Hello Shift!</div>
)
}

Notice that by importing from style.css we now have a javascript object representing out .hello. css class.

Multiple classes

Often times you'll need to apply multiple classes to the same element. For this we use the Classnames package

Consider the following CSS file with two classes:

//style.css
.hello {
color: pink;
}
.selected {
font-weight: bold;
}

You can apply both classes to the same element like this:

import React from 'react';
import style from './style.css'
import classnames from 'classnames'
export const HelloShift = () => {
return (
<div className={classNames(style.hello, style.selected)}>Hello Shift!</div>
)
}

Stateful Components

Using Local State

In many cases your component may care about state that no other components need to know about. In this case your component can manage that state locally. If you recall from above, we prefer to use functional components. Since functional components have not state of their own we need to introduce it. We do this using the useState hook. The useState hooks give us a state value and a setter method for mutating state. Here's an example building on our HelloShift component:

import React, { useState } from 'react';
import style from './style.css'
export const HelloShift = () => {
[count, setCount] = useState(0)
return (
<div>
<div className={style.hello}>Hello Shift!</div>
<div>You said hello back {count} times</div>
<button onClick={() => setCount(count + 1)}>Click to say "Hello" back</button>
</div>
)
}

A couple things to notice.

• We added something new to our imports, useState

• We added a button that calls the setCount function of our state

Getting External Data

In some cases your component will need to use external data from our API or a third-party API. In these cases we are depending on external state. Our apps centralize external and shared state management into the /data directory where module specific files are responsible for managing the fetching and mutation of external data and shared state. In their simplest form these data repositories look something like this:

import * as React from 'react';
import axios from 'axios';
export function useHelloMessage() {
const helloMessage = {}
React.useEffect(() => {
return (async () => {
helloMessage = await axios.get("https://api.someapi.com")
})()
}, [])
return helloMessage;
}

We can then call this in our component like this:

import React, { useState } from 'react';
import style from './style.css'
export const HelloShift = () => {
[count, setCount] = useState(0)
const helloMessage = useHelloMessage();
return (
<div>
<div className={style.hello}>{helloMessage}</div>
<div>You said hello back {count} times</div>
<button onClick={() => setCount(count + 1)}>Click to say "Hello" back</button>
</div>
)
}

With this example, it's important to note that while this functionality is shared the state is not. That's ok. There are many cases where state doesn't need to be shared. However, it still important to centralize the function so that we can ensure a consistent data API across the application.

Something to note: that function useTodos is a custom hook. See Hooks to learn more.

Getting Shared State

While the above example is simple it doesn't tell the full story. There are many cases where state needs to be shared. To do this we can take a similar approach as the above but we need to add in an important piece, Redux. Here's an example that expounds on sharing state using Redux and the example above.

import * as React from 'react';
import axios from 'axios';
import { useMappedState, useDispatch } from 'redux-react-hook';
import { createAsyncActions, handleAction } from 'redux-ts-utils';
// Actions
export const [
fetchHelloMessage,
fetchHelloMessageSuccess,
fetchHelloMessageFailure,
] = createAsyncActions('asset/fetch', (id: string) => id, (asset: Asset) => asset);
// Reducer
handleAction(fetchAssetsSuccess, (state, { payload }) => {
state.helloMessage = payload
})
// Hook
export function useHelloMessage() {
const dispatch = useDispatch();
React.useEffect(() => {
return (async () => {
todos = await axios.get("https://api.someapi.com").then((asset) => {
dispatch(fetchHelloMessageSuccess(asset.data))
}).catch(() => {
dispatch(fetchHelloMessageFailure(new Error("Could not fetch assets")))
})
})()
}, []);
const mapToState = (state) => ({
helloMessage: state.helloMessage
})
return useMappedState(mapToState);
}

To fully understand what's happening here it's important to be familiar with Redux. But you should see a few new things: actions, reducers, and a slightly more complex useHelloMessage function.