Skip to content

Commit

Permalink
chore: refactor ParallaxProvider to function component (#230)
Browse files Browse the repository at this point in the history
  • Loading branch information
jscottsmith authored Nov 2, 2023
1 parent a9ebdd5 commit 290dab0
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 95 deletions.
81 changes: 37 additions & 44 deletions src/components/ParallaxProvider/ParallaxProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,49 @@
import React, { Component } from 'react';
import React, { PropsWithChildren, useEffect, useRef } from 'react';

import { ParallaxContext } from '../../context/ParallaxContext';
import { ParallaxController, ScrollAxis } from 'parallax-controller';
import { ScrollAxis } from 'parallax-controller';
import { ParallaxProviderProps } from './types';
import { createController } from './helpers';

export class ParallaxProvider extends Component<ParallaxProviderProps, {}> {
static defaultProps = {
scrollAxis: ScrollAxis.vertical,
};

controller: ParallaxController | null;

constructor(props: ParallaxProviderProps) {
super(props);
this.controller = createController({
scrollAxis: props.scrollAxis,
export function ParallaxProvider(
props: PropsWithChildren<ParallaxProviderProps>
) {
const controller = useRef(
createController({
scrollAxis: props.scrollAxis || ScrollAxis.vertical,
scrollContainer: props.scrollContainer,
disabled: props.isDisabled,
});
}
})
);

componentDidUpdate(prevProps: ParallaxProviderProps) {
if (
prevProps.scrollContainer !== this.props.scrollContainer &&
this.props.scrollContainer
) {
this.controller?.updateScrollContainer(this.props.scrollContainer);
// update scroll container
useEffect(() => {
if (props.scrollContainer && controller.current) {
controller.current.updateScrollContainer(props.scrollContainer);
}
}, [props.scrollContainer, controller.current]);

if (prevProps.isDisabled !== this.props.isDisabled) {
if (this.props.isDisabled) {
this.controller?.disableParallaxController();
}
if (!this.props.isDisabled) {
this.controller?.enableParallaxController();
}
// disable/enable parallax
useEffect(() => {
if (props.isDisabled && controller.current) {
controller.current.disableParallaxController();
}
}

componentWillUnmount() {
// @ts-ignore
this.controller = this.controller.destroy();
}

render() {
const { children } = this.props;
return (
// @ts-ignore
<ParallaxContext.Provider value={this.controller}>
{children}
</ParallaxContext.Provider>
);
}
if (!props.isDisabled && controller.current) {
controller.current.enableParallaxController();
}
}, [props.isDisabled, controller.current]);

// remove the controller when unmounting
useEffect(() => {
return () => {
controller?.current && controller?.current.destroy();
controller.current = null;
};
}, []);

return (
<ParallaxContext.Provider value={controller.current}>
{props.children}
</ParallaxContext.Provider>
);
}
98 changes: 47 additions & 51 deletions src/components/ParallaxProvider/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,59 +84,53 @@ describe('A <ParallaxProvider>', () => {
});

it('to destroy the controller when unmounting', () => {
const node = document.createElement('div');
let parallaxController: ParallaxController | null = null;
const AddDestroySpy = () => {
parallaxController = useParallaxController();
if (parallaxController) {
jest.spyOn(parallaxController, 'destroy');
}
return null;
};

let instance;
ReactDOM.render(
<ParallaxProvider ref={(ref) => (instance = ref)}>
<div />
</ParallaxProvider>,
node
const screen = render(
<ParallaxProvider>
<AddDestroySpy />
</ParallaxProvider>
);

// @ts-ignore
instance.controller.destroy = jest.fn();
// @ts-ignore
const spy = instance.controller.destroy;

ReactDOM.unmountComponentAtNode(node);

expect(spy).toBeCalled();
screen.unmount();
// @ts-expect-error
expect(parallaxController?.destroy).toBeCalled();
});

it('to update the scroll container when receiving a new container el', () => {
const node = document.createElement('div');
let instance;
let providerInstance;

class StateChanger extends React.Component {
state = { el: undefined };
render() {
return (
<ParallaxProvider
scrollContainer={this.state.el}
ref={(ref) => (providerInstance = ref)}
>
<div />
</ParallaxProvider>
);
}
}
let parallaxController: ParallaxController | null = null;

ReactDOM.render(<StateChanger ref={(ref) => (instance = ref)} />, node);
const AddUpdateSpy = () => {
parallaxController = useParallaxController();
if (parallaxController) {
jest.spyOn(parallaxController, 'updateScrollContainer');
}
return null;
};

const el = document.createElement('div');
const screen = render(
<ParallaxProvider>
<AddUpdateSpy />
</ParallaxProvider>
);

// @ts-ignore
providerInstance.controller.updateScrollContainer = jest.fn();
// @ts-ignore
const spy = providerInstance.controller.updateScrollContainer;
// @ts-ignore
instance.setState({ el });

ReactDOM.unmountComponentAtNode(node);
screen.rerender(
<ParallaxProvider scrollContainer={el}>
<AddUpdateSpy />
</ParallaxProvider>
);

expect(spy).toBeCalledWith(el);
screen.unmount();
// @ts-expect-error
expect(parallaxController?.updateScrollContainer).toBeCalledWith(el);
});

// NOTE: I think this test can be removed
Expand All @@ -150,10 +144,15 @@ describe('A <ParallaxProvider>', () => {
const node2 = document.createElement('div');

const render = (node: HTMLDivElement) => {
let instance;
let instance: ParallaxController | null = null;
const GetInstance = () => {
instance = useParallaxController();
return null;
};
ReactDOM.render(
<ParallaxProvider ref={(ref) => (instance = ref)}>
<div />
// @ts-ignore
<ParallaxProvider>
<GetInstance />
</ParallaxProvider>,
node
);
Expand All @@ -162,19 +161,16 @@ describe('A <ParallaxProvider>', () => {

// first instance mounted
const instance1 = render(node1);
// @ts-ignore
expect(instance1.controller).toBeInstanceOf(ParallaxController);
expect(instance1).toBeInstanceOf(ParallaxController);

// second instance mounted
const instance2 = render(node2);
// @ts-ignore
expect(instance2.controller).toBeInstanceOf(ParallaxController);
expect(instance2).toBeInstanceOf(ParallaxController);

// unmount first instance
ReactDOM.unmountComponentAtNode(node1);

// this must still be defined
// @ts-ignore
expect(instance2.controller).toBeInstanceOf(ParallaxController);
expect(instance2).toBeInstanceOf(ParallaxController);
});
});

1 comment on commit 290dab0

@vercel
Copy link

@vercel vercel bot commented on 290dab0 Nov 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.