The Complexity of dom manipulation in React + Redux - reactjs

I'm trying to simplify the workflow when using React and Redux.
For basic DOM Manipulation tasks like scrolling an element down or performing a check for something in an interval, you have to declare many functions and all of a sudden you may have 100 lines of code when there should only be a few lines of code.
The following example is with the react-redux starter kit. Using helper modules such as
createAction, handleActions
The task performed is basicly
var objDiv = document.getElementById("id");
objDiv.scrollTop = objDiv.scrollHeight;
First we have to set our action type in the module
export const SCROLL_DOWN = 'SCROLL_DOWN'
Then we have to define the action for scrolling down
export const setScrollDown = createAction(SCROLL_DOWN, (setScrollDown = true) => setScrollDown)
Then the reducer, where we set the state hasScrolledDown which in turn has to be defined in the module we're in.
export default handleActions({
[SCROLL_DOWN]: (state, { payload }) => {
return {
...state, hasScrolledDown: payload
}
}
}, {hasScrolledDown: false})
Next we have to use all of this in the View
We define the propTypes
class MessagesView extends React.Component {
static propTypes = {
setScrollDown: PropTypes.func,
hasScrolledDown: PropTypes.bool
};
And finally we use them in the componentDidMount & componentDidUpdate functions, to manipulate the dom we need to import the react-dom module, so that goes at the top of the document.
import ReactDOM from 'react-dom'
And then we're finally able to scroll down the page.
componentDidMount () {
this.props.setScrollDown(false)
}
componentDidUpdate (prevProps, prevState) {
if (!this.props.hasScrolledDown) {
ReactDOM.findDOMNode(this).scrollIntoView(false)
this.props.setScrollDown(true)
}
}
So, my question is: Am i going about this the wrong way, or is there a simple solution to handle simple dom-manipulation like this?
As you would imagine when having a large application where you need to perform basic dom-manipulation tasks like this here and there, the codebase grows quite fast.

For DOM stuff like this, like scrolling to a certain point, doing the whole Redux state/action/reducer thing is a bit of an overkill. Of course, this all comes down to personal preference, but there's no need to make things more complicated than they are.
Not knowing your entire application, I'm just making assumptions here, but imagine that your page has a button or something which sole purpose is to scroll the user back to the top of the window. This is something Redux or React shouldn't need to care about, so leveraging it to plain old javascript is a heck of a lot easier to figure about. The way to go about this would usually involve componentDidMount, add a click listener to the button, and take care of the scrolling there.
I even use this approach on tooltips and other GUI stuff that I don't need in a persistent state, because usually it doesn't make sense. Redux is great for keeping track of application state, but that doesn't mean you are forced into using it for every little thing that you don't have to be able to reproduce at any given time.

Related

ReactJS - What are the pros and cons of managing context outside of the component tree?

In a React (Native) application, consider a util file such as:
let apples = 0;
export function addApple() {
apples++;
}
export function getApples() {
return apples;
}
Is it okay to store the state of apples this way such that components can modify it like:
import { addApple } from "./myUtil";
export function AddAppleButton {
return <button onClick={addApples} />
}
And non-React code can use it like:
import { addApple } from "./myUtil";
export function addMultipleApples(numberOfApples) {
for (let i = 0; i < numberOfApples; i++) {
addApple();
}
}
What are the pros and cons of managing context this way?
Essentially, it won't work. Since apples is not managed by react state, when it changes, React will not trigger a re-render so the value will not update on screens that read from it. It may accidentally look like it's working if a re-render happens for some other reason, React will pick up the new value -- but this would be extremely fragile and inevitably lead to indeterminate states and race conditions you don't want to exist.
For what you need to do, it would make more sense to build a store container (like a class) and put this in the React state, then add methods to that store such that its values can be read and written from the outside. The store would need to inform components listening to it the value changed, via a subscription mechanism or similar. This is a fairly complex thing to set up, so it'd probably be better to adopt a library that supports reading/writing stores from outside of React. Like Zustand.

Is this a dumb idea for how to simplify redux + react?

I'm trying to refactor an app to use redux toolkit but I'm running into an infinite loop when dispatching an action to set state and I suspect its because I'm not following the conventions.
What I'm attempting to do at a high level is have a useAppHandlers hook that returns all the handlers for the entire app. Similarly, I have a useAppState hook that returns a global object with all my state. Snippet from my config file:
export const useAppState = () => {
const coingeckoApiState: AppState[typeof coingeckoApi.reducerPath] = useSelector(state => state.coingeckoApi);
const connectionState: AppState['connection'] = useSelector(state => state.connection);
const currencyState: AppState['currency'] = useSelector(state => state.currency);
return { ...coingeckoApiState, ...connectionState, ...currencyState };
};
export const useAppHandlers = () => {
const connectionHandlers = useConnectionHandlers();
const currencyHandlers = useCurrencyHandlers();
return { ...connectionHandlers, ...currencyHandlers };
};
^^Does that look problematic? I unfortunately can't share the full repo because it's private.
In all the redux examples I've come across, people import useDispatch in addition to the actions they are dispatching within each component. I really don't like how it results in so many lines of code just for imports and set up ex:
const dispatch = useDispatch() repeated ad nauseam across the repo).
This repo is a real-world example of what I'm trying to avoid:
https://github.com/Uniswap/uniswap-interface/blob/4078390a4890445d1ff0ed5196fd4cb56a44de87/src/components/NavigationTabs/index.tsx#L116
Before I give up and just follow conventions, I'd like to pinpoint if the above way I'm configuring redux is the source of the infinite loops, or if its a random bug I introduced deeper in the code.
Honestly, just don't. You will never be able to bundle-split in the future if the need for that arises when creating such a god-object. Also, adding something now means you have to touch at least one more file that is not using that something - same goes for deleting.
In addition to that, writing so "wide" selectors that select a full slice, even if your components only ever consume a part of that slice is a horrible thing for your performance - those components will always rerender if anything in that slice changes, no matter if it is important for your component.
Lastly: Just ignore your imports and let your IDE take care of it for you, probably out of the box. Your IDE can auto-import them for you. You can configure your IDE (with eslint autofix or other plugins) to automatically sort your imports alphabetically and also remove unused imports on save. I'm sure there is even a plugin that will just collapse your imports for you if you don't want to see them.
PS: as for why in react-redux you usually import useDispatch everywhere, you can read a bit on the history of that decision here: https://react-redux.js.org/api/hooks#recipe-useactions

React + Redux share actions between modules/domains

Imagine you have an application with 2 modules (split up in the ducks way).
One is the eagerly loaded Notification module, which is used to display notifications when something succeeds or fails.
The other is a Calculation which makes some calculation
- Notification
- components
- actions
- ...
- index.js
- Calculation
- components
- actions
- ...
- index.js
In a lot of architecture articles they recommend that you should then export the action creators for each module through an index.js file, that sort of functions as the public API of your module.
For example, if I wanted to expose the a success action creator of my Notification module, then I'd export it from the index.js file in that module. Now my other modules can import these action creators.
I like this idea of a public facing API in your module.
What I find troublesome with that way of working is that you then very tightly couple the module to the redux library. Because if I'd switch to a new Notification module, then this module would have to expose action creators too, which is tied to redux.
Is my concern valid? If so, can you suggest a better (but still idiomatic) solution?
What I would do in Angular is the following:
I'd expose from the Notification module a singleton service that acts as the public facing API of this module. If any other module (eg. Calculation) needed to use a feature in the Notification module they could inject the service using dependency injection and call notificationService.addNotification('message'). In that singleton service I would then call the dispatch method on my store.
The Calculation module does not need to know whether the NotificationModule uses a store or not. And I could easily switch around the Notification module, as long as a public facing singleton service still exposed the addNotification method. By inverting the dependencies, I don't need to go change every module that uses the Notification module.
Thanks for your suggestions!
What about using connect function? This way your component
Can be user without Redux at all
dispatch and other similar redux staff will be hidden behind connect
Here is example
export const MyComponent = ({ alertState, notificationsArray, SetAlert, AddNotification }) => {
return <div>
Alert state: {alertState.toString()}
<button onClick={() => SetAlert(!alertState)}>Toggle alert</button>
<div>
Notifications: {notificationsArray.map(n => `${n}, `)}
<button onClick={() => AddNotification()}>Add notification</button>
</div>
</div>
}
export default connect(state => ({ alertState: state.alert.alertState, notificationsArray: state.notifications.notificationsArray }), {...Alerts.actionCreators, ...notification.actionsCreators})(MyComponent)
Note, that inside MyComponent there is no dispatch. So you can use MyComponent without Redux by doing
// Another file
import { MyComponent } from './MyComponent.js'
export const App = () => {
return <MyComponent alertState={true} SetAlert={(alert) => console.log(alert)} notificationsArray={[ 'notification1', 'notification2' ]} AddNotification={() => {}} />
}
Or, if you want to use it as connected, do
// Some third file
import MyComponent from './MyComponent.js' // Note, that now it is default import
export const AnotherComponent = () => {
return <MyComponent />
Now notice, that I don't provide any props to MyComponent a they will be provided by connect.
You can also move call to connect to some other file. So MyComponent will be completely independent of Redux.
You're also not obligated to fully connect MyCompoent to Redux. You can partially connect it
export default connect (state => ({ alertState: state.alert.alertState }), Alerts.actionCreators)(MyComponent)
Now you should provide notifications and AddNotification when invoking MyComponent as they are not taken from Redux.
I think the idiomatic Redux way is for the Calculation module to dispatch an action, and modules interested in that action to handle the action in their reducers. Since all actions are passed to all reducers, this makes for less tight coupling between action dispatcher and action consumer. In this case, the Calculation module doesn't need to care about which components, how many components, or indeed if any components are watching for that action. (Although in most cases, I find that I do create an action producer and one consumer -- and in most cases just one consumer -- of that action, and even though they are loosely coupled, I work on both at the same time.)
I suppose in theory it is possible to create a Notification singleton that you can call from the Calculation module, that would in turn dispatch an action that is handled only by the Notification module itself. I'm not too familiar with how Angular works, but it seems that if you are calling a function exposed by Notification, that creates tight coupling between the components. If you later want to switch out that Notification component for another, would you have to see up all the binding again? And what if other components are interested in the Calculation success? Does the Calculation module have to call functions exposed from singletons in those modules too, introducing more tight coupling?
As with most things, it seems there are pros and cons with both approaches. If you buy into the Redux way of doing things, less tight coupling between components is one of the 'pros', at the expense of less flexiblity should you decide down the road that you want to switch out Redux for a different approach.
You might be thinking/assuming some things incorrectly.
If you think in a sense, Actions/reducers etc are organized and written here in a way that makes those particular modules independent. So, Notification here is independent and so is the Calculation. Code for both are inside their respective folders. Calculation module need not worry of what is happening around the world. Calculations related stuffs are done and relevant actions are dispatched or reducers are updated. Now, if some module (e.g. Notification) wants to do something when Calculation is success, it can listen out for the success dispatch in its own territory.
(Note here that we need not make any changes in the Calculation module for Notification module to work). So, both are decoupled.
What I find troublesome with that way of working is that you then very tightly couple the module to the redux library Yes that is absolutey correct but isn't that what happens when you create a project using some particular framework. You use the syntaxes and the features provided by those libraries but that makes the overall project tightly bound to that library and if you change the library, a lot of code has to be re-written as per the new library or guidelines (unless there is some intelligent compiler). But, this doesn't makes modules coupled (here in redux at least)
This means that when I want to replace my Notification module with another Notification module that doesn't use redux, I'll have to refactor my whole app to not use the dispatch function anymore to create a success. Yes you have to because underlying library has changed. I am not an expert in angular but even in your angular project, if you decide to use something else for the Notification module, I am sure you have to rewrite a lot of stuff in or around the Calculation module for things to work out.
I think what you are saying generally happens if there are very big projects written badly which led to origin of micro-services like architecture. Take some example of e-commerce website. Initially, Authentication, Search, Checkout, Payment (basically backend services) etc were written altogether and so they were tightly coupled. Later on people created micro-services out of them and each of them can communicate with one another using APIs. Now, each of the service and underlying framework can be changed without effecting other but standard APIs are there. Similarly, in frontend as well, you can have such things but it essentially means that you have separate projects altogether which need to communicate and not the modules inside the same project. But it will have same issues be in Redux or in Angular.
Edit: Have updated few points after discussion in comments:
Can you have micro-frontends
Yes, you can have micro-frontends such as Notifications in ReactJsandCalculations in AngularJs and use some public methods such as [window.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) or eventListeners but there will be pros and cons to it
Few articles which I found:
Frontend in Microservice Architecture
Micro frontends—a microservice approach to front-end web development
https://micro-frontends.org/
Is it popular right now?
No.
I think some of the reasons being:
Compared to backend, frontend is more of UI/UX and has to look consistent in look and feel wise. So, achieving that might be a bit of issue
Network latency is a big issue and a lot needs to be downloaded. Using 2 or 3 frameworks means you have to download additional data for that framework to work. E.g. both React and Angular library etc. If you see, a lot work goes on reducing the download size which will increase upon increasing the number of frameworks
Most of the websites don't have many pages. Say at max 10-12 different pages and so, creating all of them in one framework is easy and cheap. However, if the project gets big, then big companies do divide. There are very big projects where a.domain.com is in reactJs, b.domain.com is in angular. But that generally happens when projects are big and completely separated from one another.
So, yes you can have it but it depends a lot on factors including but not limited to resources, price, availability etc
If you want to build the micro-frontend, you can use
window.postMessage
EventListeners
Isolating micro-apps into IFrames using libraries and window.postMessage APIs to coordinate. IFrames share APIs exposed by their parent window
Event Emitters (a very good library for the same is - https://github.com/chrisdavies/eev)
Using html5 storage and listening to them for the changes (on in general anything which lets us to play in or around dom/window because that will be the API layer to help us communicate between different modules)
Hope, I am able to clarify if for you. Revert for any doubts/confusion.
To decouple a state-module from the Redux dispatch/action paradigm, you can expose a "public API" via hooks:
For example, suppose your module has an action:
// notifications/actions.js
const createNotification = (data) => ({
type: 'CREATE_NOTIFICATION',
data
});
// ...
In your module, define a hook that returns a function that dispatches the action:
// notifications/hooks.js
import { useDispatch } from 'react-redux';
import { createNotification } from './actions';
function useCreateNotification() {
const dispatch = useDispatch();
return (data) => dispatch(createNotification(data))
}
// ...
Now your component doesn't have to know about dispatch/actions. Just import and use the hooks.
// components/MyComponent.js
import React from 'react';
import { useCreateNotification } from './notifications/hooks'
function MyComponent() {
const createNotification = useCreateNotification();
const handleClick = () => createNotification('foo');
return (
<button onClick={handleClick}>Create Notification</button>
);
}
If you need the public API to expose plain (non-hook) functions, you can do this via a higher-order-function that
takes dispatch and returns a set of functions. For the sake of this example, these functions will be termed "endpoints".
// endpoints.js
import * as actions from './actions';
const createEndpoints = (dispatch) => {
const createNotification = (data) => {
dispatch(actions.createNotification(data))
}
// ...
return {
createNotification,
// ...
}
}
Call the higher-order-function by giving it dispatch:
// store.js
import { createStore } from 'redux';
import rootReducer from './reducer';
import { createEndpoints } from './notifications/endpoints';
export const store = createStore(rootReducer, {});
export const {
createNotification,
// ...
} = createEndpoints(store.dispatch);
Now your UI doesn't have to know about dispatch, actions, or hooks; just call the plain functions as such:
// MyComponent.js
import { createNotification } from './store'
function MyComponent() {
const handleClick = () => createNotification('foo');
return (
<button onClick={handleClick}>Create Notification</button>
);
}
With this approach, you are largely decoupled from a redux implementation. You will still rely on having a redux "dispatch" in order to use the module, but now you are coupled at one point (createEndpoints) instead of many points throughout your components.

Sharing data (an array) between two screens in tabs view using react-navigation in react native

I am using react-navigation without redux. so i have two tabs each with their own stack navigator, having one screen each. so i need and array of locations in both screens. currently i am doing this in both screens:
state = { locations: [] };
componentDidMount() {
this.getAllLocations();
}
async getAllLocations() {
let locations = await this.getMoviesFromApi();
this.setState({ locations });
}
i just want to have this array at one location and both components should share this single source of truth. so changes made by either screen is reflected in the other screen. Is this possible without redux?
RN 0.59 has opened great possibilities with its release. One of them are react hooks, which is available in the latest version... in the future react hooks will be used everywhere. Trust me. So, a while back I looked for the possibilities of having a global state using react hooks and found the reactn library. It uses react native hooks, and even you can use global state in CLASS components. which opens a new door for theming and sharing data. Now my app supports light/dark mode, dynamic font size, Languages, and early implementation of "portals" using only this library.
The best part about it is that you can use it like state. There is no need of provider, or redux stuff (although it provides it). It can be integrated with react navigation (it requires modifying some source code, at most, adding an "n" to react, and reference the global variable). Is awesome and I love it.
I have been thinking in doing an article on medium about this, because the lib is not that popular in RN community, but hope that you will give it a chance the library is only 22KB, less than one full component.
As an alternative, you could think about writing your own library using hooks. But it's gonna be hard. Try it, there is no going back
It is possible if you have a singleton object :
export default class SharedData {
constructor(){
if(SharedData.instance){
return SharedData.instance;
}
this.state = {locations:[]};
this.listners =[];
SharedData.instance = this;
return SharedData.instance;
}
setLocations(locations){
this.state.locations = locations;
this.listners.forEach(listner=>listner(this.state.locations));
}
getLocations(){
return this.state.locations;
}
addListner(listner){
this.listners.push(listner);
return listner;
}
removeListner(listner){
let index = this.listners.indexOf(listner);
if(index > -1){
this.listners.splice(index,1);
}
}
}
and then in every tab where you want to access shared locations state:
// get an instance of SharedData
this.sharedData = new SharedData();
// subscribe to locations changes
this.listner = sharedData.addListner((locations)=>{
this.setState({locations});
});
// set locations
this.sharedData.setLocations([]);
// unregister when destroying the component
this.sharedData.removeListner(this.listner);
I guess in order to achieve your goal, you're going to need some sort of a mechanism for storing 'global data', and if you don like Redux because it requires a lot of setup to achieve this simple task of sharing global data, then you chould you unstated ... which is alot simple to setup

Passing more parameters to a pure render function in React

Lately I've been trying to write my React components as "Pure Functions" and I've noticed that sometimes I want to have something which feels a lot like state. I was thinking about passing my state as a second parameter to my component. I can achieve this by calling my component as a normal function with two parameters, props and state.
For example:
// abstracted to it's own module
const useState = (Component, state = {}) => {
return class extends React.Component {
state = createState(this, state); // will traverse and update the state
render() {
const { props, state } = this;
return Component(props, state); // <-- call the Component directly
}
};
};
const Component = (props, { index, increase }) => (
<div onClick={increase} {...props}>
Click me to increase: {index}
</div>
);
const componentState = {
index: 0,
increase: (event, state) => ({ ...state, index: state.index + 1 })
};
const StatefullComponent = useState(Component, componentState);
<StatefullComponent style={{ color: "purple" }} />;
I have a CodeSandbox example:
My questions are:
Will this pattern harm performance?
I'm no longer extending the props with state values, this might be a good thing
I am messing with the way components are rendered by default, this might be a bad thing
Will this Pattern break things like shouldComponentUpdate? (I have a sinking feeling this is modelling the old context api)
How worried should I be that future react updates will break this code?
Is there a more "Reacty" way of using State in a Pure function without resorting to libraries like Redux?
Am I trying to solve something which should not be solved?
Note: I'm using state in this example, but it could also be a theme, authorisation rules or other things you might want passed into your component.
EDIT 19-03-2018: I have noticed that people seem to be confused about what I'm asking. I'm not looking for a new framework or a conversation about "why do you want to separate your concerns?". I am quite sure this pattern will clean up my code and make it more testable and "cleaner" in general. I really want to know if the React framework will in any way hinder this pattern.
At first glanced when I checked your code I had a question:
"Why do you make it so complicated? When you can simply make it with a class declaration".
But later when I have splitted your code I found it really worth to do that.
Question 1: Doesn't really make a difference, it is the way how HOC does the composition.
I'm no longer extending the props with state values, this might be a good thing
Why/When might it be a good thing?
I am messing with the way components are rendered by default, this might be a bad thing
I don't see that you break or mess the rendering by default, I think the HOC pattern promotes the same philosophy, the difference you separate state from props.
Question 2: If a developer decide to use a stateless component then he/she should realize all “lifecycle methods” or references ref will be not available.
Your pattern make stateless component as “statefull” but in stateless declaration - amazing 😋.
Like in JSX you write in JS an "HTML" and inside it JS code with another "HTML":
<ul>
{list.map(text => <li>text</li>)} // I know there should be used key
</ul>
Mr. Baudin pattern (state-full like stateless):
import React from 'react'
import {useState} from './lib'
const state = {
index: 0,
increase: (event, state) => ({index: state.index + 1})
}
const Component = (props, state) => (
<div onClick={state.increase} {...props}>
Click me to increase: {state.index}
</div>
)
export default useState(Component, state)
Question 3: It depends what break changes will be in coming versions.
Question 4: Well... I don't think the offered pattern (implemented library) can be considered as application state management but it can be used within any state management like Redux or Mobx because it deals with internal component state.
Question 5: No, I don't think. Your solution makes code less and clean. Functional components are good for very simple or representational components and now it can be extended with state.
While this question has been open I've done some painful research on the subject and I'd like to share this research with you.
Question 1: Performance; Calling your components as functions or even as constructor functions doesn't really make a difference. You simply get your component instead of a type.
// the component
const MyComponent = () => (<div>This is my page</div>);
console.log(MyComponent());
console.log(new MyComponent());
console.log(<MyComponent />);
console.log(React.createElement(MyComponent));
Pen (Don't forget to inspect the developer tools!)
What I've noticed is that when you call a component directly you lose a little information, for example, when I use JSX the type information is preserved:
React.createElement(MyComponent).type === MyComponent // <- true
MyComponent() // <- Now way to find out what constructed this...
This doesn't seem like a big deal because the MyComponent() is seen as a normal div so it should render correctly; but I can imagine that React might do some lookup on the type of the component and calling your function like this that might interfere with the performance.
Haven't found anything in the documentation nor in the source code to suggest that this is the case, so I see no reason to worry about performance at this point.
Question 2: Does this break shouldComponentUpdate; the answer is "maybe not", but not because I need to write a class as was suggested. The problem is that React does a shallow compare on the props when you use a PureComponent and with pure functions just expects that with the same props you get the same result. In my case, because of the second parameter it might think the component doesn't need to update but actually it should. Because of some magic in my implementation this seems to work for child components of a root component wrapped with the useState function.
This is as I expected the same problem as with the original implementation of the context api. And as such I should be able to solve it using some reactive techniques.
Question 3: Seeing how "just calling a component as a function" seems to be the entire idea behind react and seeing how it results in almost exactly the same component without the original type information I see no reason why this should break in the future.
Question 4/5: No, there is no more "Reacty" way of really solving this problem. There is how ever a more functional way. I could use a state monad and lift the entire thing up; but that would envolve a lot of work and I really can't see the benefit of doing that. Passing state as a second parameter seems, at least for now, as something which might be strange but viable and actually feasable.
Question 5: When I started looking around I didn't find a lot os answers to these questions, but now that I've really dug myself in I can see a few other libraries doing the same thing. For example: recompose which calls itself "lodash for react". They seem to use this pattern of wrapping your component in a function and returning a class a lot. (Their withState implementation).
Extra information: My conclusion is that this pattern (because it's nothing more than a pattern) is valid and does not break any fundamental rules of React. Just to give a little bit of extra information Bernardo Ferreira Bastos Braga wrote that I needed to use a class to do it "the React way". I fail to see how wrapping your function and returning a class with state is anything other than "using a class".
I do however realise that wrapping a function increases complexity, but not by much; function calls are really optimised and because you write for maintainability and optimise later.
One of my biggest fears is that when the software gets more and more complocated and we get more cross-cutting concerns to deal with, it will get harder and harder to handle every concern as a parameter. In this case it might be good to use a destructuring pattern to get the concerns which you need from a "concerns" obejct passed as the second parameter.
One last thing about this pattern. I've done a small test (Just selenium rendering a page a 100 times) and this pattern, on a small scale, is faster than using Redux. The bigger your redux state gets and the more components you connect the faster this pattern becomes. The down side is that you are now doing a bit of manual state management, this comes with a real cost in complexity. Just remember to weigh all options.
A few examples of why this state component
Applications which interact with users, require that you try to keep track of the interactions they have. These interactions can be modeled in different ways but I really like a stateful approach. This means that you 'thread' state through your application. Now in react you have a few ways of creating components. The three I want o mention are:
create a class and extend from Component
create a class and extend from PureComponent
create a stateless function
I really like the last option but, to be honest, it's a pain keeping your code performant. There are a lot of articles our there explaining how lambda expression will create a new function every time your component is called, breaking the shallow compare of the props done by PureComponent.
To counteract this I use a pattern where I wrap my stateless component in a HoC where I pass my component and my state object. This HoC does some magic and passes the state as a second parameter to the stateless function, ensuring that when the props are tested by the compare of the PureComponent it should work.
Now to make the wrapper even better I memoize the lambdas so that only a single reference to that function exists so that even if you were to test the function by reference it should still be OK.
The code I use for this is:
return Object.entries(_state).reduce(
(acc, entry) => {
const [key, value] = entry;
if (value instanceof Function) {
acc[key] = _.memoize(item => (...args) => {
const newState = value.apply(null, [...args, root.state, root.props]);
root.setState(newState);
});
} else {
acc[key] = value;
}
return acc;
},
{}
);
};
As you can see I memoize the function and call it proxying the arguments and passing in the state and the props. This works as long as you can call these functions with a unique object like so:
const MyComponent = useState((props, { items, setTitle }) => {
return (
<div>
{items.map(item => (
<Component key={item.id} item={item} changeItem={setTitle(item)} />
))}
</div>
);
}, state);
1- Will this pattern harm performance?
Performance is usually not a black/white, rather it is better / worse in different scenarios. Since React already has a standard way of doing this, it it plausible that you'll be missing out on internal optimizations.
2-Will this Pattern break things like shouldComponentUpdate? (I have a sinking feeling this is modelling the old context api)
Yes, you should be using the class declaration if you need to write shouldComponentUpdate functions
3- How worried should I be that future react updates will break this code?
I think is fair to say that you should, since there are obvious and documented ways of doing the same using classes.
4 - Is there a more "Reacty" way of using State in a Pure function without resorting to libraries like Redux?
you could have a container component that has state and pass down callback functions to update the state
5- Am I trying to solve something which should not be solved?
yes, since there is already a mainstream and documented way of archieving what you need using the Component class. You should probably resort to functional components only for very simple or presentational components

Resources