How to model transient events in React/Redux? - reactjs

While React with Redux is excellent at modeling UI state, there are occasionally situations where something just happens, the UI needs to handle that event in a discrete procedural way, and it doesn’t make sense to think of that transient event as a piece of state that would persist for any period of time.
Two examples, from a JS Bin-like code editor application:
A user exports their code to a GitHub gist. When the export is complete, we want to open a new browser window displaying the gist. Thus, the React component hierarchy needs to know the ID of the gist, but only at a single moment in time, at which point it will open the window and stop caring about the gist export altogether.
A user clicks on an error message, which causes the editor to bring the line where the error occurred into focus in the editor. Again, the UI only cares about which line needs to be focused for a single moment in time, at which point the (non-React-based) editor is told to focus the line, and the whole thing is forgotten about.
The least unsatisfying solution I’ve come up with is:
When the triggering event occurs, dispatch an action to update the Redux state with the needed information (gist ID, line to focus)
The React component that’s interested in that information will monitor the appropriate props in a lifecycle hook (componentWillReceiveProps etc.). When the information appears in its props, it takes the appropriate action (loads the gist window, focuses the editor on the line)
The component then immediately dispatches another event to the Redux store essentially saying, “I’ve handled this”. The transient event data is removed from Redux state.
Is there a better pattern for this sort of situation? I think one perhaps fundamental part of the picture is that the UI’s response to the action always breaks out of the React component structure—opening a new window, calling a method on the editor’s API, etc.

Your solution would certainly work, but these sorts of problems you bring up don't sound particularly well suited to be handled with redux at all. Just using plain React and passing the necessary functions to your component sounds a lot more natural to me.
For the export case, for instance, rather than dispatching an action which updates some state, which then triggers the new window to open, why not just open the new window in place of dispatching that action? If you have the info necessary to dispatch an action to trigger opening a window, you ought to be able to just open the window in the same place.
For the example where clicking an error message triggers calling a non-React, imperative api, pass a function from the nearest common parent of the error message and the editor. The parent can also maintain a ref to the wrapper around the editor. Even if it's multiple levels deep, it's not too bad to get a ref to what you want if you pass down a function to set the ref. Thus, the function passed down from the parent to the error message component can simply call a method on the ref it maintains to the editor. Basically, something like this:
class Parent extends Component {
constructor(...args) {
super(...args)
this.onErrorMessageClick = this.onErrorMessageClick.bind(this)
}
onErrorMessageClick(lineNumber) {
if (this.editor) {
this.editor.focusOnLine(lineNumber)
}
}
render() {
return (
<div>
<ErrorMessage onClick={ this.onErrorMessageClick } lineNumber={ 1 } />
<ErrorMessage onClick={ this.onErrorMessageClick } lineNumber={ 2 } />
<EditorWrapper editorRef={ (editor) => { this.editor = editor } } />
</div>
)
}
}
const ErrorMessage = ({ onClick, lineNumber }) => (
<button onClick={ () => onClick(lineNumber) }>
{ `Error Message For ${lineNumber}` }
</button>
)
// Just adding EditorWrapper because your editor and error messages probably aren't right next to each other
const EditorWrapper = ({ editorRef }) => <Editor ref={ editorRef } />
class Editor extends Component {
focusOnLine(lineNumber) {
// Tell editor to focus on the lineNumber
}
render() {
...
}
}

I often use redux-thunk for these types of events.
Essentially, its not different to setting up a normal thunk, only I don't dispatch an action in it.
const openGist = (id) => () => {
// code to open gist for `id`
}
I then use this action creator like I would any other from the component triggering it, e.g. mapped in mapDispatchToProps and called in an onClick handler.
A common question I get asked is why don't I just put this code straight into the component itself, and the answer is simple - testability. It is far easier to test the the component if it doesn't have events that cause side effects and it's easier to test code that cause side effects in isolation to anything else.
The other advantage is that more often than not, the UX designers will step in at some point and want some kind of feedback to the user for some of the events of this nature (e.g. briefly highlight the error row) so adding an X_COMPLETED action to the event is much easier at this point.

Related

RxJS and repeated events

I am new to RxJs in general but am investigating a bug in some React code in which, upon an unrelated action, an old event seems to be emitted and rendered to a display error. Think if you had two buttons that generated two messages somewhere on screen, and clicking one button was showing the message for the other button.
Being new to RxJs I'm not positive where the problem lays. I don't see a single ReplaySubject in the code, only Obserables, Subjects, and BehaviourSubjects. So this is either misuse of an RxJs feature or just some bad logic somewhere.
Anyway I found the code with the related Observable and I'm not quite sure what this person was trying to accomplish here. I have read up on combineLatest, map, and pipe, but this looks like pointless code to me. Could it also be somehow re-emitting old events? I don't see dynamic subscriptions anywhere, especially in this case.
Tldr I don't understand the intent of this code.
export interface IFeedback {
id: number
text: string
}
export interface IFeedbackMessages {
message: IFeedback | undefined
}
feedback$ = new BehaviorSubject<IFeedback | undefined>(undefined)
feedbackNotifs$: Observable<IFeedbackMessages> = combineLatest([
feedback$
]).pipe(
map(([feedback]) => ({
feedback
})
))
I also found this which maybe be an issue. In the React component that displays this message, am I wrong but does it look like each time this thing renders it subscribes and then unsubscribes to the above Subject?
const FeedbackDisplay: React.FC () => {
const [feedbackNotifications, setFeedbackNotifications] = React.useState<IFeedbackMessages>()
React.useEffect(() =>
{
const sub = notification$.subscribe(setFeedbackNotifications)
return () => sub?.unsubscribe()
}, [notifications$])
}
Could it also be somehow re-emitting old events?
Yes, it probably is. BehaviorSubject has the unique property of immediately emitting the last value pushed to it as soon as you subscribe to it.
It's great when you want to model some persistent state value, and it's not good for events whose actual moment of occurrence is key. It sounds like the feedback messages you're working with fall into the second category, in which case Subject is probably a better choice.
does it look like each time this thing renders it subscribes and then unsubscribes to the above Subject?
Not exactly. useEffect accepts a callback, and within that callback you can optionally return a "cleanup" function. React will hang onto that function until the effect is triggered again, then it calls it to clean things up (which in this case consists of closing out the subscription) to make room for the next effect.
So in this case, the unsubscribe will only happen when the component is rendered with a new value for notifications$. Also worth pointing out that notifications$ will only change if it's either passed as a prop or created within the component function. If it's defined outside the function (imported from another file for example), you don't need to (and in fact should not) put it into useEffect's dependency array.

How to obtain a value from a file in another file thanks to the store

I have recently started a project in a team of three people. We've been learning some React for that, and when I explained to a friend what I was trying to do, he advised me to try it with Redux, but I'm not quite sure how I'm supposed to proceed.
My goal is to generate a code in an Arrival.js page, depending on a form that the customer filled, based on his first name and the date. I have the right function to generate that code. Now when the customer submits the form, a pop-up shows up, telling him "here's your code: CODE1245" for example. The thing is that this pop-up is a component located in another file named PopUpArrival.js and I can't manage to transfer the code generated in Arrival.js with the form's information.
So I tried to set up a store with reducers and actions, but I can't manage to find any actions that would fit my goals. Am I overthinking it by trying to use Redux? Or is there an easy way to do so? It's been three days and I'm quite lost and demotivated to be honest. Thanks if anyone took the time to read me and I'd be even more grateful if anyone shows up with a solution
If your components have a
parent-child relationship, then it would be easy to send the code as a prop to the child.
Sibling relationship, then What is the most simplest method of updating component's state from another component? Lets say components are siblings?
Not related at all, there are a lot of ways like cookies, local storage, context, redux, etc.
I guess using redux only for this purpose might be overkill. You can look at contexts in that case.
I think Redux maybe an overkill here. You can get this done without redux from how it looks. If you can consider the following flow.
Your Arrival.js will maintain state for the first name and the date. Your PopUpArrival.js should be a component in itself, which takes whatever text you want to show in popup as a prop. Based on when you want to trigger the popup logic, you should display the PopUpArrival component. I have tried to roughly write some code below which does the same.
const PopUpArrival = (props) => {
const { textToShow } = props;
return(
<div>{textToShow}</div>;
);
}
const Arrival = () => {
// maintains state for both name and date
const [showPopup, setPopupDisplay] = useState(false);
const togglePopup = () => {
setPopupDisplay((currentDisplay) => !currentDisplay);
}
const getTextToShow = () => {
return `${name}-{date}`; // whatever logic you have
}
return(
<>
<button onClick={togglePopup}>
Submit
</button>
{
showPopup && <PopUpArrival textToShow={getTextToShow()}/>
}
</>
)
}

Recording redux navigation state in model state?

I've structured my redux application such that my data models are handled on separate branches of the state tree.
{concerts, venues}
I've also used react-navigation-redux-helpers to put my navigation state into the tree:
{concerts, venues, nav}
However, I want to record information about the visibility state of a particular model. When the ConcertScreen is shown, I want to know when a user's looking/stops looking at a particular concert ID (and letting the server know), with the eventual goal of measuring how long a particular concert ID was visible on screen.
I've done this by adding branches for Navigation/NAVIGATE, Navigation/RESET, and Navigation/BACK to the concerts reducer, and setting visible: true on the appropriate object under concerts.
This has been error prone, since the navigation state can be modified by actions other than these specific actions. (A logout action handled directly by the nav reducer, for example.)
I see two alternatives, both not ideal:
Use props.navigation.addListener to listen to focus and blur events on the ConcertScreen, trigger custom concertFocused/concertBlurred actions, and handle those in my concert reducer instead of the Navigation/* actions.
Create a selector that computes the currently visible concert from the nav state and refactor the business logic that expects concert.visible as input to use the selector instead.
The problem with 1 seems to be that it's adding overhead to the event loop, all the extra actions flying around means extra rendering overhead.
2 avoids the extra actions, but it seems like a lot of refactoring for not a whole lot of gain, and it means I have to move business logic out of the concert reducer and put it elsewhere.
Say I use option 2. I add a middleware that, on any action, applies the selector to state.nav and from that computes what Concert is currently displayed. If I wanted to measure duration, how would I store start/end time? Fire a new action with that added data so the concert reducer catches it? That just seems like option 1 with extra steps.
I could also have this middleware add a field to every action indicating the concert display state, and have the concert reducer handle it in the default/fallthrough case. Do people do that?
I would approach your use-case in such a way, that I will get the best of both solutions.
First of all, having many actions dispatched you're worried about rendering overhead. Using a selector library, let's say reselect, the library memoization will prevent unnecessarily components rerendering.
Later on, if I understand you correctly, your goal is to let the server know the visibility status of an item (concert) and eventually its visible time. If your goal is notifying the server only, without letting know the rest app's front-end users, why do you want to keep tracking it in Redux too? You can skip the Redux part and send updates to the server only.
Let's assume, that you need Redux for the tracking. You can try on your way structuring the Store, as you already mentioned, adding the visible flag to each object in the Redux store. But if your items' structure is bigger enough and it's costly to copy and update the object each time when changing the visible flag, you can consider creating a dedicated Store branch and reducer, that will be responsible only for the tracking needs. Something like that:
tracking : {
concerts: {
1: { visible: true, time: 10 }
}
}
Now, updating an item's flag, only the above tiny structure has to be copied and modified. Even, you can make it smaller and more specific for a certain item type (trackingConcerts).
* Keep in mind, it's on your own to decide whether or not such a dedicated Store branch will improve the performance, because we don't know your detailed architecture and Store specifics.
Continuing with the solutions ...
Relying on navigation actions + middleware is error prone, as you mentioned. What about the use-case you have a general page of components (i.e. navigation action with generic name will be dispatched), but you render there one of your items (concert)? Also rendering an item, would be always coupled with modifying the mapping logic in your middleware or wherever place you track the items by action name. Another tricky case is when you render different type of items (concerts, venues) on exactly one page. How will you differ and track the items, considering you have only 1 navigation item? Also in a such setup, I don't see a straightforward way for handling an item visible time.
About the selectors as a solution - they can be only a small part of the solution. The selector is responsible for selecting and managing derived state. Nothing more.
Show me the code, please.
I would create a wrapper component around react on screen (or any similar library that tracks component visibility) and implement only the tracking visible time of the component.
The wrapper will trigger callbacks when the component visibility state is changed and a callback on componentDidUnmount including the visible time.
That's all! Now you can attach handlers on these callbacks and you can update your Redux and/or notify the server for the visibility changes, without relying on any navigation actions and middlewares.
Usage:
const App = () => (
<Tracking
onVisibilityChange={isVisible => {}}
onUnmount={visibleSeconds => {}}
>
<Concert id={1} />
</Tracking>
)
Tracking Wrapper:
import TrackVisibility from 'react-on-screen'
const Tracking = ({ children, libraryProps, ...rest }) => (
<TrackVisibility {...libraryProps}>
<TrackingCore {...rest}>
{children}
</TrackingCore>
</TrackVisibility>
)
TrackingCore, our custom tracking logic:
class TrackingCore extends React.Component {
constructor (props) {
super(props)
this.state = {
visibleSeconds: 0,
interval: null
}
}
componentDidMount() {
this.track()
}
componentWillReceiveProps (nextProps) {
this.track(nextProps)
}
componentDidUnmount() {
const { visibleSeconds, interval } = this.state
const { onUnmount } = this.props
onUnmount(visibleSeconds)
clearInterval(interval)
}
track (nextProps) {
const { isVisible, onVisibilityChange } = this.props
const { visibleSeconds, interval } = this.state
const hasVisibilityChanged = (isVisible !== nextProps.isVisible) || !nextProps
const isVisibleValue = nextProps ? nextProps.isVisible : isVisible
// On visibility change, invoke the callback prop
if (hasVisibilityChanged) {
onVisibilityChange(isVisibleValue)
// If it becomes visible, start counting the `visibleSeconds`
if (isVisibleValue) {
this.setState({
interval: setInterval(() => this.setState({
visibleSeconds: visibleSeconds + 1
}), 1000)
})
} else {
clearInterval(interval)
}
}
}
render () {
return this.props.children
}
}

Is it wrong to call component methods passed as arguments inside of redux actions?

I'm struggling with one task I've been appointed to and the only workaround I found is to call the action argument callback inside the action. Is this a bad idea from a design point of view, because the code itself works and passes numerous tests? The general purpose of this solution is to somehow trigger the component function when a certain logic is being followed.
export function myAction(componentClb: () => void): any {
return (dispatch: Dispatch<AppStore>): void => {
someRESTAPIcall()
.then((condition) => {
condition
? dispatch(anotherActionThatTakesCallbackAsArgument(componentClb))
: componentClb();
})
.catch((error: Error) => {
dispatch(myErrorAction());
});
};
}
The biggest mistake about this is to take React components as functional classes. Components really are just to handle the WHAT and HOW of page rendering. Anything outside of rendering logics should be removed from it.
Instead of working as callback, this anotherActionThatTakesCallbackAsArgument should update the redux store.
The container that componentClb belongs to should be connected to redux store and selects the fields from the stores, and pass these fields to componentClb as props, so that componentClb can handle the rendering based on the response of myAction
With the info you provided, it's hard to give a concrete code example. Maybe describe the scenario you are trying to solve and people are able to give you more direct feedbacks.

onChange event for textarea instead of get value when needed

I want to understand the performance implications of React's advocated way of handling textarea's value change.
Before React's one-direction data flow philosophy, one would do:
button.onClick( processInput(textarea.value) );
Now, one has to do
textarea.onChange( dispatch({ type: "inputChange", value: textarea.value }) );
button.onClick( dispatch({ type: "buttonClick" }) );
store(
if (action.type === "inputChange") {
this.lastInput = action.value
} else if (action.type === "buttonClick") {
processInput(this.lastInput)
}
)
Is my understanding correct? Isn't this much more events compared to before? Why spam with many useless inputChange events? If my understanding is not right, what is the correct React way of doing this?
First, you're conflating a couple different things. React's "controlled input" pattern doesn't require Redux. You can implement controlled inputs inside a component, using local component state. I have a gist discussing this concept at react-controlled-inputs.md.
Second, even if you're using Redux, you don't have to directly control the input with Redux state and actions. Whether you do so is up to you. In fact, there are times when you might want to buffer text onChange events locally in the component, and only dispatch a Redux action once the user is done typing. I've got another gist demonstrating a buffering wrapper component at FormContentsContainer.jsx.
Finally, even if you do decide to directly control an input with Redux state, I'm not exactly sure why you're saying it's "more events". The input's onChange is going to fire every time you type, regardless. It's up to you whether you choose to do something with those events like translating them into Redux actions, or ignore them. Or, if you prefer to use "uncontrolled inputs" and just ask the inputs for their values when someone clicks Submit, that's entirely up to you as well.
I think it would be better to extract actual code from the Gist and put it here.
If you need text value only on button click, you can ask it through ref
const MyComponent extends Component {
onClick() {
const input = this.refs.myInput;
const value = input.value;
// do something with the value
}
render() {
return <input type="text" ref="myInput" />
}
}
You can get access to DOM element inside of your component using refs. If you need some simple solution, that will work for you.

Resources