Skip to content

feat: Update PauseableReduxContainer to use redux-pauseable-store #12

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

Merged
merged 4 commits into from
Jun 30, 2020
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
1 change: 1 addition & 0 deletions packages/dev-helpers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"@material-ui/core": "^4.9.4",
"@testing-library/react": "^10.2.1",
"history": "^4.10.1",
"prop-types": "^15.7.2",
"react-is": "^16.13.0",
"react-redux": "^7.2.0",
"react-router": "^5.1.2",
Expand Down
28 changes: 28 additions & 0 deletions packages/dev-helpers/src/components/RenderCount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import PropTypes from 'prop-types';
import React, { useRef } from 'react';

import Typography from '@material-ui/core/Typography';

interface RenderCountProps {
prefix?: string;
}

const RenderCount: React.FC<RenderCountProps> = (props) => {
const { prefix = 'Render count: ' } = props;

const renderCountRef = useRef(0);
renderCountRef.current++;

return (
<Typography variant="body1">
{prefix}
{renderCountRef.current}
</Typography>
);
};

RenderCount.propTypes = {
prefix: PropTypes.string,
};

export default RenderCount;
3 changes: 3 additions & 0 deletions packages/dev-helpers/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ export * from './DemoContainer';

export { default as NestedState } from './NestedState';
export * from './NestedState';

export { default as RenderCount } from './RenderCount';
export * from './RenderCount';
3 changes: 3 additions & 0 deletions packages/dev-helpers/src/redux/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export { default as reduxDecorator } from './reduxDecorator';
export * from './reduxDecorator';

export { default as useCountSelector } from './useCountSelector';
export * from './useCountSelector';

export * from './store';
11 changes: 11 additions & 0 deletions packages/dev-helpers/src/redux/useCountSelector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useSelector } from 'react-redux';

import { DevHelperState } from './store';

const countSelector = (state: DevHelperState) => state.count;

const useCountSelector = (): number => {
return useSelector(countSelector);
};

export default useCountSelector;
3 changes: 1 addition & 2 deletions packages/react-hibernate/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
"react-router": ">=5.0.0"
"react-dom": ">=16.8.0"
}
}
8 changes: 5 additions & 3 deletions packages/react-pauseable-containers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@
"test:watch": "echo \"@TODO: tests for pauseable-containers\"",
"types": "tsc --noEmit --p tsconfig.json --jsx react"
},
"dependencies": {},
"dependencies": {
"prop-types": "^15.7.2",
"redux-pauseable-store": "0.0.3"
},
"devDependencies": {
"react-hibernate-dev-helpers": "0.0.2",
"react-router-hibernate": "0.0.2"
"react-hibernate-dev-helpers": "0.0.2"
},
"peerDependencies": {
"react": ">=16.8.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@ class PauseableComponentContainer extends React.Component<PauseableContainerProp
}

render(): ReactNode {
const child = this.props.children;
if (child === null || child === false) {
return null;
}
return React.Children.only(child);
return this.props.children;
}
}

Expand Down
71 changes: 35 additions & 36 deletions packages/react-pauseable-containers/src/PauseableReduxContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,43 @@
import React, { PropsWithChildren, ReactElement } from 'react';
import { Store } from 'redux';
import PropTypes from 'prop-types';
import React from 'react';
import { Provider, useStore } from 'react-redux';

import { createPauseableStore, PauseableStoreInstance } from 'redux-pauseable-store';

import { PauseableContainerProps } from './types';

const PauseableReduxContainer: React.FC<PauseableContainerProps> = ({
shouldUpdate,
children,
}: PropsWithChildren<PauseableContainerProps>): ReactElement | null => {
const store = useStore();
const staticStoreRef = React.useRef<Store>();
const wasActiveRef = React.useRef<boolean>();

const stateWhenLastActive = React.useRef<Store>();

if (shouldUpdate) {
// Track stuff for when we go inactive
stateWhenLastActive.current = store.getState();
} else {
if (wasActiveRef.current) {
// We're going inactive: freeze the store contents to the last-active state
staticStoreRef.current = {
...store,
getState: (): ReturnType<typeof store.getState> => stateWhenLastActive.current,
};
} else {
// We're somehow being rendered in an initially-inactive state: that can't be right
if (process.env.NODE_ENV !== 'production') {
console.warn(
'PauseableReduxContainer is being mounted with shouldUpdate=false: this is probably a bug',
);
}
return null;
}
}

wasActiveRef.current = shouldUpdate;
return (
<Provider store={shouldUpdate ? store : (staticStoreRef.current as Store)}>{children}</Provider>
export interface PauseableReduxContainerProps extends PauseableContainerProps {
children: React.ReactNode;
dispatchWhenPaused?: boolean | null;
}

const PauseableReduxContainer: React.FC<PauseableReduxContainerProps> = (props) => {
const { dispatchWhenPaused, shouldUpdate, children } = props;

const parentStore = useStore();
const pauseableStore = React.useMemo<PauseableStoreInstance>(
() =>
createPauseableStore(parentStore, {
// A change to the `shouldUpdate` prop will already cause a rerender, so we don't need an extra notification
notifyListersOnUnpause: false,
}),
[parentStore],
);

pauseableStore.setPaused(!shouldUpdate);
pauseableStore.setDispatch(dispatchWhenPaused);

return <Provider store={pauseableStore}>{children}</Provider>;
};

PauseableReduxContainer.defaultProps = {
dispatchWhenPaused: null,
};

PauseableReduxContainer.propTypes = {
children: PropTypes.node.isRequired,
dispatchWhenPaused: PropTypes.bool,
shouldUpdate: PropTypes.bool.isRequired,
};

export default PauseableReduxContainer;
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import PropTypes from 'prop-types';
import React, { useState } from 'react';

import Checkbox from '@material-ui/core/Checkbox';
import Chip from '@material-ui/core/Chip';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Paper from '@material-ui/core/Paper';
import Typography from '@material-ui/core/Typography';

import { RenderCount } from 'react-hibernate-dev-helpers';

import { PauseableComponentContainer } from '../src';

export interface PauseableComponentItemProps {
count: number;
}

const PauseableComponentItem: React.FC<PauseableComponentItemProps> = (props) => {
const { count } = props;

const [shouldUpdate, setShouldUpdate] = useState(true);

return (
<Paper style={{ marginTop: 10, padding: 5 }}>
<FormControlLabel
control={
<Checkbox
checked={shouldUpdate}
onChange={(event) => setShouldUpdate(event.target.checked)}
/>
}
label="shouldUpdate"
/>
<div>
<PauseableComponentContainer shouldUpdate={shouldUpdate}>
<Typography variant="body1" component="div">
count: <Chip label={count} />
</Typography>
<RenderCount />
</PauseableComponentContainer>
</div>
</Paper>
);
};

PauseableComponentItem.propTypes = {
count: PropTypes.number.isRequired,
};

export default PauseableComponentItem;
58 changes: 58 additions & 0 deletions packages/react-pauseable-containers/stories/PauseableReduxItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import PropTypes from 'prop-types';
import React, { useState } from 'react';

import Checkbox from '@material-ui/core/Checkbox';
import Chip from '@material-ui/core/Chip';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Typography from '@material-ui/core/Typography';
import Paper from '@material-ui/core/Paper';

import { RenderCount, useCountSelector } from 'react-hibernate-dev-helpers';

import { PauseableComponentContainer, PauseableReduxContainer } from '../src';

interface PauseableReduxItemProps {
dispatchWhenPaused?: boolean;
}

const PauseableReduxItem: React.FC<PauseableReduxItemProps> = (props) => {
const { dispatchWhenPaused } = props;
const count = useCountSelector();

const [shouldUpdate, setShouldUpdate] = useState(true);

return (
<Paper style={{ marginTop: 10, padding: 5 }}>
<FormControlLabel
control={
<Checkbox
checked={shouldUpdate}
onChange={(event) => setShouldUpdate(event.target.checked)}
/>
}
label="shouldUpdate"
/>
<PauseableComponentContainer shouldUpdate={shouldUpdate}>
<PauseableReduxContainer
shouldUpdate={shouldUpdate}
dispatchWhenPaused={dispatchWhenPaused}
>
<Typography variant="body1" component="div">
count: <Chip label={count} />
</Typography>
<RenderCount />
</PauseableReduxContainer>
</PauseableComponentContainer>
</Paper>
);
};

PauseableReduxItem.propTypes = {
dispatchWhenPaused: PropTypes.bool,
};

PauseableReduxItem.defaultProps = {
dispatchWhenPaused: false,
};

export default PauseableReduxItem;
29 changes: 29 additions & 0 deletions packages/react-pauseable-containers/stories/ReduxMonitor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React, { useCallback } from 'react';

import Chip from '@material-ui/core/Chip';

import { incrementAction, useCountSelector } from 'react-hibernate-dev-helpers';

import Button from '@material-ui/core/Button';
import { useDispatch } from 'react-redux';

const PauseableReduxItem: React.FC = () => {
const dispatch = useDispatch();
const count = useCountSelector();

const increment = useCallback(() => dispatch(incrementAction()), []);

return (
<>
<Button onClick={increment} variant="contained">
Increment
</Button>
<div>
Redux count:
<Chip label={count} />
</div>
</>
);
};

export default PauseableReduxItem;
Loading