Skip to content

Commit

Permalink
Merge f8df3c3 into 0f6f9b9
Browse files Browse the repository at this point in the history
  • Loading branch information
spautz committed Jun 30, 2020
2 parents 0f6f9b9 + f8df3c3 commit 96d3541
Show file tree
Hide file tree
Showing 15 changed files with 335 additions and 185 deletions.
1 change: 1 addition & 0 deletions packages/dev-helpers/package.json
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
@@ -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
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
@@ -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
@@ -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
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
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
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
@@ -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;
@@ -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
@@ -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
@@ -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;

0 comments on commit 96d3541

Please sign in to comment.