Now that we've managed to create a Note and be able to add it to our back-end, the next step is to
be able to take these notes that we've stored and be able to serve it back up onto the front-end.
So there's a lot of this back and forth going on, but it's all done in order to be able to persist
our data across time.
So as I mentioned, at the moment, whenever we create a Note and we refresh our front end, it disappears
but that actually does, in fact, get added into our back-end.
And you can see now our notes List contains a Note1 and also this mumbo jumbo that I just typed in
just now.
So how can we get that back out and to be able to display it in our app?
Well, the first thing we're going to do is we're going to work on our back-end and we're going to create
a new function.
Now, it's going to be a public function,
but remember, when we're reading from the ICP blockchain, it's done a lot faster when we don't need
to modify anything.
And because all we're doing is just reading this list of notes, we can add that "query" keyword to
speed the whole thing up a lot more.
We're creating a public query function and I'm going to call it readNotes, and it's not going to accept
any inputs, but it is going to return asynchronously and going to give us, not a list of notes, but
actually an array of notes.
So an array is another object in Motoko code that we can use, and structurally it's a lot more similar
to the arrays that we've got in JavaScript.
So if we return an array of notes, then we'll be able to work with it much more easily when we get hold
of it in our JavaScript.
Now, why are we storing it as a list, though?
Because in a lot of the operations we're doing, when we're pushing Notes onto it or when we're finding
a Note to delete later on, working with something that's an array which is serialized is not very efficient
on the blockchain.
So when we're reading it, we're going to get hold of our lists.
So we're going to return our notes, but we're going to convert it into an array.
So we're going to use a function from our List documentation that I found which is called toArray,
and this will convert a list to an array.
There was quite a lot of digging around in order to find these because as you can tell, going through
the documentation, the Motoko programming language is still not yet fully featured,
so it's not got as many utility functions as, say, JavaScript or Python,
but we can make it work.
That's what we're going to use.
We're going to say List.toArray.
And this takes just one argument, which is the list that you want to convert, and it's going to return
it in the format of an array.
We're going to pass our notes into here, and we're going to return that as an array of Note objects.
So the Motoko side is pretty simple, but now we have to address how do we get hold of it when our page
loads up?
Because when we add a newNote, the setNotes method makes sure that this note's constant is updated,
and because we're using the useState hook, that means whenever it's updated, it's going to re-render
the screen and be able to draw on the newNote that was created.
But now how do we tap into the moment when the first render is done,
when our website first loads up?
Well, in order to do that, we have to use another React hook.
And this is called the useEffect
hook. useEffect is triggered every time the render function gets called in React.
So every time the React component re-renders,
so in this case, it's the app component,
and of course that means the first time when the website is rendered to be displayed, it will also
be triggered.
So let's go ahead and add this to our code so that we can trigger a function whenever the app component
is re-rendered.
This function useEffect takes two parameters,
the first parameter is the function that should be called whenever the re-rendering happens
and I'm going to use the shorthand with my fat arrows that comes from ES6.
And this function takes no inputs and no outputs, but it is going to do something.
So first, just to assure ourselves that it's actually working,
let's first console.log() when the useEffect is triggered. And let's Save,
let's go back to our Keeper app, and open up our console.
And you can see that useEffect is triggered already because it's been reloaded.
So if I reload this website you can see that line gets logged.
So in that moment, it would be great if we could also pull out the array of notes from our Motoko code.
Inside our useEffect,
we're going to call a function that we're going to create, which is going to be called fetchData().
And the reason why I'm splitting out this fetchData is because this fetchData has to be asynchronous
and useEffect can't really be turned into an asynchronous function itself.
Let's create this async function,
fetchData(),
and let's make it get hold of our data from our Motoko code.
So we're going to tap into our dkeeper canister,
and we're going to call that method that we created here called readNotes,
and this then should be able to return us that array which we can store in a local constant, which
we can call the notesArray. Because this is going to be returned asynchronously,
we have to wait on it.
So we have to add the "await" keyword in front of this method call so that we wait for this to come
back before we continue.
Now, whenever our window loads, it's going to trigger the console.log(), but also our fetchData.
And then this method is going to wait until it gets that array back,
and once it gets it though, we want to be able to update our notes using the update method setNotes.
So just below this constant, I'm going to say setNotes.
And inside I'm just going to say, set everything to the new objects inside the notesArray.
And that is going to trigger a re-render because our state updated.
So you can imagine that we could get ourselves very quickly into an infinite loop here,
right?
Because we have setNodes which is going to update the state of our nodes constant, which then is
going to trigger a re-render of our app, React component,
and as we mentioned, every time the app component gets re-rendered, it triggers the useEffect.
So this could go on forever and we can see this happening when we console.log().
If we go ahead and run "dfx deploy",
you can see that useEffect is triggered many, many, many times.
And when we want to add a newNote, you can see that it sort of works,
but useEffect is triggered so many times because it's in this endless loop.
So what we need to know is that the useEffect hook actually has a second parameter and the second
parameter, is an array where we can specify a particular prop or some sort of variable which it checks
to see if it's changed before it triggers the function in useEffect.
So we can use this second parameter to prevent the useEffect from triggering more than once by putting
in an empty array.
And this will ensure that useEffect will run only once.
So after this first argument which is our function, we're going to add a comma and add an empty array
so that it stops once this has completed once, which is all we want because we only want this to
happen when the re-render or when the website reloads.
So now let's hit Save.
Let's go back to our Keeper app and you can see that useEffect is triggered once.
But if we go ahead and take a note and click add, you can see the Note is added in there.
But now, because these Notes are created on the front-end and persisted on the back-end, if we hit Reload,
they don't disappear because they're being read from our Motoku backend on the blockchain and they're
being pulled out each time we reload.
You can write as many Notes as you want, and now they'll actually stay on the screen.
Now, there's just one thing that you might have noticed, which is the order
the Notes go in.
So if I go ahead and add Note 5 which will be the latest note, if I click add, you see it gets
added at the end here.
But then when I click Refresh, because of the way our push function works by pre-pending a value,
so the new value gets added to the beginning of the list, then it kind of messes up the order every
time we refresh.
So how can we fix this?
Well, it's actually quite a nice thing that new Notes get added to the front, I think, because that's
how it normally works on Google Keep
anyways. If you imagine you had a list of loads of these notes and it gets added somewhere at the bottom,
you won't even see it.
It should actually be in reverse chronological order in order to make sense.
The only thing we have to change then is when we add it to the frontend,
remember that it updates because setNotes triggers an update in the state.
And here we're saying let's get hold of the previous notes and let's create this newNote and we're
taking the newNote and adding it to the end of all of the previous notes.
So if we just simply reverse this array,
so we say, let's add the new note to the beginning, and then we'll add the previous notes at the end,
then we're going to get a different order, right?
So if we hit Save, we go back and we add a new note, we'll call it Note 6 and click Add,
you can see it now gets added to the very beginning.
And if we Refresh our website, nothing happens.
None of the orders change and it works as we would expect it to.
So there you have it.
In this lesson, we've not only been able to persist our code and be able to read from our backend
using the ICP blockchain, we've also fixed our adding functionality so that the order that notes get
added in is more logical.
In the next lesson we've got the final thing we need to implement which is deleting these, because
at the moment you can see that I can delete them on the frontend and it works because we're updating
the state here using deleteNote,
but as soon as I hit Refresh and that re-render gets triggered, it's going to pull the notes from my
backend and it's just going to reappear, which is not what we want.
Let's see how we can persist that in the next lesson.