Espen Henriksen

Front-end team lead

Oslo Market Solutions

 

espen_dev

esphen

Best practices in React and friends

Let's learn from each other!

Let's go

Why are we here?

  • We're all in this together
  • We make each other better
  • We're a tiny company, everyone is an essential part
  • We need to learn from each other
  • It's everyone's responsibility to spread their knowledge

Agenda

  • Show some anonymized code examples
  • Don't fret if you see code you wrote
  • Check your ego at the door
  • Be pragmatic
  • Think of it as a great opportunity to learn

Scrollbars

Click this

Click surfaces

Temporary variables

function FooComponent({ loggedIn }) {
  let sidebarContent;
  
  if (loggedIn) {
    sidebarContent = (
      <div>
        I'm sorry Dave, I'm afraid I can't do that
      </div>
    );
  }

  if (!loggedIn) {
    sidebarContent = (
      <div>
        Welcome
      </div>
    );
  }

  return (
    <aside>
      {sidebarContent}
    </aside>
  );
}

Solution: Inline conditionals

const FooComponent = ({ loggedIn }) => (
  <aside>
    {loggedIn && (
      <div>
        I'm sorry Dave, I'm afraid I can't do that
      </div>
    )}

    {!loggedIn && (
      <div>
        Welcome
      </div>
    )}
  </aside>
);

Solution: Functions

const FooComponent = ({ loggedIn }) => (
  <aside>
    {loggedIn && renderLoggedInView()}
    {!loggedIn && <LoggedOutMessage />}
  </aside>
);

Static jsx in dynamic syntax

const FooComponent = () => (
  <Link to={'/trader/news'}>
    To the place
  </Link>
);

Solution: Don't

const FooComponent = () => (
  <Link to="/trader/news">
    To the place
  </Link>
);

String concated children

const FooComponent = ({ values: { SECTOR, LONG_NAME }}) => (
  <h2>
    {`${SECTOR}: ${LONG_NAME}`}
  </h2>
);

Solution: Jsx syntax

const FooComponent = ({ values: { SECTOR, LONG_NAME }}) => (
  <h2>
    {SECTOR}: {LONG_NAME}
  </h2>
);

Unclear intentions

const FooComponent = ({ small, location }) => {
  const { page } = qs.parse(location?.search, {
    ignoreQueryPrefix: true,
  });

  return (
    <Switch location={small && page ? { pathname: `/${page}` } : location}>
      ...
    </Switch>
  );
};

Solution: Named constants

const FooComponent = ({ small, location }) => {
  const urlQuery = qs.parse(location?.search, {
    ignoreQueryPrefix: true,
  });
  const isOnMobile = small && urlQuery.page;
  const currentLocation = isOnMobile 
    ? { pathname: `/${urlQuery.page}` } 
    : location;

  return (
    <Switch location={currentLocation}>
      ...
    </Switch>
  );
};

Solution: Component

const ResponsiveSwitch = ({ small, location, children }) => {
  const urlQuery = qs.parse(location?.search, {
    ignoreQueryPrefix: true,
  });
  const isOnMobile = small && urlQuery.page;
  const currentLocation = isOnMobile ? { pathname: `/${page}` } : location;

  return (
    <Switch location={currentLocation}>
      {children}
    </Switch>
  );
};

const FooComponent = () => (
  <ResponsiveSwitch>
    ...
  </ResponsiveSwitch>
);

Context consumer

const FooComponent = () => (
  <FooContext>
    {({ fooValue }) => (
      <p>
        {fooValue}
      </p>
    )}
  </FooContext>
);

Solution: useContext hook

const FooComponent = () => {
  const { fooValue } = useContext(FooContext);

  return (
    <p>
      {fooValue}
    </p>
  );
);

Solution: Custom hook

export const useFoo = () => {
  return useContext(FooContext);
};

const FooComponent = () => {
  const { fooValue } = useFoo();

  return (
    <p>
      {fooValue}
    </p>
  );
);

Misleading variables

const FooComponent = () => {
  const isLoggedIn = 'Espen';

  return (
    <p>
      {isLoggedIn}
    </p>
  );
};

Misleading variables

const FooComponent = ({ isLoggedIn }) => {
  if (!isLoggedIn) return <LoggedOutMessage />;

  return (
    <p>
      Welcome!
    </p>
  );
};

Solution: Intuitive naming

const FooComponent = ({ loggedInUser }) => (
  <p>
    Welcome {loggedInUser.name}!
  </p>
);

Chaining

const array = [...];
const filteredArray = array.filter(...);
const sortedArray = filteredArray.sort(...);
const mappedArray = sortedArray.map(...);
const flattenedArray = mappedArray.flat(2);

Chaining

const array = [...];
const users = array
  .filter(...)
  .map(...)
  .flat(2);
const sortedUsers = users.sort(...);

Chaining

const FooComponent = ({ users }) => (
  <ul>
    {users
      .filter(...)
      .map(...)
      .flat(2)
      .sort(...)
      .map(...)}
  </ul>
);

Chaining

const filterUsers = users => (
  users
    .filter(...)
    .map(...)
    .flat(2)
    .sort(...)
);

const FooComponent = ({ users }) => (
  <ul>
    {filterUsers(users).map(...)}
  </ul>
);

Similar components

const FooComponent = ({ type }) => (
  <div>
    {type === 'user' && <UserTable />}
    {type === 'admin' && <AdminTable />}
  </div>
);

Solution: generic component

const FooComponent = ({ type }) => (
  <>
    <Table type="user" />
    <Table type="admin" />
  </>
);

const Table = ({ type }) => (
  <table>
    <thead>
      <tr>
        <th>{type === 'user' ? 'Brukernavn' : 'Admin-navn'}</th>
        ...
      </tr>
    </thead>
    ...
  </table>
);

Similar components

import request from 'superagent';

request
  .get('https://vg.no')
  .then(function(response) {
    console.log(response.body);
  });

Solution: Async fetch

async function() {
  const response = await fetch('https://vg.no');
  console.log(await response.json());
}

Commit

$ git commit -am "ABC-123 ABC-124 ABC-125 Fix bugs"

Solution: Separate commits

$ git add file1
$ git commit -m "ABC-123 Solves button responsiveness"
$ git add file2
$ git commit -m "ABC-124 Fixes link target"
$ git add file3
$ git commit -m "ABC-125 Upgrades moment version to 3.2"

Tip: Detailed commits

$ git add file1
$ git commit -m "ABC-123 Fixes performance issues on portfolio page

This happened because iPhones expose a special API called
Steve that makes it overheat when exposed to bad design.

Fixed by using the newer Tim API which doesn't seem to care"

Commiting commented code

<label
  htmlFor="file"
  className={`file ${this.checkValidationErrors('date')}`}
>
  Fil:
  <input
    className={styles.file}
    type="file"
    name="file"
    accept=".pdf"
    placeholder="Klikk for å velge fil"
    onChange={this.handleFileChange}
  />
  {/* <FileInput name="file"
      accept=".pdf"
      placeholder="Klikk for å velge fil"
      onChange={ this.handleFileChange }
  /> */}
</label>

Solution: Delete it

It's in the git history

In summary

In summary

  1. Writing maintainable (readable) code Α+Ω
  2. Take your time - ensure the code you wrote is bug-free
  3. When you run across unmaintainable or buggy code, fix it
  4. When you run across code you don't understand, challenge yourself to understand it - you may learn something

Takeaways

  • Read other's code and try to learn from them
  • Be critical of your own code, "kill your darlings"
  • When in doubt, ask colleagues if they know any better solutions
  • Merge requests
  • Never stop being curious
  • Spread your knowledge
  • But don't confront anyone
  • Finally: What do you think about this format?

Fin

Let's talk

https://slides.com/esphen/react-patterns

Best practices in React and friends

By Eline H

Best practices in React and friends

  • 32