Window Resize - React + Redux - javascript

I'm new to Redux and I'm wondering if anyone has some tips on best practices for handling non React events like window resize. In my research, I found this link from the official React documentation:
https://facebook.github.io/react/tips/dom-event-listeners.html
My questions is, when using Redux, should I store the window size in my Store or should I be keeping it in my individual component state?

Good question. I like to to have a ui part to my store. The reducer for which might look like this:
const initialState = {
screenWidth: typeof window === 'object' ? window.innerWidth : null
};
function uiReducer(state = initialState, action) {
switch (action.type) {
case SCREEN_RESIZE:
return Object.assign({}, state, {
screenWidth: action.screenWidth
});
}
return state;
}
The action for which is pretty boilerplate. (SCREEN_RESIZE being a constant string.)
function screenResize(width) {
return {
type: SCREEN_RESIZE,
screenWidth: width
};
}
Finally you wire it together with an event listener. I would put the following code in the place where you initialise your store variable.
window.addEventListener('resize', () => {
store.dispatch(screenResize(window.innerWidth));
});
Media Queries
If your app takes a more binary view of screen size (e.g. large/small), you might prefer to use a media query instead. e.g.
const mediaQuery = window.matchMedia('(min-width: 650px)');
if (mediaQuery.matches) {
store.dispatch(setLargeScreen());
} else {
store.dispatch(setSmallScreen());
}
mediaQuery.addListener((mq) => {
if (mq.matches) {
store.dispatch(setLargeScreen());
} else {
store.dispatch(setSmallScreen());
}
});
(I'll leave out the action and reducer code this time. It's fairly obvious what they look like.)
One drawback of this approach is that the store may be initialised with the wrong value, and we're relying on the media query to set the correct value after the store has been initialised. Short of shoving the media query into the reducer file itself, I don't know the best way around this. Feedback welcome.
UPDATE
Now that I think about it, you can probably get around this by doing something like the following. (But beware, I have not tested this.)
const mediaQuery = window.matchMedia('(min-width: 650px)');
const store = createStore(reducer, {
ui: {
largeScreen: mediaQuery.matches
}
});
mediaQuery.addListener((mq) => {
if (mq.matches) {
store.dispatch(setLargeScreen());
} else {
store.dispatch(setSmallScreen());
}
});
UPDATE II: The drawback of this last approach is that the ui object will replace the entire ui state not just the largeScreen field. Whatever else there is of the initial ui state gets lost.

Use redux-responsive to handle the responsive state of your application. It uses a store enhancer to manage a dedicated area(property) of your store's state (normally called 'browser') via its own reducer, so that you don't have to implicitly add event listeners to the document object.
All you need to do is to map the browser.width, browser.height, etc. to your component's props.
Please note that only the reducer defined in redux-responsive is responsible for updating these values.

I have a similar case where I need the window size for purposes other than responsiveness. According to this, you could also use redux-thunk:
function listenToWindowEvent(name, mapEventToAction, filter = (e) => true) {
return function (dispatch) {
function handleEvent(e) {
if (filter(e)) {
dispatch(mapEventToAction(e));
}
}
window.addEventListener(name, handleEvent);
// note: returns a function to unsubscribe
return () => window.removeEventListener(name, handleEvent);
};
}
// turns DOM event into action,
// you can define many of those
function globalKeyPress(e) {
return {
type: 'GLOBAL_KEY_PRESS',
key: e.key
};
}
// subscribe to event
let unlistenKeyPress = store.dispatch(listenToWindowEvent('keypress', globalKeyPress));
// eventually unsubscribe
unlistenKeyPress();
Although in reality, if your use case is a simple one you don't even need to use a thunk function. Simply create a listener function that takes Redux dispatch as a parameter and use it to dispatch desired action. See the reference for an example. But the currently accepted answer pretty much covers this case

Related

React functional component: Trying to set a state once a window break point is reached

Here is my code for the problem, my issue is that the event is triggering multiple times when it hits the 960px breakpoint. This should only fire once it reaches the breakpoint, but I am getting an almost exponential amount of event triggers.
const mediaQuery = '(max-width: 960px)';
const mediaQueryList = window.matchMedia(mediaQuery);
mediaQueryList.addEventListener('change', (event) => {
if (event.matches) {
setState((prevState) => ({
...prevState,
desktopNavActivated: false,
}));
} else {
setState((prevState) => ({
...prevState,
menuActivated: false,
navItemExpanded: false,
}));
}
});```
Hi there and welcome to StackOverflow!
I'm going to assume, that your code does not run within a useEffect hook, which will result in a new event listener being added to your mediaQueryList every time your component gets updated/rendered. This would explain the exponential amount of triggers. The useEffect hook can be quite unintuitive at first, so I recommend reading up on it a bit. The docs do quite a good job at explaining the concept. I also found this article by Dan Abramov immensely helpful when I first started using effects.
The way you call your setState function will always cause your component to update, no matter whether the current state already matches your media query, because you pass it a new object with every update. Unlike the setState method of class based components (which compares object key/values iirc), the hook version only checks for strict equality when determining whether an update should trigger a re-render. An example:
{ foo: 'bar' } === { foo: 'bar' } // Always returns false. Try it in your console.
To prevent that from happening, you could set up a state hook that really only tracks the match result and derive your flags from it. Booleans work just fine with reference equality:
const [doesMatch, setDoesMatch] = useState(false)
So to put it all together:
const mediaQuery = '(max-width: 960px)';
const mediaQueryList = window.matchMedia(mediaQuery);
const MyComponent = () =>
const [match, updateMatch] = useState(false)
useEffect(() => {
const handleChange = (event) => {
updateMatch(event.matches)
}
mediaQueryList.addEventListener('change', handleChange)
return () => {
// This is called the cleanup phase aka beforeUnmount
mediaQueryList.removeEventListener('change', handleChange)
}
}, []) // Only do this once, aka hook-ish way of saying didMount
const desktopNavActivated = !match
// ...
// Further process your match result and return JSX or whatever
}
Again, I would really advise you to go over the React docs and some articles when you find the time. Hooks are awesome, but without understanding the underlying concepts they can become very frustrating to work with rather quickly.

How to reduce the number of times useEffect is called?

Google's lighthouse tool gave my app an appalling performance score so I've been doing some investigating. I have a component called Home
inside Home I have useEffect (only one) that looks like this
useEffect(() => {
console.log('rendering in here?') // called 14 times...what?!
console.log(user.data, 'uvv') // called 13 times...again, What the heck?
}, [user.data])
I know that you put the second argument of , [] to make sure useEffect is only called once the data changes but this is the main part I don't get. when I console log user.data the first 4 console logs are empty arrays. the next 9 are arrays of length 9. so in my head, it should only have called it twice? once for [] and once for [].length(9) so what on earth is going on?
I seriously need to reduce it as it must be killing my performance. let me know if there's anything else I can do to dramatically reduce these calls
this is how I get user.data
const Home = ({ ui, user }) => { // I pass it in here as a prop
const mapState = ({ user }) => ({
user,
})
and then my component is connected so I just pass it in here
To overcome this scenario, React Hooks also provides functionality called useMemo.
You can use useMemo instead useEffect because useMemo cache the instance it renders and whenever it hit for render, it first check into cache to whether any related instance has been available for given deps.. If so, then rather than run entire function it will simply return it from cache.
This is not an answer but there is too much code to fit in a comment. First you can log all actions that change user.data by replacing original root reducer temporarlily:
let lastData = {};
const logRootReducer = (state, action) => {
const newState = rootReducer(state, action);
if (newState.user.data !== lastData) {
console.log(
'action changed data:',
action,
newState.user.data,
lastData
);
lastData = newState.user.data;
}
return newState;
};
Another thing causing user.data to keep changing is when you do something like this in the reducer:
if (action.type === SOME_TYPE) {
return {
...state,
user: {
...state.user,
//here data is set to a new array every time
data: [],
},
};
}
Instead you can do something like this:
const EMPTY_DATA = [];
//... other code
data: EMPTY_DATA,
Your selector is getting user out of state and creating a new object that would cause the component to re render but the dependency of the effect is user.data so the effect will only run if data actually changed.
Redux devtools also show differences in the wrong way, if you mutate something in state the devtools will show them as changes but React won't see them as changes. When you assign a new object to something data:[] then redux won't show them as changes but React will see it as a change.

React functional component is taking snapshot of state at the time of registering handler on websocket

react functional component is taking snapshot of state at the time of subscription.
For ex. PFB code.
If i click setSocketHandler button and then press setWelcomeString button. Now if i receive message over socket when i log welcomestring it is empty.
But if i click setWelcomeString button and then click setSocketHandler button. Now if i receive message on socket Welcome is getting logged on console.
I have seen same behaviour in project so just created this simple app to prove.
If i use class component which is commented below.. everything works fine.
So my question is why react functional component is working on a state at the time of reg and not on actual state at the time message is received.
This is very weird. How to make it work in functional component correctly.
import React, {useEffect, useState} from 'react';
import logo from './logo.svg';
import './App.css';
const io = require('socket.io-client');
const socket = io.connect('http://localhost:3000/');
const App : React.FunctionComponent = () => {
const [welcomeString, setWelcomeString] = useState("");
const buttonCliecked = () => {
console.log("clocked button");
setWelcomeString("Welcome")
}
const onsockethandlerclicked = () => {
console.log("socket handler clicked");
socket.on('out', () => {
console.log("Recived message")
console.log(welcomeString);
});
}
return (
<div>
<header className="component-header">User Registration</header>
<label>{welcomeString}</label>
<button onClick={buttonCliecked}>setWelcomeString</button>
<button onClick={onsockethandlerclicked}>setSocketHandler</button>
</div>
);
}
/*class App extends React.Component {
constructor(props) {
super(props);
this.state = {
welcomeString:""
}
}
buttonCliecked = () => {
console.log("clocked button");
this.setState({ welcomeString:"Welcome"})
}
onsockethandlerclicked = () => {
console.log("socket handler clicked");
socket.on('out', () => {
console.log("Recived message")
console.log(this.state.welcomeString);
});
}
render() {
return (
<div>
<header className="component-header">User Registration</header>
<label>{this.state.welcomeString}</label>
<button onClick={this.buttonCliecked}>setwelcomestring</button>
<button onClick={this.onsockethandlerclicked}>setSocketHandler</button>
</div>
);
}
}*/
export default App;
For those of us coming from a Redux background, useReducer can seem deceptively complex and unnecessary. Between useState and context, it’s easy to fall into the trap of thinking that a reducer adds unnecessary complexity for the majority of simpler use cases; however, it turns out useReducer can greatly simplify state management. Let’s look at an example.
As with my other posts, this code is from my booklist project. The use case is that a screen allows users to scan in books. The ISBNs are recorded, and then sent to a rate-limited service that looks up the book info. Since the lookup service is rate limited, there’s no way to guarantee your books will get looked up anytime soon, so a web socket is set up; as updates come in, messages are sent down the ws, and handled in the ui. The ws’s api is dirt simple: the data packet has a _messageType property on it, with the rest of the object serving as the payload. Obviously a more serious project would design something sturdier.
With component classes, the code to set up the ws was straightforward: in componentDidMount the ws subscription was created, and in componentWillUnmount it was torn down. With this in mind, it’s easy to fall into the trap of attempting the following with hooks
const BookEntryList = props => {
const [pending, setPending] = useState(0);
const [booksJustSaved, setBooksJustSaved] = useState([]);
useEffect(() => {
const ws = new WebSocket(webSocketAddress("/bookEntryWS"));
ws.onmessage = ({ data }) => {
let packet = JSON.parse(data);
if (packet._messageType == "initial") {
setPending(packet.pending);
} else if (packet._messageType == "bookAdded") {
setPending(pending - 1 || 0);
setBooksJustSaved([packet, ...booksJustSaved]);
} else if (packet._messageType == "pendingBookAdded") {
setPending(+pending + 1 || 0);
} else if (packet._messageType == "bookLookupFailed") {
setPending(pending - 1 || 0);
setBooksJustSaved([
{
_id: "" + new Date(),
title: `Failed lookup for ${packet.isbn}`,
success: false
},
...booksJustSaved
]);
}
};
return () => {
try {
ws.close();
} catch (e) {}
};
}, []);
//...
};
We put the ws creation in a useEffect call with an empty dependency list, which means it’ll never re-fire, and we return a function to do the teardown. When the component first mounts, our ws is set up, and when the component unmounts, it’s torn down, just like we would with a class component.
The problem
This code fails horribly. We’re accessing state inside the useEffect closure, but not including that state in the dependency list. For example, inside of useEffect the value of pending will absolutely always be zero. Sure, we might call setPending inside the ws.onmessage handler, which will cause that state to update, and the component to re-render, but when it re-renders our useEffect will not re-fire (again, because of the empty dependency list)—as a result that closure will go on closing over the now-stale value for pending.
To be clear, using the Hooks linting rule, discussed below, would have caught this easily. More fundamentally, it’s essential to break with old habits from the class component days. Do not approach these dependency lists from a componentDidMount / componentDidUpdate / componentWillUnmount frame of mind. Just because the class component version of this would have set up the web socket once, in componentDidMount, does not mean you can do a direct translation into a useEffect call with an empty dependency list.
Don’t overthink, and don’t be clever: any value from your render function’s scope that’s used in the effect callback needs to be added to your dependency list: this includes props, state, etc. That said—
The solution
While we could add every piece of needed state to our useEffect dependency list, this would cause the web socket to be torn down, and re-created on every update. This would hardly be efficient, and might actually cause problems if the ws sends down a packet of initial state on creation, that might already have been accounted for, and updated in our ui.
If we look closer, however, we might notice something interesting. Every operation we’re performing is always in terms of prior state. We’re always saying something like “increment the number of pending books,” “add this book to the list of completed,” etc. This is precisely where a reducer shines; in fact, sending commands that project prior state to a new state is the whole purpose of a reducer.
Moving this entire state management to a reducer would eliminate any references to local state within the useEffect callback; let’s see how.
function scanReducer(state, [type, payload]) {
switch (type) {
case "initial":
return { ...state, pending: payload.pending };
case "pendingBookAdded":
return { ...state, pending: state.pending + 1 };
case "bookAdded":
return {
...state,
pending: state.pending - 1,
booksSaved: [payload, ...state.booksSaved]
};
case "bookLookupFailed":
return {
...state,
pending: state.pending - 1,
booksSaved: [
{
_id: "" + new Date(),
title: `Failed lookup for ${payload.isbn}`,
success: false
},
...state.booksSaved
]
};
}
return state;
}
const initialState = { pending: 0, booksSaved: [] };
const BookEntryList = props => {
const [state, dispatch] = useReducer(scanReducer, initialState);
useEffect(() => {
const ws = new WebSocket(webSocketAddress("/bookEntryWS"));
ws.onmessage = ({ data }) => {
let packet = JSON.parse(data);
dispatch([packet._messageType, packet]);
};
return () => {
try {
ws.close();
} catch (e) {}
};
}, []);
//...
};
While slightly more lines, we no longer have multiple update functions, our useEffect body is much more simple and readable, and we no longer have to worry about stale state being trapped in a closure: all of our updates happen via dispatches against our single reducer. This also aids in testability, since our reducer is incredibly easy to test; it’s just a vanilla JavaScript function. As Sunil Pai from the React team puts it, using a reducer helps separate reads, from writes. Our useEffect body now only worries about dispatching actions, which produce new state; before it was concerned with both reading existing state, and also writing new state.
You may have noticed actions being sent to the reducer as an array, with the type in the zero slot, rather than as an object with a type key. Either are allowed with useReducer; this is just a trick Dan Abramov showed me to reduce the boilerplate a bit :)
What about functional setState()
Lastly, some of you may be wondering why, in the original code, I didn’t just do this
setPending(pending => pending - 1 || 0);
rather than
setPending(pending - 1 || 0);
This would have removed the closure problem, and worked fine for this particular use case; however, the minute updates to booksJustSaved needed access to the value of pending, or vice versa, this solution would have broken down, leaving us right where we started. Moreover, I find the reducer version to be a bit cleaner, with the state management nicely separated in its own reducer function.
All in all, I think useReducer() is incredibly under-utilized at present. It’s nowhere near as scary as you might think. Give it a try!
Happy coding!

Access React Context via function

I am currently developing a package, which gives my React-widget responsiveness. The problem is, that the responsiveness does not depends on the viewport-width but on on the width of the widget-container-element.
Currently I am wrapping my <App> with a <ResponsiveProvider>. This provider subscribes to the windows.resize event and stores the format into the context's value.
All children elements get re-rendered if the format changes. That's fine.
Now, for show/hide components based on the current widget format, I just could implement a component, which accesses this context with contextType.
But I need a function, which I can use in any place of my application like: ResponsiveUtil.isSmall() or ResponsiveUtil.getCurrentFormat().
What would be the best approach to make the information (format) accessable via a function?
Thanks
I'm not sure if this would be the best approach, but it will work. You can set up a global event listener that will be dispatched each time the format changes in your component. I found a package here for the global events, but it wouldn't be hard to write your own either. Using react-native-event-listeners, it would look something like:
ResponsiveUtil.js
import { EventRegister } from 'react-native-event-listeners';
let format = {};
EventRegister.addEventListener('responsive-format-changed', data => {
format = data;
});
const ResponsiveUtils = {
getCurrentFormat() {
return Object.assign({}, format);
},
isSmall() {
//isSmall logic
}
};
export default ResponsiveUtils;
Then, in your <ResponsiveProvider>, during the resize event, dispatch the new format when you update the context
ResponsiveProvider.js
import { EventRegister } from 'react-native-event-listeners';
//...Other component code
window.resize = () => {
let format = calculateNewFormat();
//update context...
//dispatch new format
EventRegister.emit('responsive-format-changed', format);
};

Redux: turn middleware on and off

I am looking for a way to turn a middleware on and off. I introduced a tutorial functionality - I listen to what the user is doing with the UI by checking each action with a "guidance" middleware. if the user clicks on the right place he moves to the next step in the tutorial. However this behaviour is only needed when the tutorial mode is on. Any ideas?
const store = createStore(holoApp, compose(applyMiddleware(timestamp, ReduxThunk, autosave, guidance),
window.devToolsExtension ? window.devToolsExtension() : f => f));
for now my solution was to keep the "on" switch in a guidanceState reducer and dirty check it in the middleware:
const guidance = store => next => action => {
let result = next(action)
const state = store.getState();
const { guidanceState } = state;
const { on } = guidanceState;
if (on) {
....
However, ~95% of the time the tutorial mode would be off so dirty checking every action all the time feels a bit, well, dirty... ;) Any other ways?
Don't do stateful things in middleware (unless you have a good pattern for managing that state, like Sagas). Don't do stateful things with your middleware stack at all if you can avoid it. (If you must do so, #TimoSta's solution is the correct one).
Instead, manage your tours with a reducer:
const finalReducer = combineReducers({
// Your other reducers
tourState: tourReducer
});
function tourReducer(state = initalTourState, action) {
switch(action.type) {
case TOUR_LAST_STEP:
return /* compose next tour step state here */;
case TOUR_NEXT_STEP:
return /* compose last tour step state here */;
case TOUR_CLOSE:
return undefined; // Nothing to do in this case
default:
return state;
}
}
Then, in your application use the current state of tourState to move the highlighting, and if there is nothing in tourState, turn the tour off.
store.subscribe(() => {
const state = store.getState();
if (state.tourState) {
tourManager.setTourStep(state.tourState);
} else {
tourManager.close();
}
});
You don't have to use a stateful tour manager either - if you're using React it could just be a component that pulls out tourState with a connect wrapper and renders null if there is no state:
// waves hands vigorously
const TourComponent = (props) => {
if (props.currentStep) return <TourStep ...props.currentStep />;
return null;
}
I don't know of any way to replace middlewares on the fly via redux's API.
Instead, you could create a completely new store with the old store's state as initial state and the new set of middlewares. This may work seamlessly with your application.
Three ideas you could consider:
Have the middleware listen for "GUIDANCE_START" and "GUIDANCE_STOP" actions. When those come through, update some behavior, and don't actually pass them to next.
You could write a middleware that constructs its own middleware pipeline internally, and dynamically adds and removes the guidance middleware as needed (somewhat related discussion at replaceMiddleware feature for use with lazy-loaded modules)
This might be a good use case for something like a saga, rather than a middleware. I know I've seen discussions of using sagas for onboarding workflows, such as the Key&Pad app (source:key-and-pad)

Categories