Viewing Memories

Since you have a couple of placeholder memories in the store, you decide to make a component for displaying a memory. When you made your database schema for Unforget, you gave each memory a year, a month, a day, and a text entry describing the day's events. When the client loads, you want to display all the memories that are currently in the store. In the App component, you pull the memories out with useSelector:

// ...
import {useSelector} from 'react-redux';

function App() {
const memories = useSelector(state => state.memories);
// ...
}

You want the memories to appear one after another in a two-column gridded structure that looks like this:

Wireframe of the Unforget client.

To ensure that the columns are all the same width across the page, without having to figure out what that width should be, you decide to add a container div that will use a CSS Grid layout. You give the div the class name memories:

function App() {
// ...
return (
<div className="App">
<div className="memories">
{/* ... */}
</div>
</div>
);
}

In App.css, you remove all the default styling that create-react-app generates, leaving only a margin around the page:

.App {
margin: 10px;
}

You configure the memories grid to have two columns, the first of which will be sized around its content and the second of which will expand to fill the available space:

.memories {
display: grid;
grid-template-columns: auto 1fr;
grid-row-gap: 10px;
}

You also leave a gap between rows to separate the memories from one another.

A list of memories must go inside the div. You decide to organize the logic of displaying a single memory in a Memory component. The map function turns your array of memories from the store into an array of Memory components:

// ...
import {Memory} from './Memory';

function App() {
// ...
return (
<div className="App">
<div className="memories">
{memories.map(memory =>
<Memory key={memory.id} memory={memory} />
)}
</div>
</div>
);
}

Each component instance expects the memory it displays to be passed in as a prop. This code fails currently since you don't have a Memory component. You open up src/Memory.js and start your component off to only display the text entry:

export function Memory(props) {
const {memory} = props;

return (
<div>{memory.entry}</div>
);
}

Do you see the entries for the two placeholder memories appear in the browser?

Because you are using a two-column grid, you need two elements for each memory: a left cell holding the date and a right cell holding the text entry. What happens when you add these two elements?

export function Memory(props) {
const {memory} = props;

return (
<div>{memory.year}</div>
<div>{memory.entry}</div>
);
}

You see an error telling you that components must return a structure with a single root element. Bummer. You could try nesting the two elements within a container element, but that will break the grid, which expects its cells to be its immediate children. Interposing a container will make them grandchildren, and you won't get the layout you want.

React provides a better fix. You can use a Fragment component. This component bundles the elements together but doesn't expand to an actual HTML element:

import {Fragment} from 'react';

export function Memory(props) {
const {memory} = props;

return (
<Fragment>
<div>{memory.year}</div>
<div>{memory.entry}</div>
</Fragment>
);
);
}

The error goes away. The month and day are missing, so you add those. Additionally, you want to apply some styling to this component, so you add some class names:

// ...

export function Memory(props) {
const {memory} = props;

return (
<Fragment>
<div className="memory-left memory-cell">
<span className="year">{memory.year}</span>
<span className="month-day">{memory.month} {memory.day}</span>
</div>
<div className="memory-right memory-cell">{memory.entry}</div>
</Fragment>
);
}

In your stylesheet, you add rules to adjust sizes, colors, and spacing:

.memory-cell {
padding: 10px;
}

.memory-left {
background-color: #fae7d4;

min-height: 200px;
border-right: 2px solid #fad9b6;

display: flex;
flex-direction: column;
align-items: flex-end;
gap: 2px;
}

.year {
font-size: 3em;
font-weight: bold;
}

.month-day {
background-color: #fae7d4;
font-size: 1.5em;
}

.memory-right {
background-color: linen;
}

This is starting to feel like an app. Except that month number doesn't feel right. You think it'd be friendlier to show the month name instead. You could make an array of month names and index into it. However, dates look different in different parts of the world. In places like the United States, you want to show a date like September 21. But in New Zealand, you want to show 21 September.

Since you like the idea of supporting people who aren't just like you, you decide to use Date.toLocaleString to produce localized date strings:

// ...

export function Memory(props) {
// ...

const day = new Date(memory.year, memory.month - 1, memory.day);

const monthDay = day.toLocaleString('default', {
month: 'long',
day: 'numeric',
});

const weekday = day.toLocaleString('default', {
weekday: 'long'
});

return (
<Fragment>
<div className="memory-left memory-cell">
<span className="year">{memory.year}</span>
<span className="month-day">{monthDay}</span>
<span className="weekday">{weekday}</span>
</div>
<div className="memory-right memory-cell">{memory.entry}</div>
</Fragment>
);
}

This is good progress, no doubt, but these memories are local. You want to display memories that have been fetched from the web service.