Skip to main content

Mocking data

By now, we're ready to create our TimeEntries and TimeEntry components, and supply them with hardcoded placeholder data. In other words: we're adding static dates, client names and hours.

When our components are ready, we can start making them more dynamic.

Preparing dynamic data

Eventually our application will receive data from a back-end database and display it to the users, but until that moment we will create a dataset that holds a realistic representation of the real thing.

In most cases, requesting data from a back-end returns data that is contained in arrays and objects. You can consider Arrays as lists, and objects as single entities containing named properties. In the case of TaskFlow, a realistic data structure underlying a list of time entries would take the form of a single array:

const timeEntries = [
{
client: 'Heineken',
startTime: "09:00",
stopTime: "17:00"
date: '16-04-2022'
},
{
client: 'Port of Rotterdam',
startTime: '09:00',
stopTime: '17:00',
date: '17-04-2022',
},
];

Two things are still missing. First, in data structures, unique entities are supplied with an id - a unique identifier that is used to reference the object it represents. Second, data that represents a moment in time, also called a timestamp, is stored in a standardized ISO format. This format defines a moment in time, including a date and timezone location.

const timeEntries = [
{
id: 1,
client: 'Heineken',
startTimestamp: '2022-09-23T16:00:00.000Z',
stopTimestamp: '2022-09-23T18:00:00.000Z'
},
{
id: 2,
client: 'Port of Rotterdam',
startTimestamp: '2022-09-24T16:00:00.000Z',
stopTimestamp: '2022-09-25T18:00:00.000Z'
},
];

That's it - this is a realistic mock dataset! Now it's time to 'map' this data onto the components you've built.

Mapping mock data

We can now map this array containing time entries - in other words we will render a single time entry component for every time entry object in the timeEntries array. This is done with an array method called map. This method allows you to iterate over an array. What does that mean? It means that you take an array, and for every item in that array, you perform a function that you specify. This is done for all array items and the result is once again put in an array. For example:

const numbers = [1, 2, 3, 4];
const mappedNumbers = numbers.map((number) => number * 2);
console.log(mappedNumbers); // [2, 4, 6, 8]

As you can see, an array is returned for an array, but for every item in that array, a function was applied that multiplies every item, this function is called a 'callback function'.

The callback function in a map does whatever you tell it to, so we can also render a React component for every item in an array. Does that sound applicable to our mock time entries data set?

Try this in your TimeEntries component template, make sure to import mockTimeEntries:

import timeEntries from '../../fixtures/time-entries';

As you can see, we're importing the time entries from the fixtures/ directory. The term "fixtures" is derived from test fixtures, which is the setup used to test electronic equipment. In software development fixtures are considered data used for testing.

It is generally a good idea to put your mock data in a useState directly, so you can eventually replace it quite easily once the actual data is available. Using your mock data directly prevents you from, in this case, renaming mockTimeEntries to timeEntries when the mocked dataset will be deleted.

First, put mockTimeEntries as an initial value of your useState. Then, map the time entries and return a template for each individual time entry:

const TimeEntries = () => {
const [timeEntries, setTimeEntries] = useState(mockTimeEntries);

return timeEntries.map((timeEntry)=> (
<div>{timeEntry.client}</div>
);
}

You should now see a list consisting of the two clients specified in your mockTimeEntries. Next, try out mapping your timeEntries to your TimeEntry component and supplying it with a client prop. Notice we've also added a unique identifier key, this is required when using the .map() method:

const TimeEntries = () => {
const [timeEntries, setTimeEntries] = useState(mockTimeEntries);

return timeEntries.map((timeEntry) => (
<TimeEntry client={timeEntry.client} key={timeEntry.id} />
);
}

This example does require you to declare a client prop in your TimeEntryProps and that you specify client as an argument at the top of your TimeEntry component. Please refer back to the 'Creating components' chapter to see how you define a component prop and put it to use. Note that it is also perfectly valid to pass the whole timeEntry object to the TimeEntry component!

Adding data

While the example above works perfectly fine for presenting data, it wouldn't when we'd add data. The reason for this is that React expects you to use its local state management. React will continuously check for state changes and update the view accordingly.

We'll use the same hook as before, the useState hook. We'll name the state timeEntries and the setter will be named consistently, setTimeEntries. When initializing this hook a default value can be provided. Pass the timeEntries as the initial value.

Now that this component has been rewritten to use React's state hook, it's time to add new data and see this change be processed in the view. Add a temporary button that we can use to trigger this behavior:

<button onClick={handleClick}>Add time entry</button>

We're referring to handleClick as the handler function for the onClick event. Prefixing event handlers with handle is very common in the React community. It doesn't necessarily have to be named handleClick, something like addTimeEntry would've been perfectly fine. For simple components it might be smart to stick to recognizable function names like handleClick though.

The final step is to create the handler function and have it add a new time entry to the component's state. We'll use the setter setTimeEntries that was returned by the useState hook. When the state is an array or object it's very important to create a new array/object when modifying the state. Otherwise React won't observe the change causing the view not to be updated. The common approach for this is to define a new array/object and insert the existing data using the spread operator.

For convenience's sake and contrary to regular practice, we'll be giving our new object a unique identifier with the Math.random() method. We will replace this later on!

const handleClick = () => {
setTimeEntries([
...timeEntries,
{
id: Math.random(),
client: 'Port of Rotterdam',
startTimestamp: '2022-09-26T16:00:00.000Z',
stopTimestamp: '2022-09-26T18:00:00.000Z'
}
]);
}

This is all there's to it! Click the button and watch the new time entry appear alongside the other time entries.