DEV Community

loading...
Cover image for Company Themed Resume Builder using react and redux

Company Themed Resume Builder using react and redux

shambhavijs profile image shambhavijs ・8 min read

This guide will help you in building a company themed resume builder using reactjs. You will fill in your details and then select the company you wish to apply for. This web application will generate your resume based on the company you have to apply for.

STEP 1 - Setting up redux store

First of all, we need to create a store which will contain the state tree of the application. I created the store in index.js file. To bring any changes to state in redux, we need to dispatch action and write its reducer function.

const store = createStore(rootReducer);
Enter fullscreen mode Exit fullscreen mode

Then we will wrap the App component inside Provider component. It makes the redux store available to all the nested components.

<Provider store={store}>
      <App />
</Provider>
Enter fullscreen mode Exit fullscreen mode

Now, we use a connect function inside all other components. It lets that component re-read the values of state whenever the state updates.

const mapStateToProps = (state) => {
    return{
        state
    }
};
const TwitterContainer = connect(mapStateToProps, null)(Twitter);
export default TwitterContainer;
Enter fullscreen mode Exit fullscreen mode

Connect() takes two arguments. Both are optional. I have used only the first one: mapStateToProps. It is called every time the store state is changed. It receives the entire store state and returns an object of data which is required by that component.

STEP 2 - Getting the details from user

Input fields are created to get user input. This includes name, contact number, email-id, work experience, education, projects, skills, links for linkedin and portfolio. The state have these as properties and will be empty initially. For work-ex, education, projects and skills, array is used. Each element of these array will have:
1) id
2) subheading
3) description

work_experience: [{
            id: 1,
            subheading: '',
            desc: ''
        }
]
Enter fullscreen mode Exit fullscreen mode

On entering input in these fields, an action will be dispatched.

const addName = (value) => {
        props.dispatch({
          type: "ADD_NAME",
          text: value
        });
 }
Enter fullscreen mode Exit fullscreen mode

The reducer check for the type of action and make changes in state. For taking input for fields like work-ex, education, projects and skills, id is also needed.

The code below is shown for subheading of work-ex

const addWorkSubheading = (i,value) =>{
        props.dispatch({
            type: 'ADD_WORK_SUBHEADING',
            id: i,
            text: value
        });
 }
Enter fullscreen mode Exit fullscreen mode

Similarly, actions are dispatched for other subheadings and descriptions.

To change the state according to the input from user, firstly action.id is compared to the id of all elements of work-ex array and the subsection to be changed is assigned to a variable and its index is assigned to another variable.
Now, if the length of array is one , then using spread operator state is spread and then inside work-ex array, first element is spread and value is assigned.
If the length is 2, then state is spread and the index of the subsection is checked using switch. If index is 0, then changes are made in the first element and second element is returned as it is and vice versa.
If the length is more than 2, then state is spread and work-ex array is sliced from 0 till subsectionIndex and returned as it is, the required subsection is spread and changes are made and then again work-ex is sliced from (subsectionIndex + 1) till the end and returned as it is.

case 'ADD_WORK_SUBHEADING':
            const subsection = state.work_experience.filter(w=>{ return w.id === action.id })[0];
            const subsectionIndex = state.work_experience.findIndex(w=>{ return w.id === action.id });
            if (state.work_experience.length <= 1){
                return{
                    ...state,
                    work_experience: [
                        {
                            ...state.work_experience[0],
                            subheading: action.text
                        }
                    ]
                };
            }
            else if (state.work_experience.length === 2) {
                switch (subsectionIndex) {
                    case 0:
                        return {
                            ...state,
                            work_experience: [
                                {
                                    ...state.work_experience[0],
                                    subheading: action.text
                                },
                                state.work_experience[1]
                            ]
                        };
                    case 1:
                        return {
                            ...state,
                            work_experience: [
                                state.work_experience[0],
                                {
                                    ...state.work_experience[1],
                                    subheading: action.text
                                }
                            ]
                        };
                }
            }
            else {
                return {
                    ...state,
                    work_experience: [
                        ...state.work_experience.slice(0, subsectionIndex),
                        {
                            ...subsection,
                            subheading: action.text
                        },
                        ...state.work_experience.slice(subsectionIndex+1, state.work_experience.length)
                    ]
                };
            }
Enter fullscreen mode Exit fullscreen mode

Similarly, state is changed for other subheadings and descriptions.

resume1

Now to add subsection, there is a plus button. When this button is clicked, an action is dispatched. And in reducer, firstly state is spread. Then work-ex is spread and one more element is added to the array with id as length of array +1.

case 'ADD_WORK_SUBSECTION':
            return {
            ...state,
            work_experience: [
                ...state.work_experience,
                {
                    id: state.work_experience.length+1,
                    subheading: '',
                    desc: ''

                } 
                ]
            };
Enter fullscreen mode Exit fullscreen mode

STEP 3 - Preview Resume

On filling the details, you can preview the resume for different companies.

resume2

On clicking these buttons, you will get your themed resume.
The data entered by the user will be displayed with custom styling based on the company one selects from this page.

STEP 4 - Sign in with Github

For building sign-in feature, I have used react-firebase auth.
The follwing steps are to be followed when building authentication using react-firebase:

Step 1

Create a project in firebase and enable the signup method you want to use for your project.

Step 2

Install react firebase.
npm i @react-firebase/auth

Step 3

Get your firebase config using this:
https://console.firebase.google.com/project/PROJECT_NAME/settings/general/
Replace PROJECT_NAME with your project name in firebase.
Paste this in a file named as config.js and export config.

Step 4

Import firebase in project.

import firebase from "firebase/app";
import "firebase/auth";
import {
  FirebaseAuthProvider,
  FirebaseAuthConsumer
} from "@react-firebase/auth";
import { config } from "./config";
Enter fullscreen mode Exit fullscreen mode

Step 5

Wrap your app code inside FirebaseAuthProvider and FirebaseAuthConsumer:

<FirebaseAuthProvider firebase={firebase} {...config}>
<div>
            <FirebaseAuthConsumer>
            {({ isSignedIn, user, providerId}) => {
                if(isSignedIn === true){
                    return(
                        <div>
                            <Router>
                            <Switch>
                              <Route exact path="/" render={() => <MainApp uid={user.uid}/>} />
</div>
);
}
else{
                    return(
                      <div className="signin-div">
                        <button
                        className="signin"
                        onClick={() => {
                        const githubAuthProvider = new firebase.auth.GithubAuthProvider();
                        firebase.auth().signInWithPopup(githubAuthProvider);
                        }}>
                        Sign In with Github
                        </button>
                      </div>
                    );
                }

          }}
            </FirebaseAuthConsumer>
        </div>
      </FirebaseAuthProvider>
Enter fullscreen mode Exit fullscreen mode

FirebaseAuthConsumer returns isSignedIn as true if the user has signed in and as false if there is no user signed in.
Using this condition, either MainApp and all other components are rendered or a page is rendered with sign up button.

STEP 5 - Storing user's data in Firebase Cloud Firestore

The following steps are to be followed for creating and storing data in Cloud Firestore

Step 1

Go to your project and navigate to Cloud Firestore. Select starting mode as Test mode. Select location and click on done.

Step 2

Install Cloud Firestore SDK
npm install firebase@8.4.3 --save

Step 3

Create a file database.js in your project and import firestore

import firebase from "firebase/app";
import "firebase/firestore";
Enter fullscreen mode Exit fullscreen mode

Step 4

Initialise Cloud Firestore and db and export db.

firebase.initializeApp({
  apiKey: '### FIREBASE API KEY ###',
  authDomain: '### FIREBASE AUTH DOMAIN ###',
  projectId: '### CLOUD FIRESTORE PROJECT ID ###'
});

const db = firebase.firestore();
export default db;
Enter fullscreen mode Exit fullscreen mode

Import db in files where either you have to save data or fetch data.

Step 5

Now to save data in firestore, a save button is used. This button is available on the user details page.

resume3

On clicking this button, following code will run.

const saveData = () => {
        db.collection("users").doc(props.uid).set({
            name: props.state.name,
            contact: props.state.contact,
            email: props.state.email,
            work_experience: props.state.work_experience,
            education: props.state.education,
            projects: props.state.projects,
            skills: props.state.skills,
            linkedin: props.state.linkedin,
            portfolio: props.state.portfolio
        })
        .then(() => {
            console.log("Document successfully written!");
        })
        .catch((error) => {
            console.error("Error writing document: ", error);
        });
    }
Enter fullscreen mode Exit fullscreen mode

On running this code, a collection "users" will be created in database. While authentication, we get uid inside user. In the database, different documents will be created for different uid. The data from state will be saved to database using .set().

STEP 6 - Retrieving user's data from Firebase Cloud Firestore

Data retrieval from Cloud Firestore will happen when the Home page will mount.

const fetchUsers = async () => {
        await db.collection("users").doc(props.uid).get().then((doc) => {
            if (doc.exists) {
                console.log("Document data:", doc.data().portfolio);
                props.dispatch({
                    type: "ADD_DATA",
                    text: doc.data()
                })
            } 
            else {
                console.log("No such document!");
            }
            }).catch((error) => {
            console.log("Error getting document:", error);
            });
    };

    useEffect( () => { fetchUsers() }, [] );
Enter fullscreen mode Exit fullscreen mode

Using useEffect with an empty array, we'll not be watching any variables. So, it will only update state only on first render like componentDidMount().
Inside fetchUsers, .get() is called with "users" as collection and "uid" as document. It will retrieve data for that uid. An action is then dispatched and in reducer function the following changes will occur in state.

case 'ADD_DATA':
            return{
            ...state,
            name: action.text.name,
            contact: action.text.contact,
            email: action.text.email,
            work_experience: action.text.work_experience,
            education: action.text.education,
            projects: action.text.projects,
            skills: action.text.skills,
            linkedin: action.text.linkedin,
            portfolio: action.text.portfolio,
            firebaseprocessing: false
            };
Enter fullscreen mode Exit fullscreen mode

STEP 7 - Sharing resume's link

On choosing any company for which you wish to have your resume created, you'll get to a page where you'll see your resume and share button. On clicking this button, you'll get a link. You should copy this link and paste it wherever you wish to.

resume4

To get this link, firstly we should have the hostname, protocol and port.

const hostname = window.location.hostname;
const protocol = window.location.protocol;
const port = window.location.port;
Enter fullscreen mode Exit fullscreen mode

Now to display this link, a div will be created which will contain this link and will be visible only when share button is clicked and then it will disappear when clicked anywhere other than the div. For this, I used ClickAwayListener. You can read more about it on https://www.npmjs.com/package/react-click-away-listener.

{(props.state.link === true)?
                    <ClickAwayListener onClickAway={e=>hideLink()}>
                    <section className="link-part3" >
                        {(port === 0 || port === '')?
                            <p>Copy this link {protocol}//{hostname}/{props.uid}/amazon</p>:
                            <p>Copy this link {protocol}//{hostname}:{port}/{props.uid}/amazon</p> 
                        }
                    </section>
                    </ClickAwayListener>:
                    <Fragment />
                }
Enter fullscreen mode Exit fullscreen mode

In this section of code, first we'll check if props.state.link is true or not. This is used to display link. Then, it is checked if port number is 0 or empty string or any other value.
If it is 0 or empty string, then this is the default case (80 for http and 443 for https). In default case, we don't need to mention port number in the link.
If it is not 0 or empty string, then we need to mention the port number in the link.
The link will also have uid which will be used to retrieve data when this link is entered.

STEP 8 - Viewing resume using the link

For indicating that the link is entered externally, a parameter external is passed to the props when routing this link.

<Route path="/:id/youtube" render={() => <Youtube external={true} />}/>
Enter fullscreen mode Exit fullscreen mode

Inside the Youtube component, while using useEffect, we check if props.extrenal is true or not. This checks whether the link is entered externally or not.
If props.external is true, then fetchUsers is called with id. We use this id from the link entered. For this, useParams() is used.
If props.external is false, it is internal link and fetchUsers is called with props.uid.
This is used to get the document corresponding to the user id whose resume is being viewed.

const fetchUsers = async (i) => {
        await db.collection("users").doc(i).get().then((doc) => {
            if (doc.exists) {
                console.log("Document data:", doc.data().portfolio);
                props.dispatch({
                    type: "ADD_DATA",
                    text: doc.data()
                });
            } 
            else {
                console.log("No such document!");
            }
            }).catch((error) => {
            console.log("Error getting document:", error);
            });
    };

    useEffect( () => { 
        (props.external)?
        fetchUsers(id):
        fetchUsers(props.uid) 
    }, [] );
Enter fullscreen mode Exit fullscreen mode

Now, user's data is retrieved from Cloud Firestore and the state is changed according to that. And the data from store is used to display data on resume when any of the link is entered.

Source Code: https://github.com/shambhavijs/themed-resume
Live Demo: https://bit.ly/2SiZUZ4

Discussion (9)

Collapse
bajro17 profile image
Bajro

There are few things to change and add. First of all great job I love it. But phone numbers you never write in numerical value only like text mostly because of country code. I would probably add in that personal section also add extra fields where you can enter the title and value of the field. Everything else looks good ♥💓

Collapse
hurleyng profile image
Hurley-Ng

Hi, I love this your's the theme you are using, may I know what the the theme you are, if you don't mind!
Thanks 🙏🏼

Collapse
shambhavijs profile image
shambhavijs Author

I used pure CSS for styling everything from scratch.

Collapse
hurleyng profile image
Hurley-Ng

Thanks for sharing!

Collapse
thatanjan profile image
Anjan Shomodder

wow that's so cool. Have you ever used redux/toolkit? It really simplify the redux setup with react

Collapse
bajro17 profile image
Bajro

Also, I get this all the time No such document! I guess your CSS not loading correctly or something

Collapse
shambhavijs profile image
shambhavijs Author

I'll look into this. Thanks for informing.

Collapse
shubhambigdream profile image
Collapse
shambhavijs profile image
shambhavijs Author

Thanks Shubham.

Forem Open with the Forem app