React class instance prop prevents from using PureComponent - reactjs

My React component has a prop that is a class instance based from data coming from the redux store.
The reason for this is that it is much more convenient to deal with this data using a class and all its custom methods (more than 50).
I cannot use PureComponent as React always consider that this prop has changed. The problem is that most of my React components are connected to this prop...
I am aware of solution like reselect but this would mean having too many (as many as my class has custom methods) selectors only for the manipulation of this data.
What would you suggest?
My redux select looks like this:
getStation(state) {
// some logic to fetch the right station
return new WeatherStation(state.services.stationList[i])
}
where WeatherStation is:
class WeatherStation {
constructor (data) {
this.station = data
}
getId() {...}
// many more methods
}
and within my React Compoonent:
class MyComponent extends React.Component {
...
const mapStateToProps = (state, ownProps) => {
return {
station: getStation(state),
}
}
}

Have you tried using reselect along these lines?
import { createSelector } from 'reselect';
getStationData(state) {
// some logic to fetch the right station
// no new instances here, just selecting the relevant data
return state.services.stationList[i];
}
// create "memoized" selector.
const getEnhancedStation = createSelector(
getStationData,
stationData => new WeatherStation(stationData)
)
If I understand reselect and createSelector correctly, then this should generate a new WeatherStation instance only if the underlying data, ie. the thing returned from getStationData, has changed. (Instead of generating a new instance each time anything in the state changes.)

Related

Using useContext in a pure Typescript class

I am having a pure Typescript class that does a homogenous activity as a utility class. I have created a context that can be used anywhere, and I wanted to know if we could use this context and its values in a pure Typescript class.
The class looks like this:
export class UtilityClass {
public async method1() {
...
}
public async method2() {
...
}
}
I would like to use the Context like this:
const { total, cartItems } = useContext(CartContext);
I have tried by creating a static context type like this in the class:
export class UtilityClass {
static contextType = CartContext
public async method1() {
const { total, cartItems } = useContext(CartContext);
...
}
public async method2() {
...
}
}
But this is throwing following error:
any
Property 'context' does not exist on type 'UtilityClass'.
Context is integrally tied in to the react component tree. When a component tries to access context (either via useContext in a function component, or various methods in a class component), react looks up the tree to find the nearest context provider. So the placement of your component in the tree relative to any providers is crucial for determining what value you get.
As a result, it's nonsensical to try to access context outside a component. If you're not in a component, then there's no hierarchy of components above you to access. Depending on what you're trying to do, there's a couple possibilities:
If the value you're trying to share is a global value that's not really related to the component tree, you could remove it from the components entirely. The value can be exported from a file, and imported wherever it is needed. This is easy if the value never changes, but if it does change then components will not automatically rerender. So you may need to set up some way of notifying components of the change, at which point the components set state and rerender themselves.
Another option is that you can write your utility functions so that you pass in the values that you need. The component that uses the function will be responsible for listening to context, and then when it calls the utility function it passes those values in.
const SomeComponent = () => {
const { total, cartItems } = useContext(CartContext);
useEffect(() => {
new UtilityClass().method1(total, cartItems);
}, []);
...
}
export class UtilityClass {
public async method1(total, cartItems) {
...
}
Impossible. You can access context in class that extends React.Component, And the child of Context Provider.

How do I get Redux state before the component re-renders?

Due to how data flows through React-Redux apps, data that is updated in the store is not available in a React component until that component re-renders.
My ask is, within a given component I'd like to use the updated store without re-rendering the component. I am looking to do something like this:
import React, { Component } from "react";
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import * as AActions from "../aactions";
import * as BActions from "../bactions";
import * as CActions from "../cactions";
class MyComponent extends Component<Props>{
constructor(props){
super(props);
this.func = this.func.bind(this);
};
func(){
// Add item to A
this.props.addAItem("value");
// Get id of newly added item in A
let addedA = this.props.A.find(a => a.name === "value");
// Insert new items in B with the id of A
this.props.addBItem(addedA.id, "valueB");
// etc...
}
render(){
<div onClick={() => this.func()}>click me</div>
}
}
function mapStateToProps(state, props){
return {
A: state.A,
B: state.B,
C: state.C
};
}
function mapDispatchToProps(dispatch){
return bindActionCreators({...AActions, ...BActions, ...CActions}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
This doesn't work, because props is not updated until MyComponent re-renders. I cannot call this.props.A.find(a => a.name === "value"); because props will not have the most recently-added "A" value. In my case, I can't wait for MyComponent to re-render. What are my options to access the most-recent state in MyComponent?
I've done something like this but it's absolutely horrible but it works...
func(){
if (this.props.step1){
// Add item to A
this.props.addAItem("value");
this.props.setStep2();
} else if (this.props.step2) {
// Get id of newly added item in A
let addedA = this.props.A.find(a => a.name === "value");
this.props.setStep3();
}
// etc...
}
Ok if I understand your question correctly, your func method addItemA then addItemB is dependant on the response from addItemA, addItemA would be an async operation, So in your addItemA action when you add the item you can simply return promise from your action which when resolves returns the id of newly created element and inside your component you can handle in a then block as
func(){
// Add item to A
this.props.addAItem("value").then((id) => {
// no need to find the id now
// Insert new items in B with the id of A
this.props.addBItem(id, "valueB");
});
// etc...
}
Hope it helps
Do to a lot of back and forth, and testing, I ran into many walls trying to figure this one out but eventually figured it out. Here's what you need to do.
Note: This is not considered part of the React Redux public API, and may break without notice. We do recognize that the community has use cases where this is necessary, and will try to make it possible for users to build additional functionality on top of React Redux, but our specific use of context is considered an implementation detail. If you have additional use cases that are not sufficiently covered by the current APIs, please file an issue to discuss possible API improvements. - https://react-redux.js.org/using-react-redux/accessing-store#using-reactreduxcontext-directly
Ensure your react package is 16.8.3 or later. Ensure your react-redux package is 7.1 or greater. You need these versions because of the new way (in React 16.8.3) React passes down Context to child components. We care about Context because this new React Context API is how the ReactReduxContext works.
The ReactReduxContext is the magic sauce we need in order to get the current state of the store in a component. Note the changes in our below component.
import React, { Component } from "react";
import { bindActionCreators } from "redux";
import { connect, ReactReduxContext } from "react-redux"; // CHANGED!
import * as AActions from "../aactions";
import * as BActions from "../bactions";
import * as CActions from "../cactions";
class MyComponent extends Component<Props>{
constructor(props){
super(props);
this.func = this.func.bind(this);
};
func(store){ // CHANGED!
// Add item to A
this.props.addAItem("value");
// Get id of newly added item in A
let addedA = store.getState().A.find(a => a.name === "value"); // CHANGED!
// Insert new items in B with the id of A
this.props.addBItem(addedA.id, "valueB");
// etc...
}
// CHANGED!
render(){
<ReactReduxContext.Consumer>
{({store}) => {
return <div onClick={() => this.func(store)}>click me</div>
}}
</ReactReduxContext.Consumer>
}
}
function mapStateToProps(state, props){
return {
A: state.A,
B: state.B,
C: state.C
};
}
function mapDispatchToProps(dispatch){
return bindActionCreators({...AActions, ...BActions, ...CActions}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
The magic is store.getState() which returns the updated Redux store.
I want to point out I looked extensively at redux-thunk, but this approach didn't end up giving what I needed. The main problem is the use of bindActionCreators in my component. Because I am using bindActionCreators, any action that I call expects an empty object, which gets dispatched to the Redux store.
I attempted many ways to try and return the updated Redux state in an action but since I was using bindActionCreators, I could not return the value of the Redux state from a given action. The only way to retrieve an updated Redux store state without relying on the component itself to be updated/re-rendered is to use the method I outline above.
shouldComponentUpdate should do the trick

how components get updated when data store changes

I'm new to React and still struggling in understanding in using redux with React.
For example, below is some code :
const mapStateToProps = (storeData) => ({
editing: storeData.isEditMode;
})
const mapDispatchToProps = {
saveCallback: xxx
}
const connectFunction = connect(mapStateToProps, mapDispatchToProps);
export const ProductDisplay = connectFunction(
class extends Component {
render() {
if (this.props.editing) {
...
} else {
...
}
}
}
)
and a component that uses ProductDisplay
export default class App extends Component {
render() {
return <ProductDisplay/>
}
}
Let's say storeData.isEditMode is changed by other component, so the wrapped component's props.editing is changed. As we know that the only way to trigger update process is to use setState() but how does react know that ProductDisplay component needs to be updated since there is no setState() method involved?
The connect function generates a wrapper component that subscribes to the store. When an action is dispatched, the wrapper component's callback is notified. It then runs your mapState function, and shallow-compares the result object from this time vs the result object from last time (so if you were to rewrite a redux store field with its same value, it would not trigger a re-render). If the results are different, then it passes the results to your "real" component" as props.
Dan Abramov wrote a great simplified version of connect at (connect.js) that illustrates the basic idea, although it doesn't show any of the optimization work.
update
React-Redux v6.0.0 made some major internal changes to how connected components receive their data from the store.
For more details: https://spin.atomicobject.com/2018/04/02/redux-rerendering/

Where to keep active user data on React + Redux client application

On my React + Redux client app, I need to get the active user info (fetch from myapp.com/users/me) and keep it somewhere so that I can access it from multiple components.
I guess window.activeUser = data would not be the best practice. But I could not find any resource about the best practice of doing that. What would be the best way to do what I want?
you can keep it in a separate reducer, and then import multiple parts of your state with connect() in your components.
Say if you have 2 reducers called users.js and tags.js which are combined with combineReducers when setting up your store. You would simply pull different parts by passing a function to your connect() call. So using es6 + decorators:
const mapStateToProps = state => {
return {users: state.users, tags: state.tags}
}
#connect(mapStateToProps)
export default class MyComponent extends React.Component {
and then down in your render function:
return (
<div>
<p>{this.props.users.activeUsernameOrWhatever}</p>
<p>{this.props.tags.activeTags.join('|')}</p>
</div>
);
So your different reducer states become objects on this.props.
You can use React's context, HOC or global variables to make your data available to multiple components, via context
Something like...
class ParentDataComponent extends React.Component {
// make data accessible for children
getChildContext() {
return {
data: "your-fetchet-data"
};
}
render() {
return < Child />
}
}
class Child extends React.Component {
render() {
// access data from Parent's context
return (<div> {this.context.data} </div>);
}
}
create an action creator and call it on componentWillMount of the appropriate component so it runs right before your component mounts, and in the action creator fetch the data you need and pass it to a reducer. in that reducer you can keep the data you want throughout your application. so whenever you needed the data you can retrieve it from redux state. this tutorial from official redux website covers everything you need to know. mention me if you had any questions.

Actions (playing sound) based on Redux store changes

I'm building an app that contains a store with an "offers" section of the state tree (ImmutableJS List Object). I need to take some action (play a browser sound) whenever an item is added to this list. Items can be added to this list via several different types of Redux actions.
I am trying to figure out the best way to react to the changes to a particular part of the store. I could do it in each action/reducer method, but then I would have it all over the place. I'd rather have one central place to handle the logic.
What's the best way to handle this? Should I create a generic store subscriber and has it's own logic for keeping track of the list values?
In this case your best bet is a store listener. Either a plain listener function or a redux connected React component.
Assuming a simple function to make noise:
function playSound () {
const audio = new Audio('audio_file.mp3')
audio.play()
}
You can create a store observer and listen for changes:
function createSoundObserver (store) {
let prevState = store.getState()
return store.subscribe(() => {
const nextState = store.getState()
if (prevState.messages.length < nextState.messages.length) {
playSound()
}
prevState = nextState
})
}
You can achieve the same with a React component:
import React, {Component, PropTypes} from 'react'
import {connect} from 'react-redux'
class Notifier extends Component {
static propTypes = {
messages: PropTypes.array.isRequired
}
componentDidUpdate (prevProps) {
if (this.props.messages.length > prevProps.messages.length) {
playSound()
}
}
render () { return null }
}
export default connect((state, props) => {
const {messages} = state
return {messages}
}, {})(Notifier)
As long as a Notifier is present amongst the rendered tree, it will check for changes and play the sound accordingly. The advantage of this approach is that you don't have to take extra care of unsubscribing the event if you want to stay quiet, and it seamlessly works server-side rendering.

Resources