Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: refactor ParallaxProvider to function component #230

Merged
merged 1 commit into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
});
});
Loading