Web application that connects programmers based on their experience, interests and the type of person they are looking for (mentor, buddy, mentee) through matching and contact sharing.
Here's my PR for Contacts, it consists from following tasks:
Add Contacts Page and Message Component
Add functionality to contacts page and components
Add mock API
Add message cathegories and corresponding components
Add displaying messages
Fix CSS styles according to structure changes
Add ReadMore component
Add buttons
Add TODO comments
And now to explain the magic!
PAGE
FUNCTION
We have four types of messages - with matched contacts, new match suggestions, matches I suggested and matches I ignored. To display messages in those four blocks, each block with it's title, Jindra helped me created groupMessagesByType function with message: MesageData[] as it's parameter. MessageData[] is an array filled with information about each message (for now it's type and content).
TYPE
We set the type of Record<MessageType, MessageData[]>. MessageType are our four message types mentioned above defined in another file. Record<Key, Type> [docs] is a TypeScript device for type transformation. In this case, the object we're creating with groupMessagesByType function now has values of MessageTypeas keys and MessageData[]array values as values of those keys.
REDUCE
Now to the body of the groupMessagesByType function! The functional way to go through all messages a user has is reduce. This is mainly because reduce is immutable (you're never changing original array in the middle of your loop), it iterates over all elements in an array and accumulates them into one "thing" (whatever you define) with a return. This gives it many advantages over forEach and other loops (ways to "filter" or go through all data).
Reduce has two parameters - array.reduce(callback[, initialValue]) (initialValue is optional).
FIRST PARAMETER - CALLBACK
In our case, our callback is an anonymous arrow function(previousGroupMessages, currentMessage) => { return ... }. It's two parameters previousGroupMessages and currentMessage define what messages we already went through and what message we're iterating over right now.
Our anonymous array function (callback) uses so called spread operator..., which clones (copies) object it's used on. ...previousGroupMessages therefore are all messages reduce (loop) already went through with all their types and data. As such, the loop acknowledges what previousGroupMessages it has, what currentMessage it has and then pushes the currentMessage into it's correct type category. The currentMessage becomes a part of previousGroupMessages and reduce goes to next iteration (evaluates next currentMessage) until there are no messages left.
SECOND PARAMETER - INITIAL VALUE
Second parameter of reduce after callback (our anonymous arrow function) is an initialValue. Our initialValue is an object {}defining empty arrays []for each MessageType as in here [MessageType.MATCH]: [].
And when there are no messages to iterate over/go through, our reduce returns same structure as initalValue object {} with four MessageType types and each has an array full of corresponding messages. :)
Since we don't have a back-end/API yet, we set up an object full of fakeMessages. Each one only has a content (it's text) and it's type (correspoding with MessageType enum defined in different file).
DISPLAY FAKEMESSAGES
To display those fakeMessages (later our API data) in a Contact page, we create a variable const messageGroups = groupMessagesByType(fakeMessages) using our groupMessagesByType function that uses reduce to loop/iterate through all user's messages and returns four separate categories with their corresponding messages.
OBJECT.ENTRIES()
We then want to use .map function to display them, but our types could get all mixed up with a complexity of our value (remember, messageGroups const has the Record<Key, Type> type as Record<MessageType, MessageData[]>). To successfully map this, we use Object.entries()JavaScript method [docs] that can map objects with both keys and types.
MAPObject.entries(messageGroups).map takes const messageGroups as it's parameter and then maps it using an anonymous arrow function ([type, messages]) => ( return ). It's parameters (the message and it's type) are defined in [] array parentheses as a way to deconstruct an array of entries (so we can easily refer to it in the code).
RETURN
As a return, we then display our HTML structure using Styled Components and use key and message parameters to define every message that will be displayed in a <Message key={${type}_${index}} message={message} />. The magic in the key happens because Object.entries() wanted to force our key as a string which wouldn't work so we had to be more precise with it's declaration.
Returning messages like this also allows us to say the whole <MessageBoxBorder /> element is hidden when there is 0 messages (messages of lenght 0) and to display the title (to define where one MessageType starts and other begins) for the whole element, not each message.
COMPONENT
In the component, all that remains to do is to switch between components (each MessageType has it's own component in the Message folder due to them having different buttons/submit options).
Tadá!
Please, feel free to ask for details! I'm barely starting to understand this myself.
DESKTOP / MOBILE VIEW
( I'm not solving the size of the titles yet because I presume this will be set globally when we choose the font and add appropriate rem font sizes! That's why the font is quite big on the mobile!)
Here's my PR for Contacts, it consists from following tasks:
And now to explain the magic!
PAGE
FUNCTION We have four types of messages - with matched contacts, new match suggestions, matches I suggested and matches I ignored. To display messages in those four blocks, each block with it's title, Jindra helped me created
groupMessagesByType
function withmessage: MesageData[]
as it's parameter. MessageData[] is an array filled with information about each message (for now it'stype
andcontent
).TYPE We set the type of
Record<MessageType, MessageData[]>
. MessageType are our four message types mentioned above defined in another file.Record<Key, Type>
[docs] is a TypeScript device for type transformation. In this case, the object we're creating withgroupMessagesByType
function now has values ofMessageType
as keys andMessageData[]
array values as values of those keys.REDUCE Now to the body of the
groupMessagesByType
function! The functional way to go through all messages a user has isreduce
. This is mainly because reduce is immutable (you're never changing original array in the middle of your loop), it iterates over all elements in an array and accumulates them into one "thing" (whatever you define) with a return. This gives it many advantages over forEach and other loops (ways to "filter" or go through all data).Reduce has two parameters -
array.reduce(callback[, initialValue])
(initialValue is optional).FIRST PARAMETER - CALLBACK In our case, our
callback
is an anonymous arrow function(previousGroupMessages, currentMessage) => { return ... }
. It's two parameterspreviousGroupMessages
andcurrentMessage
define what messages we already went through and what message we're iterating over right now.Our anonymous array function (callback) uses so called spread operator
...
, which clones (copies) object it's used on....previousGroupMessages
therefore are all messages reduce (loop) already went through with all their types and data. As such, the loop acknowledges whatpreviousGroupMessages
it has, whatcurrentMessage
it has and then pushes thecurrentMessage
into it's correct type category. ThecurrentMessage
becomes a part ofpreviousGroupMessages
and reduce goes to next iteration (evaluates next currentMessage) until there are no messages left.SECOND PARAMETER - INITIAL VALUE Second parameter of reduce after callback (our anonymous arrow function) is an
initialValue
. Our initialValue isan object {}
definingempty arrays []
for each MessageType as in here[MessageType.MATCH]: []
.And when there are no messages to iterate over/go through, our reduce returns same structure as initalValue object {} with four MessageType types and each has an array full of corresponding messages. :)
Since we don't have a back-end/API yet, we set up an object full of
fakeMessages
. Each one only has acontent
(it's text) and it'stype
(correspoding withMessageType
enum defined in different file).DISPLAY FAKEMESSAGES To display those
fakeMessages
(later our API data) in a Contact page, we create a variableconst messageGroups = groupMessagesByType(fakeMessages)
using ourgroupMessagesByType
function that uses reduce to loop/iterate through all user's messages and returns four separate categories with their corresponding messages.OBJECT.ENTRIES() We then want to use
.map
function to display them, but our types could get all mixed up with a complexity of our value (remember,messageGroups
const has theRecord<Key, Type>
type asRecord<MessageType, MessageData[]>
). To successfullymap
this, we useObject.entries()
JavaScript method [docs] that can map objects with both keys and types.MAP
Object.entries(messageGroups).map
takes constmessageGroups
as it's parameter and then maps it using an anonymous arrow function([type, messages]) => ( return )
. It's parameters (the message and it's type) are defined in [] array parentheses as a way to deconstruct an array of entries (so we can easily refer to it in the code).RETURN As a return, we then display our HTML structure using Styled Components and use key and message parameters to define every message that will be displayed in a
<Message key={
${type}_${index}} message={message} />
. The magic in the key happens because Object.entries() wanted to force our key as a string which wouldn't work so we had to be more precise with it's declaration.Returning messages like this also allows us to say the whole element, not each message.
<MessageBoxBorder />
element ishidden
when there is 0 messages (messages of lenght 0) and to display the title (to define where one MessageType starts and other begins) for the wholeCOMPONENT
In the component, all that remains to do is to switch between components (each MessageType has it's own component in the Message folder due to them having different buttons/submit options).
Tadá!
Please, feel free to ask for details! I'm barely starting to understand this myself.
DESKTOP / MOBILE VIEW
( I'm not solving the size of the titles yet because I presume this will be set globally when we choose the font and add appropriate rem font sizes! That's why the font is quite big on the mobile!)