All posts by admin

Deep Copy a JS object/array using recrusion

Check if its of type ‘object’

Arrays and objects are both of type “Object”, so if the inObject is neither an array nor object, we exit because we only deep copy objects and arrays.

Initialize to [] or {} depending on whether its an array or object

Initialization Phase. If the incoming object is an array, we initialize to an empty array [].
If its an object, we initialize our source to empty literal object {}

iterate over the index/key

Now that we have a correctly initialized empty data structure, we go on to doing the copying.
In JS, using for (key in obj) works for both object/arrays.

If its an array, each key is the element index
If its an object, then its key/value as is

Hence, say we create an array pass it in. Since all elements in the array are primitives, we do normal copying via assignment operator (=)


-- myDeepCopy -- [ 'a', 'b', 'c', 'd', 'e' ]
incoming object is an array!
key 0
value a
key 1
value b
key 2
value c
key 3
value d
key 4
value e
outObject [ 'a', 'b', 'c', 'd', 'e' ]

But! there’s a problem

If one of the values is an object, we’d be doing a shallow copy because the assignment operator (=) simply points the array[i] or obj[i] reference to the object.

This means we’re simply just pointing our outObject[key] to the same object value (inObject[key]) is pointing to. Hence, we dont’ have our own copy.
Let’s look at a test case here:

In our test, we have our src obj change its array luckyNumbers. However, we see the same change in our destination object. This proves that the two arrays’ luckyNumbers reference are pointing to the same array. This is an example of shallow copy.


-- myDeepCopy -- { name: 'ricky', age: 40, luckyNumbers: [ 6, 8 ] }
incoming object is an object!
key name
value ricky
key age
value 40
key luckyNumbers
value [ 6, 8 ]
outObject { name: 'ricky', age: 40, luckyNumbers: [ 6, 8 ] }

src object { name: 'ricky', age: 40, luckyNumbers: [ 8, 8 ] }
destination obj { name: 'ricky', age: 40, luckyNumbers: [ 8, 8 ] }

In order to make our own copy, we use recursion and let our ‘Initialization Phase’
create the deep initial object first, then copy over the primitives one by one.

where recursion executes myDeepCopy with array luckyNumbers [6, 8].
Then in that myDeepCopy, we allocate our own array to use during the Initialization Phase. We don’t want to share with others

Now when you run your test code, you’ll see that changes do the source won’t affect the destination:


-- myDeepCopy -- { name: 'ricky', age: 40, luckyNumbers: [ 6, 8 ] }
incoming object is an object!
key name
value ricky
key age
value 40
key luckyNumbers
value [ 6, 8 ]
-- myDeepCopy -- [ 6, 8 ]
incoming object is an array!
key 0
value 6
key 1
value 8
outObject [ 6, 8 ]
outObject { name: 'ricky', age: 40, luckyNumbers: [ 6, 8 ] }

src object { name: 'ricky', age: 40, luckyNumbers: [ 8, 8 ] }
destination obj { name: 'ricky', age: 40, luckyNumbers: [ 6, 8 ] }

Full Source

DNY client (12 – basic site)

dnyNodeAPI-basic-site
npm install to install all node modules
Then, npm run dev to run the server

dny-client-basic-site
npm install to install all node modules
Then, npm run start to run the client

So far we have a basic site with these functionalities:

Create a user

POST on http://localhost:8080/signup
body: JSON.stringify(user)

src/auth/index.js

src/user/Signup.js

When the user successfully creates an account, we will show a link to sign in.

Authenticate a user

POST on http://localhost:8080/signin
body: JSON.stringify(user)

We first use the email and password to try to get user user data and a token back, which are both packaged into a object called ‘data’. Once we get the data, we pass it into AuthenticateUser, which will store it via localStorage at property ‘jwt’. Thus, we can always access the value at property ‘jwt’. We get this value, which has properties token and user, which we can use.

src/user/Signup.js

Once we get the token and user data successfully into the localStorage, we redirect to the main page.
The main page’s component is Home

Also, we have a Menu for navigation and showing/hiding of links according to authentication.

Menu

Basically, we use IsAuthenticated to read localStorage’s jwt value. If it’s available, we show what we want to show, like the sign-out link, user’s name, id.

User Profile

GET on http://localhost:8080/user/${userId}

The main idea is that we must give the userId so the backend knows which user to fetch. We also need to give a token so that the backend can authenticate it.

There is a situation where if you’re on someone else’s (say tom’s) profile, and you click on the user profile (john) link in the Menu, the Profile’s component render function will be called first, which will fetch and render tom’s data again. But soon after, your click on john’s profile link will trigger john’s data to be fetched also. Hence, there will two fetches.

Edit user profile

PUT on http://localhost:8080/user/${userId}
body: user

Editing is basically about updating the user on the backend.

Once we update the user, we must remember to update the localStorage with the latest retrieved user data.

Delete user profile

removeUser does a fetch for Delete on http://localhost:8080/user/${userId}
where SignOutUser does a fetch for GET on http://localhost:8080/signout

Here, we tell the backend to clear cookie for this particular client. Cookies are stored per-user on the user’s machine. A cookie is usually just a bit of information. Cookies are usually used for simple user settings colours preferences ect. No sensitive information should ever be stored in a cookie.

View all users

GET on http://localhost:8080/users

DNY client (11 – Profile Photo Display)

Server side

First we put in the route

routes/user.js

Then we implement the function responder for when the user goes to /user/photo/:userId

Since we already retrieved the user object at function userById, everything is already in req.profile
We simply check for the image data, and then set the content type.

controllers/user.js

Client Side

we create a photoUrl so that our image control has a src.

src/user/EditProfile.js

However, there is an issue. The browser cache images and when you try to upload another image, and you come back to this page, the browser will be showing the old image.

In this situation, simply give the latest time and append it to the URL

DNY client (10 – Profile Photo Upload)

Client side

Add the html for an input control that accepts file images

src/user/EditProfile.js

Basically, we need to send the form data to the backend.
We need to use browser API called FormData

FormData interface provides a way to easily construct a set of key/value pairs representing form fields and their values.

We update our handleChange to reflect responding to photo file clicks.

src/user/EditProfile.js

We need to change our body so that we’re sending the formData, and not a string.
We also need to remove the Content-Type headers because its not json anymore. Its key/value formData

src/user/apiUser.js

Now, refresh the page and try sending a photo, for the PUT request, look at your FormData, you should see all the data there.

Server side

We comment out the previous updateUser function and write a new one.

controllers/user.js

Now, try to upload the photo.

DNY client (9 – Edit Users)

We first create our EditProfile component

src/user/EditProfile.js

Then, we add a Route to our EditProfile component. Whenever this link is clicked, it hits our EditProfile component.
src/MainRouter.js

Make sure we implement UpdateUser fetch operation, which does a PUT operation on the server for a particular user. That way, we can tell the server to update this particular user with this particular data.

Remember that when we give the user data in body, we need to stringify it. The server does not take objects as data. It takes a string.
If you were to send a body, the updateUser function on the server side won’t be triggered and it will be hard to debug. So make sure you always JSON stringify body data.

src/user/apiUser.js

EditProfile component source

Editing forms is about presenting a html form with textfield controls. We then put our state’s value in the textfield’s value property. Our state has already been updated with the latest value from the server via the initUserID function when the component was loaded.

DNY client (8 – Delete Users)

Client Side

removeUser tells the server side to look for the user with the specific ID. Then, remove that user from the database.

user/apiUser.js

auth/index.js

SignOutUser first looks at the client’s local storage. It removes the jwt key/value.
Then it tells the server to clear the cookie for this client.

Cookies are stored per-user on the users machine. A cookie is usually just a bit of information. Cookies are usually used for simple user settings colours preferences ect.

Cookies can be cleared through the browser’s settings via a “clear cookie” button. A cookie file can also be opened by the user when they go through your temporary Internet files history and can locate your cookie there. They can then open it and look at the contents. Hence, No sensitive information should ever be stored in a cookie.

We use server calls for removing the user and clearing the cookie in our DeleteUser functionality. After deleting is complete, we simply redirect the user to the home screen.

user/DeleteUser.js

Server side

controllers/user.js

DNY client (7 – Users)

Create the User file under user folder.

Include it into the router.

Then put it into the menu.

user/Users.js

The result is a list of users.

Props Change on Users

1) Go the users list http://localhost:3000/users
2) Click on View Profile link of any user. You’ll get to the profile page of that user with that user’s data.
3) If you were to click on your own profile link above, the component data wouldn’t change. This is because the URL is part of prop. Since you’ve changed the URL, you’re changing the prop. This change happens at this.props.match.params.userId.

This is the props of the component and when it changes, we need to fetch data for the profile user again. However, in our Profile component, we only do this for when componentDidMount. Thus, we need to implement fetch data in a component hook called componentDidUpdate like so:

user/Profile.js

DNY client (6 – Profile Page)

src/user/Profile.js

Notice when we added our Profile page, the path is /user/:userId.
This says whenever we have a URL in that format, we’ll hit the Profile component.

src/MainRouter.js

Use the user object you get from localStorage. Inside that object, you’ll get the user object. Access the _id of the user and use it for the url parameter when we click on the profile link.

src/core/Menu.js

When fetching data from Server