diff --git a/README.md b/README.md
index 44a56cf..9172664 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ A collection of commonly used hooks for React apps.
 **Packages:**
 
 - [`use-promised`](./packages/use-promised)
-- [`use-optionally-controlled-state`](./packages/use-optionally-controlled-state)
+- [`use-optional-state`](./packages/use-optional-state)
 - [`use-last`](./packages/use-last)
 - [`use-debounced`](./packages/use-debounced)
 - [`use-presence`](./packages/use-presence)
diff --git a/packages/use-debounced/package.json b/packages/use-debounced/package.json
index 8981eff..640a0c5 100644
--- a/packages/use-debounced/package.json
+++ b/packages/use-debounced/package.json
@@ -38,6 +38,6 @@
     "react": "^18.0.0",
     "react-dom": "^18.0.0",
     "tsdx": "^0.14.1",
-    "typescript": "^4.6.3"
+    "typescript": "^4.8.2"
   }
 }
diff --git a/packages/use-last/package.json b/packages/use-last/package.json
index 14cad53..e02db98 100644
--- a/packages/use-last/package.json
+++ b/packages/use-last/package.json
@@ -38,6 +38,6 @@
     "react": "^18.0.0",
     "react-dom": "^18.0.0",
     "tsdx": "^0.14.1",
-    "typescript": "^4.6.3"
+    "typescript": "^4.8.2"
   }
 }
diff --git a/packages/use-optionally-controlled-state/.eslintrc.js b/packages/use-optional-state/.eslintrc.js
similarity index 100%
rename from packages/use-optionally-controlled-state/.eslintrc.js
rename to packages/use-optional-state/.eslintrc.js
diff --git a/packages/use-optionally-controlled-state/README.md b/packages/use-optional-state/README.md
similarity index 82%
rename from packages/use-optionally-controlled-state/README.md
rename to packages/use-optional-state/README.md
index 81e5690..75ce35b 100644
--- a/packages/use-optionally-controlled-state/README.md
+++ b/packages/use-optional-state/README.md
@@ -1,6 +1,6 @@
-# use-optionally-controlled-state
+# use-optional-state
 
-[![Stable release](https://img.shields.io/npm/v/use-optionally-controlled-state.svg)](https://npm.im/use-optionally-controlled-state)
+[![Stable release](https://img.shields.io/npm/v/use-optional-state.svg)](https://npm.im/use-optional-state)
 
 A React hook to enable a component state to either be controlled or uncontrolled.
 
@@ -18,21 +18,21 @@ When implementing a component, it's sometimes hard to choose one or the other si
 
 This hook helps you to support both patterns in your components, increasing flexibility while also ensuring ease of use.
 
-Since the solution can be applied on a per-prop basis, you can even enable this behaviour for multiple props that are orthogonal (e.g. a `<Prompt isOpen inputValue="" />` component).
+Since the solution can be applied on a per-prop basis, you can also enable this behaviour for multiple props that are orthogonal (e.g. a `<Prompt isOpen inputValue="" />` component).
 
 ## Example
 
 **Implementation:**
 
 ```jsx
-import useOptionallyControlledState from 'use-optionally-controlled-state';
+import useOptionalState from 'use-optional-state';
 
 function Expander({
   expanded: controlledExpanded,
   initialExpanded = false,
   onChange
 }) {
-  const [expanded, setExpanded] = useOptionallyControlledState({
+  const [expanded, setExpanded] = useOptionalState({
     controlledValue: controlledExpanded,
     initialValue: initialExpanded,
     onChange
diff --git a/packages/use-optionally-controlled-state/package.json b/packages/use-optional-state/package.json
similarity index 87%
rename from packages/use-optionally-controlled-state/package.json
rename to packages/use-optional-state/package.json
index 08f8e4c..af4d377 100644
--- a/packages/use-optionally-controlled-state/package.json
+++ b/packages/use-optional-state/package.json
@@ -1,12 +1,12 @@
 {
-  "name": "use-optionally-controlled-state",
+  "name": "use-optional-state",
   "version": "1.2.0",
   "license": "MIT",
   "description": "A React hook to implement components that support both controlled and uncontrolled props.",
   "author": "Jan Amann <jan@amann.me>",
   "repository": {
     "type": "git",
-    "url": "https://github.com/amannn/react-hooks/tree/master/packages/use-optionally-controlled-state"
+    "url": "https://github.com/amannn/react-hooks/tree/master/packages/use-optional-state"
   },
   "scripts": {
     "start": "tsdx watch --tsconfig tsconfig.json --verbose --noClean",
@@ -16,7 +16,7 @@
     "prepublish": "npm run build"
   },
   "main": "dist/index.js",
-  "module": "dist/use-optionally-controlled-state.esm.js",
+  "module": "dist/use-optional-state.esm.js",
   "typings": "dist/index.d.ts",
   "files": [
     "README.md",
@@ -42,6 +42,6 @@
     "react": "^18.0.0",
     "react-dom": "^18.0.0",
     "tsdx": "^0.14.1",
-    "typescript": "^4.6.3"
+    "typescript": "^4.8.2"
   }
 }
diff --git a/packages/use-optionally-controlled-state/src/index.tsx b/packages/use-optional-state/src/index.tsx
similarity index 55%
rename from packages/use-optionally-controlled-state/src/index.tsx
rename to packages/use-optional-state/src/index.tsx
index 868b424..2bd2112 100644
--- a/packages/use-optionally-controlled-state/src/index.tsx
+++ b/packages/use-optional-state/src/index.tsx
@@ -1,37 +1,44 @@
 import {useState, useCallback} from 'react';
 import useConstant from 'use-constant';
 
-type Options<Value> =
-  | {
-      controlledValue?: Value;
-      initialValue: Value;
-      onChange?(value: Value): void;
-    }
-  | {
-      controlledValue: Value;
-      initialValue?: Value;
-      onChange?(value: Value): void;
-    };
+// Controlled
+export default function useOptionalState<Value>(opts: {
+  controlledValue: Value;
+  initialValue?: Value | undefined;
+  onChange?(value: Value): void;
+}): [Value, (value: Value) => void];
+
+// Uncontrolled with initial value
+export default function useOptionalState<Value>(opts: {
+  controlledValue?: Value | undefined;
+  initialValue: Value;
+  onChange?(value: Value): void;
+}): [Value | undefined, (value: Value) => void];
+
+// Uncontrolled without initial value
+export default function useOptionalState<Value>(opts: {
+  controlledValue?: Value | undefined;
+  initialValue?: Value;
+  onChange?(value: Value): void;
+}): [Value | undefined, (value: Value) => void];
 
 /**
  * Enables a component state to be either controlled or uncontrolled.
  */
-export default function useOptionallyControlledState<Value>({
+export default function useOptionalState<Value>({
   controlledValue,
   initialValue,
   onChange
-}: Options<Value>): [Value, (value: Value) => void] {
+}: {
+  controlledValue?: Value | undefined;
+  initialValue?: Value | undefined;
+  onChange?(value: Value): void;
+}) {
   const isControlled = controlledValue !== undefined;
   const initialIsControlled = useConstant(() => isControlled);
   const [stateValue, setStateValue] = useState(initialValue);
 
   if (__DEV__) {
-    if (initialValue === undefined && controlledValue === undefined) {
-      throw new Error(
-        'Either an initial or a controlled value should be provided.'
-      );
-    }
-
     if (initialIsControlled && !isControlled) {
       throw new Error(
         'Can not change from controlled to uncontrolled mode. If `undefined` needs to be used for controlled values, please use `null` instead.'
@@ -45,15 +52,14 @@ export default function useOptionallyControlledState<Value>({
     }
   }
 
-  // Options type ensures that either `controlledValue` or `stateValue` is defined
-  const value = (isControlled ? controlledValue : stateValue)!;
+  const value = isControlled ? controlledValue : stateValue;
 
   const onValueChange = useCallback(
     (nextValue: Value) => {
       if (!isControlled) setStateValue(nextValue);
       if (onChange) onChange(nextValue);
     },
-    [onChange, isControlled]
+    [isControlled, onChange]
   );
 
   return [value, onValueChange];
diff --git a/packages/use-optionally-controlled-state/test/index.test.tsx b/packages/use-optional-state/test/index.test.tsx
similarity index 51%
rename from packages/use-optionally-controlled-state/test/index.test.tsx
rename to packages/use-optional-state/test/index.test.tsx
index ef0d90d..67b5553 100644
--- a/packages/use-optionally-controlled-state/test/index.test.tsx
+++ b/packages/use-optional-state/test/index.test.tsx
@@ -1,6 +1,6 @@
 import {fireEvent, render, screen} from '@testing-library/react';
 import * as React from 'react';
-import useOptionallyControlledState from '../src';
+import useOptionalState from '../src';
 
 (global as any).__DEV__ = true;
 
@@ -15,7 +15,7 @@ function Expander({
   initialExpanded,
   onChange
 }: Props) {
-  const [expanded, setExpanded] = useOptionallyControlledState({
+  const [expanded, setExpanded] = useOptionalState({
     controlledValue: controlledExpanded,
     initialValue: initialExpanded,
     onChange
@@ -66,6 +66,20 @@ it('supports an uncontrolled mode', () => {
   screen.getByText('Children');
 });
 
+it('supports an uncontrolled mode with no initial value', () => {
+  const onChange = jest.fn();
+
+  render(<Expander onChange={onChange} />);
+  expect(screen.queryByText('Children')).toBe(null);
+  fireEvent.click(screen.getByText('Toggle'));
+  expect(onChange).toHaveBeenLastCalledWith(true);
+
+  screen.getByText('Children');
+  fireEvent.click(screen.getByText('Toggle'));
+  expect(onChange).toHaveBeenLastCalledWith(false);
+  expect(screen.queryByText('Children')).toBe(null);
+});
+
 it('allows to use an initial value without a change handler', () => {
   // Maybe the value is read from the DOM directly
   render(<Expander initialExpanded />);
@@ -81,12 +95,6 @@ it('uses the controlled value when both a controlled as well as an initial value
   screen.getByText('Children');
 });
 
-it('throws when neither a controlled nor an initial value is provided', () => {
-  expect(() => render(<Expander />)).toThrow(
-    'Either an initial or a controlled value should be provided.'
-  );
-});
-
 it('throws when switching from uncontrolled to controlled mode', () => {
   const {rerender} = render(<Expander initialExpanded />);
 
@@ -102,3 +110,73 @@ it('throws when switching from controlled to uncontrolled mode', () => {
     /Can not change from controlled to uncontrolled mode./
   );
 });
+
+/**
+ * Type signature tests
+ */
+
+function TestTypes() {
+  const controlled = useOptionalState({
+    controlledValue: true
+  });
+  controlled[0].valueOf();
+
+  const uncontrolledWithInitialValue = useOptionalState({
+    initialValue: true
+  });
+  // @ts-expect-error Null-check would be necessary
+  uncontrolledWithInitialValue[0].valueOf();
+
+  const uncontrolledWithoutInitialValue = useOptionalState<boolean>({});
+  // @ts-expect-error Null-check would be necessary
+  uncontrolledWithoutInitialValue[0].valueOf();
+
+  // Only used for type tests; mark the variables as used
+  // eslint-disable-next-line no-unused-expressions
+  [controlled, uncontrolledWithInitialValue, uncontrolledWithoutInitialValue];
+}
+
+// Expected return type: `[boolean, (value: boolean) => void]`
+function Controlled(opts: {controlledValue: boolean; initialValue?: boolean}) {
+  const [value, setValue] = useOptionalState(opts);
+
+  setValue(true);
+  return value.valueOf();
+}
+
+// Expected return type: `[boolean | undefined, (value: boolean) => void]`
+// Note that theoretically `undefined` shouldn't be possible here,
+// but the types seem to be quite hard to get right.
+function UncontrolledWithInitialValue(opts: {
+  controlledValue?: boolean;
+  initialValue: boolean;
+}) {
+  const [value, setValue] = useOptionalState(opts);
+
+  setValue(true);
+
+  // @ts-expect-error Null-check would be necessary
+  return value.valueOf();
+}
+
+// Expected return type: `[boolean | undefined, (value: boolean) => void]`
+function UncontrolledWithoutInitialValue(opts: {
+  controlledValue?: boolean;
+  initialValue?: boolean;
+}) {
+  const [value, setValue] = useOptionalState(opts);
+
+  setValue(true);
+
+  // @ts-expect-error Null-check would be necessary
+  return value.valueOf();
+}
+
+// Only used for type tests; mark the functions as used
+// eslint-disable-next-line no-unused-expressions
+[
+  TestTypes,
+  Controlled,
+  UncontrolledWithInitialValue,
+  UncontrolledWithoutInitialValue
+];
diff --git a/packages/use-optionally-controlled-state/tsconfig.json b/packages/use-optional-state/tsconfig.json
similarity index 100%
rename from packages/use-optionally-controlled-state/tsconfig.json
rename to packages/use-optional-state/tsconfig.json
diff --git a/packages/use-presence/package.json b/packages/use-presence/package.json
index df82a69..4292d85 100644
--- a/packages/use-presence/package.json
+++ b/packages/use-presence/package.json
@@ -38,6 +38,6 @@
     "react": "^18.0.0",
     "react-dom": "^18.0.0",
     "tsdx": "^0.14.1",
-    "typescript": "^4.6.3"
+    "typescript": "^4.8.2"
   }
 }
diff --git a/packages/use-promised/package.json b/packages/use-promised/package.json
index 0120dfb..ed0b59b 100644
--- a/packages/use-promised/package.json
+++ b/packages/use-promised/package.json
@@ -38,6 +38,6 @@
     "react": "^18.0.0",
     "react-dom": "^18.0.0",
     "tsdx": "^0.14.1",
-    "typescript": "^4.6.3"
+    "typescript": "^4.8.2"
   }
 }
diff --git a/yarn.lock b/yarn.lock
index 9573882..b241a69 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2122,6 +2122,11 @@
   dependencies:
     "@babel/types" "^7.3.0"
 
+"@types/eslint-visitor-keys@^1.0.0":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
+  integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==
+
 "@types/estree@*":
   version "0.0.45"
   resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884"
@@ -2300,7 +2305,17 @@
     eslint-scope "^5.0.0"
     eslint-utils "^2.0.0"
 
-"@typescript-eslint/parser@5.20.0", "@typescript-eslint/parser@^2.12.0", "@typescript-eslint/parser@^5.0.0":
+"@typescript-eslint/parser@^2.12.0":
+  version "2.34.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.34.0.tgz#50252630ca319685420e9a39ca05fe185a256bc8"
+  integrity sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA==
+  dependencies:
+    "@types/eslint-visitor-keys" "^1.0.0"
+    "@typescript-eslint/experimental-utils" "2.34.0"
+    "@typescript-eslint/typescript-estree" "2.34.0"
+    eslint-visitor-keys "^1.1.0"
+
+"@typescript-eslint/parser@^5.0.0":
   version "5.20.0"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.20.0.tgz#4991c4ee0344315c2afc2a62f156565f689c8d0b"
   integrity sha512-UWKibrCZQCYvobmu3/N8TWbEeo/EPQbS41Ux1F9XqPzGuV7pfg6n50ZrFo6hryynD8qOTTfLHtHjjdQtxJ0h/w==
@@ -9444,11 +9459,16 @@ typescript@^3.7.3:
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8"
   integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==
 
-typescript@^4.0.0, typescript@^4.6.3:
+typescript@^4.0.0:
   version "4.6.3"
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c"
   integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==
 
+typescript@^4.8.2:
+  version "4.8.2"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.2.tgz#e3b33d5ccfb5914e4eeab6699cf208adee3fd790"
+  integrity sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==
+
 uglify-js@^3.1.4:
   version "3.10.4"
   resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.10.4.tgz#dd680f5687bc0d7a93b14a3482d16db6eba2bfbb"