Reputation: 814
I have a small component that renders another page, the webpage URL has a token attached as an URL parameter, like in the sample bellow:
const SampleComponent = () => {
const { refreshToken } = useSelector(state => state.auth);
const src = `${HOSTNAME}/page/?refresh_token=${refreshToken}`;
return <webview src={src} />;
};
export default SampleComponent;
I have a special cron
that runs every hour and updates the tokens and Redux is updated as well with the new tokens.
window.tokensCron = new CronJob('0 0 * * *', () => {
store.dispatch(getTokens());
});
When the token is updated in Redux the page is being refreshed automatically. How to prevent updating the component so that the refresh page won't happen?
Upvotes: 1
Views: 2070
Reputation: 1004
Your state may be malformed. As I see you have:
So consider modify your state to follow this structure:
state = {
auth: {
initialToken,
refreshToken
}
};
Then in your component, simply do that:
const initialToken = useSelector(state => state.auth.initialToken);
Important, in your useSelector
please returns only the value you want (your token, not the whole auth
). Like that your component will update ONLY if your token changes.
As you do in your current code if auth
changes, your component is updated even if token did not change.
Upvotes: 0
Reputation: 39270
So you want to use the token from redux state only when the component mounts?
You can make a custom hook that sets the token only once after the component mounts by deliberately leaving out a dependency of an effect, then use that in a HOC to pass the value of the token as it was when it mounted with other props to the component that needs the token:
//custom hook gets token only on mount
const useToken = () => {
const token = useSelector(selectToken);
const [val, setVal] = useState();
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => setVal(token), []);
return val;
};
//hoc that will only re render if props change (not when token changes)
const withToken = (Component) => (props) => {
const token = useToken();
const propsWithToken = useMemo(
() => ({ ...props, token }),
[props, token]
);
return token ? <Component {...propsWithToken} /> : null;
};
Make sure that the component you pass to withToken is a pure component so it won't get re rendered when props passed to it won't change.
Code snippet with this example is below.
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const {
useState,
useRef,
useEffect,
memo,
useMemo,
} = React;
const initialState = {
token: 1,
};
//action types
const REFRESH_TOKEN = 'REFRESH_TOKEN';
//action creators
const refreshToken = () => ({
type: REFRESH_TOKEN,
});
const reducer = (state = initialState, { type }) => {
if (type === REFRESH_TOKEN) {
return {
...state,
token: state.token + 1,
};
}
return state;
};
//selectors
const selectToken = (state) => state.token;
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(() => (n) => (a) => n(a))
)
);
//custom hook gets token only on mount
const useToken = () => {
const token = useSelector(selectToken);
const [val, setVal] = useState();
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => setVal(token), []);
return val;
};
//hoc that will only re render if props change (not when token changes)
const withToken = (Component) => (props) => {
const token = useToken();
const propsWithToken = useMemo(
() => ({ ...props, token }),
[props, token]
);
return token ? <Component {...propsWithToken} /> : null;
};
const Component = ({ token }) => {
const r = useRef(0);
r.current++;
return (
<div>
rendered: {r.current} token: {token}
</div>
);
};
//using React.memo to make Component a pure component
const PureWithToken = withToken(memo(Component));
const App = () => {
const token = useSelector(selectToken);
const [toggle, setToggle] = useState(true);
const dispatch = useDispatch();
//refresh token every second
useEffect(() => {
const interval = setInterval(
() => dispatch(refreshToken()),
1000
);
return () => clearInterval(interval);
}, [dispatch]);
return (
<div>
<div>token:{token}</div>
<label>
Toggle component with token
<input
type="checkbox"
checked={toggle}
onChange={() => setToggle((t) => !t)}
/>
</label>
{/* when component re mounts it will have the newest token */}
{toggle ? <PureWithToken /> : null}
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<div id="root"></div>
Upvotes: 1