get() Upstream

get() Upstream demo built with Expressive MVC - the complete source below runs as an editable sandbox when JavaScript is enabled.

App.tsx

import './App.css';

import { Component, get } from '@expressive/react';
import type { ReactNode } from 'react';

export default () => (
  <div className="container">
    <h1>Upstream</h1>
    <p>
      <code>get(Form)</code> locates the nearest form by class, not by prop. A{' '}
      <code>Component</code> is in context for everything it renders, so the form
      provides itself just by being one - and every field below finds it, however
      deep it sits.
    </p>
    <Form />
    <p>
      With <code>false</code> the lookup is optional. This is the same Field class,
      rendered outside any form: it finds nothing, says so, and still works.
    </p>
    <Field label="Nickname" />
    <small>
      One class covers both placements because the answer is typed{' '}
      <code>Form | undefined</code> - the compiler makes you handle the standalone
      case, rather than a missing provider making you find out at runtime.
    </small>
  </div>
);

class Form extends Component {
  locked = false;

  render() {
    const { locked } = this;

    return (
      <form className="signup" onSubmit={(e) => e.preventDefault()}>
        <Field label="Name" />

        <Group title="Contact">
          <Field label="Email" />
        </Group>

        <footer>
          <button type="button" onClick={() => (this.locked = !locked)}>
            {locked ? 'Unlock' : 'Lock'} form
          </button>
        </footer>
      </form>
    );
  }
}

class Field extends Component {
  form = get(Form, false);
  label = '';
  value = '';

  render() {
    const { form, label, value } = this;

    return (
      <label className="field">
        <span>
          {label}
          <small>{form ? 'in a form' : 'no form above'}</small>
        </span>
        <input
          value={value}
          disabled={form?.locked}
          placeholder={form?.locked ? 'locked' : `Your ${label.toLowerCase()}`}
          onChange={(e) => (this.value = e.target.value)}
        />
      </label>
    );
  }
}

const Group = (props: { title: string; children?: ReactNode }) => (
  <fieldset className="group">
    <legend>{props.title}</legend>
    {props.children}
  </fieldset>
);

App.css

.signup {
  display: flex;
  flex-direction: column;
  gap: var(--s4);
  width: 100%;
  padding: var(--s4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  width: 100%;
  text-align: left;
}

.field span {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s2);
  font-size: var(--t-sm);
  font-weight: 500;
}

.field small {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
}

.field input:disabled {
  color: var(--muted);
  background: var(--surface-2);
  cursor: not-allowed;
}

.signup footer {
  display: flex;
  padding-top: var(--s3);
  border-top: 1px solid var(--border);
}

.signup footer button {
  flex: 1;
  font-size: var(--t-sm);
}

.group {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  margin: 0;
  padding: var(--s3);
  border: 1px dashed var(--border-2);
  border-radius: var(--r);
}

.group legend {
  padding: 0 var(--s2);
  font-size: var(--t-xs);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}