Skip to content

[Submissions Closed] Participating in #HacktoberFest? Here's some beginner friendly ways to contribute to CodeSandbox! #2621

@Saeris

Description

@Saeris

Update 2: Thanks everyone who participated! We're asking that no one open any additional PRs at this point for Hacktoberfest. We've still got a lot of submissions to review and merge before the 31st and we'd like to make sure that everyone who contributed gets credited for their work.

Update: We've received a ton of submissions from this and we're really grateful to everyone who's participated this year! As you may have noticed, we're a bit behind on merging your PRs and updating the list of components to be refactored. This week we're going to go through all the remaining open PRs in order of submission and either request changes or merge them. Going forward, we're sticking with the first-come first-served approach, meaning any duplicate submissions will simply be closed. If you're still considering making a contribution, you can up your chances of getting your PR merged if you can make sure someone else hasn't beaten you to that part of the code base by searching through all of the Hacktoberfest PRs first.

Hey everyone! As you might already know, this month is #Hacktoberfest, which is an event that encourages people (especially newcomers) to participate in open source development. Since CodeSandbox's client is an open source project, we thought it would be a good idea to throw together a small guide on how you can make some quick contributions and rack up those PRs.

Right now we're working on overhauling the code base to transition everything to Typescript and replace our old state management solution (Cerebral) with Overmind (both developed by our own @christianalfoni). We're at a point where a lot of the groundwork for these big refactors has been done, and now all that's left is to implement changes across the application. We've come up with some patterns and best practices around how to do this, which we'll explain below.

About the Event

But first let's quickly go over Hacktoberfest and how you can use this issue as a guide to participating in the event. Here are the rules as explained on the official website:

Event details

Hacktoberfest® is open to everyone in our global community. Whether you’re a developer, student learning to code, event host, or company of any size, you can help drive growth of open source and make positive contributions to an ever-growing community. All backgrounds and skill levels are encouraged to complete the challenge.

Hacktoberfest is open to everyone in our global community!
Pull requests can be made in any GitHub-hosted repositories/projects.
Sign up anytime between October 1 and October 31.

Rules

To qualify for the official limited edition Hacktoberfest shirt, you must register and make four pull requests (PRs) between October 1-31 (in any time zone). PRs can be made to any public repo on GitHub, not only the ones with issues labeled Hacktoberfest. If a maintainer reports your pull request as spam or behavior not in line with the project’s code of conduct, you will be ineligible to participate. This year, the first 50,000 participants who successfully complete the challenge will earn a T-shirt.

To help you get started, we've put together a list of components that still need to be refactored to use Overmind. We'd ask that you please choose up to 4 components from this list and create separate PRs for each. You can reply to this issue to let other participants know which components you plan on refactoring (and using your comment, we'll mark who's doing what on the list to help people choose). We'd like to make it so as many people as would like to be able to participate in the event, so that's why we would appreciate a maximum of 4 contributions per person.

Contributing

First, if you've never contributed to CodeSandbox before, we'd recommend you check out our Contributing Guide which covers some basics to help you get started running the project locally and how to submit your code.

To make a Hacktoberfest contribution, please create a new PR with:

  1. The component name your PR refactors
  2. Add the 🔨 Refactor, 🧠 Overmind, and most importantly the Hacktoberfest labels
  3. Request a review from @Saeris and @christianalfoni so that we can make sure your PR gets merged

image

Important! Your PR has to have the Hacktoberfest label such as in the screenshot above for it to count towards your 4 PRs! And don't forget to signup on the event homepage: https://hacktoberfest.digitalocean.com/profile

Hacktoberfest is an event run by a third party organization, so you have to follow their rules in order for your work to count towards a T-shirt!

For the refactor itself, in some cases it's really rather simple. Many of our components have already been re-written as functional components which use React hooks (in some cases, a component may need to be additionally refactored from a class component to a functional component). As part of our transition from Cerebral, we used to some glue code in our initial tests, which follow a pattern similar to this:

import { Button } from '@codesandbox/common/lib/components/Button';
import Input from '@codesandbox/common/lib/components/Input';
import Margin from '@codesandbox/common/lib/components/spacing/Margin';
import track from '@codesandbox/common/lib/utils/analytics';
import { inject, hooksObserver } from 'app/componentConnectors';
import React from 'react';

import {
  WorkspaceSubtitle,
  WorkspaceInputContainer,
} from 'app/pages/Sandbox/Editor/Workspace/elements';

import { Error } from './elements';

type Props = {
  style?: React.CSSProperties;
  store: any;
  signals: any;
};
export const CreateRepo = inject('store', 'signals')(
  hooksObserver(
    ({
      style,
      signals: {
        git: { repoTitleChanged, createRepoClicked },
      },
      store: {
        editor: { isAllModulesSynced },
        git: { repoTitle, error },
      },
    }: Props) => {
      const updateRepoTitle = ({
        target: { value: title },
      }: React.ChangeEvent<HTMLInputElement>) => repoTitleChanged({ title });
      const createRepo = () => {
        track('Export to GitHub Clicked');
        createRepoClicked();
      };

      return (
        <div style={style}>
          {!isAllModulesSynced && (
            <Error>Save your files first before exporting.</Error>
          )}

          {error && <Error>{error}</Error>}

          <WorkspaceSubtitle>Repository Name</WorkspaceSubtitle>

          <WorkspaceInputContainer>
            <Input onChange={updateRepoTitle} value={repoTitle} />
          </WorkspaceInputContainer>

          <Margin horizontal={1} bottom={1}>
            <Button
              block
              disabled={error || !repoTitle || !isAllModulesSynced}
              onClick={createRepo}
              small
            >
              Create Repository
            </Button>
          </Margin>
        </div>
      );
    }
  )
);

To this:

import { Button } from '@codesandbox/common/lib/components/Button';
import Input from '@codesandbox/common/lib/components/Input';
import Margin from '@codesandbox/common/lib/components/spacing/Margin';
import track from '@codesandbox/common/lib/utils/analytics';
import { useOvermind } from 'app/overmind';
import React from 'react';
import {
  WorkspaceSubtitle,
  WorkspaceInputContainer,
} from 'app/pages/Sandbox/Editor/Workspace/elements';
import { Error } from './elements';

interface ICreateRepoProps {
  style?: React.CSSProperties;
}

export const CreateRepo: React.FC<ICreateRepoProps> = ({ style }) => {
  const {
    state: {
      editor: { isAllModulesSynced },
      git: { repoTitle, error },
    },
    actions: {
      git: { repoTitleChanged, createRepoClicked },
    },
  } = useOvermind();

  const updateRepoTitle = ({
    target: { value: title },
  }: React.ChangeEvent<HTMLInputElement>) => repoTitleChanged({ title });

  const createRepo = () => {
    track('Export to GitHub Clicked');
    createRepoClicked();
  };

  return (
    <div style={style}>
      {!isAllModulesSynced && (
        <Error>Save your files first before exporting.</Error>
      )}

      {error && <Error>{error}</Error>}

      <WorkspaceSubtitle>Repository Name</WorkspaceSubtitle>

      <WorkspaceInputContainer>
        <Input onChange={updateRepoTitle} value={repoTitle} />
      </WorkspaceInputContainer>

      <Margin horizontal={1} bottom={1}>
        <Button
          block
          disabled={Boolean(error || !repoTitle || !isAllModulesSynced)}
          onClick={createRepo}
          small
        >
          Create Repository
        </Button>
      </Margin>
    </div>
  );
};

Please take note how we were previously using the inject and hooksObserver higher order components to pass store and signals as props to the component. Those get replaced with a new useOvermind hook that returns an object containing the keys state and actions which are synonymous with store and signals. In most cases these simply need to be swapped out as in the example above (which also includes some extra tidying of types and code style changes).

One of the greatest benefits of this refactor is that Overmind now provides us full Typescript support for our global state management. However that does mean that in some cases there may be type errors that also need to be resolved. To go about resolving these, you may need to do one or more of a few things:

  • Update the type definitions for the state itself (found under app/overmind/namespaces)
  • Fix outdated or missing prop types for related components
  • Or something more tricky is going on that we may need to help you with

What I've found is that this guide is a great resource for learning how to write types for React applications. I first check that before hitting up Google and StackOverflow for answers to type errors I encounter. Typescript can be a little difficult, but for the most part this refactor doesn't involve any of the really advanced Typescript wizardry.

So to summarize:

  • You may need to refactor the component to a functional component (choose a different component if this seems to daunting!)
  • Follow our coding style guide (explained below) to implement best practices
  • The inject and observer/hooksObserver are to be removed, also remove the import at the top of the file
  • Introduce the useOvermind hook from app/overmind import:
import { useOvermind } from 'app/overmind'
  • Destructure the state and/or actions from the hook:
const SomeComponent: React.FunctionComponent = ({ store: { someState }, signals: { someTrigger } }) => {
  const { state, actions } = useOvermind()
}
  • Use the existing store and/or signals and convert that into state and/or actions
const SomeComponent: React.FunctionComponent = () => {
  const { state: { someState }, actions: { someTrigger } } = useOvermind()
}
  • Run yarn typecheck and yarn lint, fix any errors that crop up (warnings can be ignored)
  • If you know where the component is used in the app, try manually testing it to check that it still works
  • Submit your PR for review
  • Implement any requested changes

Components to be refactored:

Best Practices and Style Guide

We've been working on a set of coding guidelines that we're applying across the project to increase readability and consistency. While not hard rules, we'd ask that you stick to these a close as possible. Since these are mostly stylistic choices, they're unlikely to cause anything to break if not followed, but we may ask you to fix a few if we feel it's important.

  • Always use Named Exports (here's some good reasons why this is a good idea: https://humanwhocodes.com/blog/2019/01/stop-using-default-exports-javascript-module/)
  • Write functional components with hooks (makes it much easier to detect unused methods, among numerous other good reasons)
  • Prefer arrow functions (death to this)
  • Prefer destructuring to property accessors
  • Components should always be their own file with a Capitalized name matching the component name
  • Multiple file components should be in a Capitalized folder matching the component name, with an index.ts file handling all exports to be consumed by outside components
  • Prefer interface over type for typing Props
  • In general, prop interfaces should follow a IComponentNameProps naming scheme, though this is largely stylistic
  • Prefer const SomeComponent: React.FC<ISomeComponentProps> = ({ . . . }) => { } over const SomeComponent = ({ . . . }: ISomeComponentProps) => { } to avoid needing to define common React component props
  • For styled components, Inline types are fine where their footprint is small, ie:
const SomeElement = styled.div<{ large: boolean }>` 
  ${({ large }) => css`
    /* 
    Always wrap style declarations in a single function call and don't forget to
    use "css" for syntax highlighting!
    */
  `}
`
  • All type interface definitions should live in a types.ts file adjacent to the component to reduce clutter in the component definition
  • If a component uses GraphQL queries/mutations, all GraphQL documents should live in their files adjacent to the component that uses them, ie: queries.gql and mutations.gql to reduce clutter in the component definition
  • All styles should be applied with styled-components and all styled elements should be defined in elements.js to reduce clutter in the component definition
  • Global styles should be defined using styled-components in a GlobalStyles.ts file adjacent to the root component that requires it
  • When using props in styled components, always wrap all of the styles in a single arrow function to make it easy to destructure props all in one place and always wrap nested template literals with the css tagged template to enable style linting/syntax highlighting.
  • Style rules should be well ordered (to be enforced by stylelint at a later date). In general this means rules should be grouped by category, starting with Position > Box Model > Fonts > Animation > Misc > pseudo-selectors. For now this is low-priority.

In general, our component file structure looks like this:

./ ParentComponent
  ./ ChildComponent
    - index.ts
    - ChildComponent.tsx
    - elements.ts
  - SomeOtherChildComponent.tsx
  - index.ts
  - ParentComponent.tsx
  - elements.ts
  - types.ts
  - queries.gql
  - mutations.gql

Simple components (those without any styles, few types, and no grapql) live in single named files like SomeOtherChildComponent.tsx, while others live in their own directories with folder names that match the main component's name, using index.ts to define all exports to be exposed to all outside consumers up the tree and files such as elements.ts to handle concerns like component styling, shared type definitions, graphql queries/mutations, etc.

Many of our existing components just live in their own directory and are defined in the index.js file there. If you encounter one of these, it's best to split it up according the the rules defined in the style guide. If it's a simple component, you can just rename the file to the component's name and move it to the parent directory, getting rid of the folder it's in entirely.

If you're confused by any of these, just try your best and we'll point out any changes we'd like you to make when we review your PR! Don't be afraid to as us questions too!

Other Quick Wins

So while Overmind is the main focus of our current refactor, we're also looking to convert more of the code base over to Typescript and React Hooks. If you'd like to try and refactor any of our JavaScript files or convert a class component, you're more than welcome to do so! We don't have a convenient list of files that need to be refactored for those, but it shouldn't be too hard to find.

In general we could always use contributions like bug fixes and better documentation, so if you spot an issue you'd like to try and fix or something in our docs has a typo or isn't clear enough, please feel free to submit a PR for those too!

Thanks and Happy Hacking!

Lastly, we'd like to extend our sincerest thanks to our community for all the feedback and support you give us! The entire CodeSandbox team is driven by your enthusiasm and we're always on the lookout for new ways to collaborate together and build new features to address all the awesome use-cases you come up with for our app. Thanks so much for your time and effort!

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions