Building a React employee database page using Airtable and Quarkly

Have you heard about a tool like Airtable, but didn't know where to start? Then we invite you to the world of visual programming of building a database!







With this post, we begin a series of tutorials in which we will give practical examples of working with our Quarkly tool. In this tutorial, we will make a simple web application that will display company employees. When creating the application, not a single Russian Railways employee was hurt.







The front will be done using Quarkly, and the data will be pulled from the database into Airtable. At the output, we get a react application synchronized with the database.









Preamble. Why Airtable



Airtable is a popular no-code tool where you can make your databases large. They look like tables, but they have much more powerful functionality. In particular, for our lesson, Airtable was chosen because of the easy way to transfer data through the API.







If this is your first time hearing about Airtable, it will not be superfluous to read the official manual on the company's website before starting work . We also advise you not to be shy and ask questions in the Airtable Chat & Community telegram.







The front-end part of the work will be done in Quarkly, and for this we will use only two components:







  • Employee card . It will contain a photo, text data and two buttons: send an email and call. The card will receive this data from the parent component - the wrapper.
  • Wrapper . It will receive data from Airtable, generate cards and transfer data to them.


For those who do not have time to delve into the post in print format, we have prepared a video with subtitles and timecodes:











Part 1. Making visuals in Quarkly



Card creation:



  1. Quarkly, Airtable Example;
  2. ;
  3. . + Team;







  4. (StackItem) ;







    ยซยป Convert to Component. EmployeeCard.



  5. react-, -.


:



  1. . , ;



  2. EmployeeCard Stack. Stack , EmployeeCard: , ยซยป Convert to Component. EmployeeTable.







    , . Airtable.




2. Airtable



Airtable /.







  1. Add a base, . Start with a template;



  2. HR & Recruiting Employee directory. Use template;



  3. ;





, .









3. API



Airtable API. , , Airtable .







  1. API : https://airtable.com/api



  2. Employee directory. AUTHENTICATION.



  3. , EXAMPLE USING BEARER TOKEN (RECOMMENDED).



    :



    $ curl https://api.airtable.com/v0/app2MdLITmRTBsrkg/Employee%20directory \

    -H "Authorization: Bearer YOUR_API_KEY"




  4. YOUR_API_KEY. , . .



  5. API Generate API key;



  6. . 3. .




4. Airtable Quarkly



EmployeeTable , API.







  1. . Components <> EmployeeTable ( );



  2. :



  3. :



    import React from "react";
          
          





    :



    import React, { useEffect, useState } from "react";
          
          





    useEffect useState, ;
  4. , EmployeeCard:



    import EmployeeCard from "./EmployeeCard";
          
          



  5. children ( ) override (, ):



    const EmployeeTable = props => {
    	const {
    		children,
    		rest
    	} = useOverrides(props, overrides, defaultProps);
          
          





    :



    const EmployeeTable = props => {
    	const {
    		override,
    		rest
    	} = useOverrides(props, overrides, defaultProps);
          
          



  6. useState, :



    const [employees, setEmployees] = useState([]);
          
          



  7. useEffect, API Airtable setEmployees.



    , . fetch URL , ?view=All%20employees. headers API , 3 , 4.



    useEffect(() => {
    			fetch("https://api.airtable.com/v0/appWw7KBKSc9bPjZE/Employee%20directory?view=All%20employees", {
    				headers: {
    					'Authorization': 'Bearer YOUR_API_KEY'
    				}
    			})
    			.then(response => response.json())
    			.then(data => setEmployees(data.records.map(({ fields }) => fields)));
    	}, []);
          
          



  8. , props override. , .



    :



    return <Stack {...rest}>
    		{children}
    	</Stack>;
    };
          
          





    :



    return <Stack {...rest}>
    		{
    			employees.map(employee => <EmployeeCard  {...override("employeeCard")}  employee={employee} />)
    		}
    	</Stack>;
    };
          
          



  9. Ctrl + S ( Cmd + S Mac). :



    import React, { useEffect, useState } from "react";
    import { useOverrides, Stack } from "@quarkly/components";
    import EmployeeCard from "./EmployeeCard";
    const defaultProps = {
    	"margin-top": "40px"
    };
    const overrides = {};
    
    const EmployeeTable = props => {
    	const {
    		override,
    		rest
    	} = useOverrides(props, overrides, defaultProps);
    
    	const [employees, setEmployees] = useState([]);
    
    	useEffect(() => {
    			fetch("https://api.airtable.com/v0/appWw7KBKSc9bPjZE/Employee%20directory?view=All%20employees", {
    				headers: {
    					'Authorization': 'Bearer YOUR_API_KEY'
    				}
    			})
    			.then(response => response.json())
    			.then(data => setEmployees(data.records.map(({ fields }) => fields)));
    	}, []);
    	
    	return <Stack {...rest}>
    		{
    			employees.map(employee => <EmployeeCard  {...override("employeeCard")} employee={employee} />)
    		}
    	</Stack>;
    };
    
    Object.assign(EmployeeTable, {
    	...Stack,
    	defaultProps,
    	overrides
    });
    export default EmployeeTable;
          
          





    : API YOUR_API_KEY.


! Airtable, employees map. employees <EmployeeCard/>, .







EmpolyeeCard .









5. EmpolyeeCard



.







  1. . Components, EmployeeCard, <>.
  2. :



    import React from "react";
    import { useOverrides, Override, StackItem } from "@quarkly/components";
    import { Box, Text } from "@quarkly/widgets";
    const defaultProps = {
    	"width": "25%",
    	"lg-width": "50%",
    	"sm-width": "100%"
    };
    const overrides = {
    	"box": {
    		"kind": "Box",
    		"props": {
    			"height": "0",
    			"margin": "0 0 20px 0",
    			"padding-bottom": "100%",
    			"background": "url(https://images.unsplash.com/photo-1503443207922-dff7d543fd0e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=582&q=80) 50% 0/cover no-repeat"
    		}
    	},
    	"text": {
    		"kind": "Text",
    		"props": {
    			"color": "--grey",
    			"margin": "0",
    			"children": "CEO"
    		}
    	},
    	"text1": {
    		"kind": "Text",
    		"props": {
    			"as": "h3",
    			"font": "--headline3",
    			"margin": "5px 0 20px 0",
    			"children": "Nathan K. Joe"
    		}
    	},
    	"text2": {
    		"kind": "Text",
    		"props": {
    			"as": "p",
    			"margin": "20px 0 5px 0",
    			"children": "This space is 100% editable. Use it to introduce a team member, describe their work experience and role within the company. This is also a great place to highlight a team member's strong sides."
    		}
    	}
    };
    
    const EmployeeCard = props => {
    	const {
    		override,
    		children,
    		rest
    	} = useOverrides(props, overrides, defaultProps);
    	return <StackItem {...rest}>
    		<Override slot="StackItemContent" flex-direction="column" />
    		<Box {...override("box")} />
    		<Text {...override("text")} />
    		<Text {...override("text1")} />
    		<Text {...override("text2")} />
    		{children}
    	</StackItem>;
    };
    
    Object.assign(EmployeeCard, { ...StackItem,
    	defaultProps,
    	overrides
    });
    export default EmployeeCard;
          
          



  3. :



    } = useOverrides(props, overrides, defaultProps);
          
          





    :



    const { employee = {} } = rest;
          
          





    employee .
  4. , , . :



    <Box {...override("box")} />
          
          





    :



    <Box {...override("box")} background-image={`url(${employee.Photo && employee.Photo[0] && employee.Photo[0].url})`}/>
          
          





    :



    "background": "url(https://images.unsplash.com/photo-1503443207922-dff7d543fd0e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=582&q=80) 50% 0/cover no-repeat"
          
          





    :



    "background-size": "cover",
    "background-position": "center",
    "background-image": "url(https://images.unsplash.com/photo-1503443207922-dff7d543fd0e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=582&q=80) 50% 0/cover no-repeat"
          
          





    :



  5. , . API Airtable . https://airtable.com/api, .



    EMPLOYEE DIRECTORY TABLE.



    , :



    Name

    Department

    Home address

    Email address

    DOB

    Start date

    Phone

    Reports to

    Title

    Status

    Photo

    Location
  6. Title. :



    <Text {...override("text")} />
          
          





    :



    <Text {...override("title")} children={employee.Title} />
          
          





    overrides , .



    :



    "text": {
    	"kind": "Text",
    	"props": {
    		"color": "--grey",
    		"margin": "0",
    		"children": "CEO"
    	}
    },
          
          





    :



    "title": {
    	"kind": "Text",
    	"props": {
    		"color": "--grey",
    		"margin": "0",
    		"children": "Title"
    	}
    },
          
          





    :







    : .
  7. Name Home address.



    :



    <Text {...override("text1")} />
    <Text {...override("text2")} />
          
          





    :



    <Text {...override("name")} children={employee.Name} />
    <Text {...override("address")} children={employee['Home address']} />
          
          





    overrides. :



    "text1": {
    	"kind": "Text",
    	"props": {
    		"as": "h3",
    		"font": "--headline3",
    		"margin": "5px 0 20px 0",
    		"children": "Nathan K. Joe"
    	}
    },
    "text2": {
    	"kind": "Text",
    	"props": {
    		"as": "p",
    		"margin": "20px 0 5px 0",
    		"children": "This space is 100% editable. Use it to introduce a team member, describe their work experience and role within the company. This is also a great place to highlight a team member's strong sides."
    	}
    }
          
          





    :



    "name": {
    	"kind": "Text",
    	"props": {
    		"as": "h3",
    		"font": "--headline3",
    		"margin": "5px 0 5px 0",
    		"children": "Name"
    	}
    },
    "address": {
    	"kind": "Text",
    	"props": {
    		"as": "p",
    		"margin": "10px 0 5px 0",
    		"children": "Home address"
    	}
    },
          
          





    :



  8. Text . Department Reports to, DEPARTMENTS TABLE.



    :



    <Text {...override("address")} children={employee['Home address']} />
    <Text {...override("Start date")} children={`Start date: ${employee['Start date']}`} />
    <Text {...override("Status")} children={employee['Status']} />
    <Text {...override("DOB")} children={`Birth date: ${employee['DOB']}`} />
          
          









    "address": {
    	"kind": "Text",
    	"props": {
    		"as": "p",
    		"margin": "10px 0 5px 0",
    		"children": "Home address"
    	}
    },
    "Start date": {
    	"kind": "Text",
    	"props": {
    		"as": "p",
    		"margin": "10px 0 5px 0",
    		"children": "Start date"
    	
    	}
    },
    "Status": {
    	"kind": "Text",
    	"props": {
    		"as": "p",
    		"margin": "10px 0 5px 0",
    		"children": "Status"
    	}
    },
    "DOB": {
    	"kind": "Text",
    	"props": {
    		"as": "p",
    		"margin": "10px 0 5px 0",
    		"children": "Birth date"
    	}
    },
          
          





    :



  9. Now let's add two Link components , in which we will have Phone and Email :



    import { Box, Text } from "@quarkly/widgets";
          
          





    change to:



    import { Box, Text, Link } from "@quarkly/widgets";
          
          





    And add the following lines:



    <Link {...override("Email address")} children={employee['Email address']} href={`mailto:${employee['Email address']}`} />
    <Link {...override("Phone")} children={employee['Phone']} href={`tel:${employee['Phone']}`}/>
          
          





    Not forgetting about their overrides :



    "Email address": {
    	"kind": "Link",
    	"props": {
    		"margin": "10px 0 5px 0",
    		"color": "--primary",
    		"text-decoration": "none",
    		"children": "Email"
    	}
    },
    "Phone": {
    	"kind": "Link",
    	"props": {
    		"margin": "10px 0 5px 0",
    		"color": "--primary",
    		"text-decoration": "none",
    		"children": "Phone"
    	}
    },
          
          





    Checking the result:





Our final code looks like this:







import React from "react";
import { useOverrides, Override, StackItem } from "@quarkly/components";
import { Box, Text, Link } from "@quarkly/widgets";
const defaultProps = {
	"width": "25%",
	"lg-width": "50%",
	"sm-width": "100%"
};
const overrides = {
	"box": {
		"kind": "Box",
		"props": {
			"height": "0",
			"margin": "0 0 20px 0",
			"padding-bottom": "100%",
			"background-size": "cover",
			"background-position": "center",
			"background-image": "url(https://images.unsplash.com/photo-1503443207922-dff7d543fd0e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=582&q=80) 50% 0/cover no-repeat"
		}
	},
	"title": {
		"kind": "Text",
		"props": {
			"color": "--grey",
			"margin": "0",
			"children": "title"
		}
	},
	"name": {
		"kind": "Text",
		"props": {
			"as": "h3",
			"font": "--headline3",
			"margin": "5px 0 5px 0",
			"children": "Name"
		}
	},
	"address": {
		"kind": "Text",
		"props": {
			"as": "p",
			"margin": "10px 0 5px 0",
			"children": "Home address"
		}
	},
	"Start date": {
		"kind": "Text",
		"props": {
			"as": "p",
			"margin": "10px 0 5px 0",
			"children": "Start date"
		}
	},
	"Status": {
		"kind": "Text",
		"props": {
			"as": "p",
			"margin": "10px 0 5px 0",
			"children": "Status"
		}
	},
	"DOB": {
		"kind": "Text",
		"props": {
			"as": "p",
			"margin": "10px 0 5px 0",
			"children": "Birth date"
		}
	},
	"Email address": {
		"kind": "Link",
		"props": {
			"margin": "10px 0 5px 0",
			"color": "--primary",
			"text-decoration": "none",
			"children": "Email"
		}
	},
	"Phone": {
		"kind": "Link",
		"props": {
			"margin": "10px 0 5px 0",
			"color": "--primary",
			"text-decoration": "none",
			"children": "Phone"
		}
	},
};

const EmployeeCard = props => {
	const {
		override,
		children,
		rest
	} = useOverrides(props, overrides, defaultProps);
	
		const { employee = {} } = rest;

	
	return <StackItem {...rest}>
		<Override slot="StackItemContent" flex-direction="column" />
		<Box {...override("box")} background-image={`url(${employee.Photo[0].url})`}/>
		<Text {...override("title")} children={employee.Title} />
		<Text {...override("name")} children={employee.Name} />
		<Text {...override("address")} children={employee['Home address']} />
		<Text {...override("Start date")} children={`Start date: ${employee['Start date']}`} />
		<Text {...override("Status")} children={employee['Status']} />
		<Text {...override("DOB")} children={`Birth date: ${employee['DOB']}`} />
		<Link {...override("Email address")} children={employee['Email address']} href={`mailto:${employee['Email address']}`} />
		<Link {...override("Phone")} children={employee['Phone']} href={`tel:${employee['Phone']}`}/>
		{children}
	</StackItem>;
};

Object.assign(EmployeeCard, { ...StackItem,
	defaultProps,
	overrides
});
export default EmployeeCard;
      
      





We commit to GitHub and publish to Netlify:











We wait a few minutes and check: https://keen-varahamihira-c54ae1.netlify.app/













To check synchronization, change the data in the database:









They will now appear in the application:









In the future, we can style our elements with cards as we like, without breaking the configured import from Airtable. An example can be found here .







GitHub repository: https://github.com/quarkly-dev/Getting-data-from-Airtable-tutorial







Thank you for attention!







โ€” . , , .








All Articles