Repository: streamich/react-use (ts)

Issue:
useOrientation is not SSR safe
### What is the current behaviour?
When using `useOrientation` in an SSR app it causes the error `window is undefined` to be thrown.

### Steps to reproduce it and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have extra dependencies other than react-use. Paste the link to your JSFiddle or CodeSandbox example below:
Use `useOrientation` in an SSR project

### What is the expected behaviour?
If there isn't window object use some default values.

Pull request description:
Fix useOrientation on SSR
# Description
When using useOrientation in an SSR app it causes the error window is undefined to be thrown.

I took over from #1569
Fixes #1570

also noticed that `useOrientation` is listed here #947, but no `eslint-disable` in the file.
<!-- Please include a summary of the change along with relevant motivation and context. -->


## Type of change

<!-- Check all relevant options. -->
- [x] Bug fix _(non-breaking change which fixes an issue)_
- [ ] New feature _(non-breaking change which adds functionality)_
- [ ] **Breaking change** _(fix or feature that would cause existing functionality to not work as before)_

# Checklist
- [ ] Read the [Contributing Guide](https://github.com/streamich/react-use/blob/master/CONTRIBUTING.md)
- [ ] Perform a code self-review
- [ ] Comment the code, particularly in hard-to-understand areas
- [ ] Add documentation
- [ ] Add hook's story at Storybook
- [ ] Cover changes with tests
- [ ] Ensure the test suite passes (`yarn test`)
- [ ] Provide 100% tests coverage
- [ ] Make sure code lints (`yarn lint`). Fix it with `yarn lint:fix` in case of failure.
- [ ] Make sure types are fine (`yarn lint:types`).

<!-- If you can't check all the checkboxes right now - check what you can, create a Draft PR, make some changes if needed and get back to it when you will be able to put some marks in list. -->


Patch:
diff --git a/src/useOrientation.ts b/src/useOrientation.ts
index cb315218..b5365af9 100644
--- a/src/useOrientation.ts
+++ b/src/useOrientation.ts
@@ -12,10 +12,10 @@ const defaultState: OrientationState = {
 };
 
 const useOrientation = (initialState: OrientationState = defaultState) => {
-  const screen = window.screen;
   const [state, setState] = useState(initialState);
 
   useEffect(() => {
+    const screen = window.screen;
     let mounted = true;
 
     const onChange = () => {
@@ -25,7 +25,7 @@ const useOrientation = (initialState: OrientationState = defaultState) => {
         if (orientation) {
           const { angle, type } = orientation;
           setState({ angle, type });
-        } else if (window.orientation) {
+        } else if (window.orientation !== undefined) {
           setState({
             angle: typeof window.orientation === 'number' ? window.orientation : 0,
             type: '',


Test patch:
diff --git a/tests/useOrientation.test.ts b/tests/useOrientation.test.ts
new file mode 100644
index 00000000..58a8392f
--- /dev/null
+++ b/tests/useOrientation.test.ts
@@ -0,0 +1,87 @@
+import { act, renderHook } from "@testing-library/react-hooks";
+import { replaceRaf } from "raf-stub";
+import useOrientation from "../src/useOrientation";
+
+declare var requestAnimationFrame: {
+  reset: () => void;
+  step: (steps?: number, duration?: number) => void;
+};
+
+describe('useOrientation', () => {
+  beforeAll(() => {
+    replaceRaf();
+  });
+
+  beforeEach(() => {
+    (window.screen.orientation as object) = {
+      type: 'landscape-primary',
+      angle: 0,
+    };
+    (window.orientation as number) = 0;
+  })
+
+  afterEach(() => {
+    requestAnimationFrame.reset();
+  });
+
+  it('should be defined', () => {
+    expect(useOrientation).toBeDefined();
+  });
+
+  function getHook(...args) {
+    return renderHook(() => useOrientation(...args));
+  }
+
+  function triggerOrientation(type: string, angle: number) {
+    (window.screen.orientation.type as string) = type;
+    (window.screen.orientation.angle as number) = angle;
+    
+    window.dispatchEvent(new Event('orientationchange'));
+  }
+
+  it('should return current window orientation', () => {
+    const hook = getHook();
+
+    expect(typeof hook.result.current).toBe('object');
+    expect(typeof hook.result.current.type).toBe('string');
+    expect(typeof hook.result.current.angle).toBe('number');
+  });
+
+  it('should use initial values in case of no parameters', () => {
+    const hook = getHook();
+
+    expect(hook.result.current.type).toBe('landscape-primary');
+    expect(hook.result.current.angle).toBe(0);
+  });
+
+  it("should re-render after orientation change on closest RAF", () => {
+    const hook = getHook();
+
+    act(() => {
+      triggerOrientation('portrait-secondary', 180);
+      requestAnimationFrame.step();
+    });
+
+    expect(hook.result.current.type).toBe('portrait-secondary');
+    expect(hook.result.current.angle).toBe(180);
+  });
+
+  it('should return window.orientation number if window.screen.orientation is missing', () => {
+    (window.screen.orientation as unknown) = undefined;
+
+    const hook = getHook();
+    
+    expect(hook.result.current.type).toBe('');
+    expect(hook.result.current.angle).toBe(0);
+  });
+
+  it('should return 0 if window.orientation is not a number and if window.screen.orientation is missing', () => {
+    (window.screen.orientation as unknown) = undefined;
+    (window.orientation as unknown) = null;
+    
+    const hook = getHook();
+    
+    expect(hook.result.current.type).toBe('');
+    expect(hook.result.current.angle).toBe(0);
+  })
+});