Creating refs outside of component. Is that a bad practice? - javascript

I am working on a library which requires exporting a couple of functions for users to call upon. Those functions need access to component ref in order to add/remove classNames and auto scroll etc.
I was able to get it to work by moving my ref (created by React.createRef) outside of the component itself (NOT talking about defining it outside of the constructor but inside the component)
Here's how my code looks like (used a class component instead of functional as the hook useRef obviously can't be used outside)
import React, { PureComponent, createRef } from "react";
import { typingEffect } from "../redux/actions/dispatch";
import { containerRef } from "./Container";
let typingRef = createRef();
export async function displayTypingEffect() {
await typingEffect();
typingRef.current.className += " rcb-is-typing";
containerRef.current.scrollTop = containerRef.current.scrollHeight + 700;
}
export function hideTypingEffect() {
typingRef.current.className = "rcb-typing-container";
containerRef.current.scrollTop = containerRef.current.scrollHeight + 700;
}
export default class Typing extends PureComponent {
render() {
return (
<div ref={typingRef}>
rest of the component code which is unnecessary for this question
</div>
)
}
I am just wondering if there's a possibility of any unforeseen issues or bugs if I follow this pattern.
Thank you.

This makes the typingRef
a global variable (inside the module), and
be created outside of any React life cycles
typingRef will be the same object for every instance of the Typing component, i.e. if two components are created from the Typing class, both will write to the same typingRef. Your API will provide access some DOM element, but you can not be sure which one it currently is.
typingRef is created as soon as the file is imported, before React even starts, and will live for the life time of the Javascript code, not the life time of any React component.
I think (not 100% sure) any DOM elements referenced by typingRef will be kept (at least) until typingRef gets overwritten (or the Javascript execution is ended). So if a Typing component gets unmounted, the DOM element (and everything that's connected to it) is still kept in memory. So your API will provide access to "useless" DOM elements.

Related

How to pass the set[State] function to a non-descendent trigger component

Here is the diagram. ChildComponentB has a state - stateX. In ChildComponentA, once the event occurs, it will change the stateX in ChildComponentB.
If the ChildComponentA is the child component of ChildComponentB, then it's easy, just pass the setStateX as a prop to ChildComponentA. But in this case, it's not.
The real scenario is the following. I have a canvas component, there are some static Rectangles already there, once there are mouse move over the line of the Rectangles, I'd like to add the indicator lines to another child component of the canvas component.
Hence, the rectComponent is not the descendent of the distanceIndicatorsComponent. So I can't pass the setLines to RectComponent.
What's your approach to do that?
If I use useContext approach, will it work?
Thank you, #KonradLinkowski to provide your solution. Here is his code. However, useContext is still lifing the state up to ParentComponent.
import React, { useContext, createContext, useState } from "react";
const Context = createContext();
function ChildComponentA(props) {
const { setStateX } = useContext(Context);
return (
<div>
componentA button:{" "}
<button onClick={() => setStateX((i) => i + 1)}>add</button>
</div>
);
}
function ChildComponentB(props) {
const { stateX } = useContext(Context);
return <div> stateX is {stateX} </div>;
}
export default function ParentComponent(props) {
const [stateX, setStateX] = useState(0);
return (
<>
<Context.Provider value={{ stateX, setStateX }}>
<ChildComponentA> </ChildComponentA>
<ChildComponentB> </ChildComponentB>
</Context.Provider>
</>
);
}
Regarding the reusbility of the ComponentB i.e. distanceIndicatorsComponent in this scenario, it includes the JSX and the states plus the interface in which there are logic to change the states. The are all parts which should be reusable in the furture.
From OOP perspective, the lines (state) belongs to DistanceIndicatorsComponent, and the how to change the lines (Add Line in this case) should be also reusable logic which belongs to distanceIndicatorsComponent.
However, from React perspective, to lift the setLines (this is the interface triggered under some event) is not "good enough" from OOP perspective. To lift the state - lines and state management function - setLines up to CanvasComponent is a "not good enough" in terms of the encapsulation. Put a wrap component on top of ComponentB is the same thing, the setLines still can't be passed to FrameComponent unless FrameComponent is a child-component of the wrap component.
It's very common to see there is a very heavy component holding all the state and the events at the top. It makes me feel that's a bad smell of the code. The reusability of the component should be based on a set of components, in this set of components, there is one uncontrolled component at the top, and underneath of this uncontrolled component are controlled components. This set of components is a external reusability unit.
Here, in this diagram, there should be more than one reusable unit rather than one. If lift the state up to CanvasComponent, it makes all the components underneath are un-reusable. In some extents, you still can re-use the JSX of this component, but I'd say, in terms of reusablity, it should invovle as many reusable logic as possible.
I might be wrong, please correct me. And thank you for sharing your valuable comments.
Requirements
First let us sum up the requirements.
Rect Component and Distance Indicators have not much to do with each other. Making them aware of each other or creating a dependency between them would be not desired in a good OOP design.
The interaction between both is very specific. Establishing a mechanism or a data structure just for this special sort of interaction would add an overhead to all components that don't need this sort of interaction.
General Concepts
So you must use a mechanism that is so generic that it does not add any sort of coupling. You need to establish something between these two components, which only these two components know and which for all the rest of your program is nonsense. What mechanisms serve for such a purpose?
Function pointers
Lambda functions
Events
Function pointers and lambda functions are complicated constructs. Not everybody prefers to use them. Now you see why events are so popular. They address a common requirement of connecting two components without revealing any of the details of them to anybody.
I personally recommend you to use lambda functions in this situation. Because this is one strength of JavaScript. Search in google for callback or asynchronous lambda function. This often adds the least overhead to existing code. Because a lambda functions has an important property:
With lambda functions you can do things very locally. Doing things locally is an important design principle. You don't need to define extra methods or functions or classes. You can just create them wherever you are, return them, pass them freely around to where you actually need them and store them there. You can store them even without knowing what is behind them.
I think, this is your answer. The only thing you need is a mechanism to pass lambda functions and to store your lambda functions. But this is on a very generic level and therefore adds no coupling.
With events you are on similar path. The event mechanism is already there. But therefore you already have a good answer.
Example with pure JavaScript
When applying this to JavaScript we can imagine that function pointers could be compared to function expressions in JavaScript. And lambda functions can be compared to arrow functions in JavaScript. (Note: Arrow functions also provide "closures", which is required in this case, see How do JavaScript closures work?).
A simple example illustrates this:
class DistanceIndicator {
constructor(height, width) {
this.height = height;
this.width = width;
}
resize(height){
this.height = height;
}
incorrect_resizer(height){
return this.resize;
}
resizer(){
return (height) => this.resize(height);
}
resizer_with_less_overhead(){
return (height) => this.height = height;
}
}
p = new DistanceIndicator();
p.resize(19);
// If you want to use this, you have to store p. You may see
// this as not so nice, because, you are not interested in what
// actually p is. And you don't want to expose the information
// that it has a method resize. You want to have the freedom
// of changing such details without the need of changing all
// the code where something happens with Rectangles.
console.log(p.height);
resizer = p.incorrect_resizer()
//resizer(18);
// In this example, resizer is a function pointer. It would be
// nice to store it and be able to call it whenever we want to
// inform Rectangle about something interesting. But it does not
// work because the resize method cannot be isolated from the
// class. The "this" is not there.
console.log(p.height);
resizer = p.resizer();
resizer(17);
// That works. Lambda functions do the job. They are able to
// include the "this" object.
console.log(p.height);
resizer = p.resizer_with_less_overhead();
resizer(16);
console.log(p.height);
// As you have now a resizer, you can store it wherever you want.
// You can call it without knowing what is behind it.
The idea in the example is that you can store the resizers wherever you want without knowing what they are. You shouldn't name them resizer, but give them a generic name like size_notification.
Example for React
The React concept for contexts is a typical candidate for data exchange between components. But the principle of React is a pure unidirectional data flow (top-down). This is also true for the context, which means, we cannot use a context for what we want.
React does not provide support for the implementation of the proposed idea. React is only responsible for the pure construction of the HTML page and a comfortable and performant rendering. It is not responsible for the "business" logic of our HTML page. This is done in full JavaScript. That makes sense because you want be able to develop complex web applications. Therefore you need all your favourite programming concepts. A real application does not follow the design principle of React. React is only a presentation layer. Most people like OOP progamming.
So when implementing something with React we must keep in mind that React is just a library for JavaScript. The full power of JavaScript is always available and should be used for our web application.
After realizing this, the problem becomes simple. See this code:
import React from 'react';
let sizeNotificator = (newValue) => {console.log(newValue)};
function Rect(props) {
return <button onClick={() => sizeNotificator("12")}>resize to 12</button>;
}
class DistanceIndicator extends React.Component {
state = {
size: "0",
};
setSize(newValue) {
this.setState({
size : newValue
});
};
componentDidMount(){
sizeNotificator = ((newValue) => {this.setSize(newValue);})
}
render() {
return <p>Current size: { this.state.size}</p>;
}
}
class App extends React.Component {
render() {
return(<div>
<DistanceIndicator/>
<Rect/>
</div>);
}
}
export default App;
With this code the requirement is fulfilled that none of the DistanceIndicator implementation details are revealed to the outside of DistanceIndicator.
Obviously this example code only works if there is not more than one DistanceIndicator. To solve this is a different topic with probably not only one good solution.
If keeping the shared state in the ParentComponent is the problem, you can extract the Context.Provider to a separate component and pass components as it's children, those children can access the context value via useContext hook.
function ParentContextProvider({ children }) {
const [stateX, setStateX] = useState(0);
return (
<Context.Provider value={{ stateX, setStateX }}>
{children}
</Context.Provider>
);
}
export default function ParentComponent(props) {
return (
<ParentContextProvider>
<ChildComponentA />
<ChildComponentB />
</ParentContextProvider>
);
}
Now you can add any new state/setState to the ParentContextProvider and can pass it to it's children
Have you looked at Redux stores? You could have a variable like "showLine" or "originX"/"originY", then have one child dispatch changes, and the other child useSelector for the values?
Do you know if Redux works for your use case?
I prefer to use a simple events pattern for this type of scenario. Eg using a component such as js-event-bus.
CHILD COMPONENT A
props.eventBus.emit('MouseOverRectangle', null, new MyEvent(23));
CHILD COMPONENT B
useEffect(() => {
startup();
return () => cleanup();
}, []);
function startup() {
props.eventBus.on('MouseOverRectangle', handleEvent);
}
function cleanup() {
props.eventBus.detach('MouseOverRectangle', handleEvent);
}
function handleEvent(e: MyEvent) {
// Update state of component B here
}
RESULTS
This tends to result in quite clean encapsulation and also simple code. Eg any React conponent can communicate with any other, without needing to reveal internal details.

React component doesn't show correct render count [duplicate]

I am new to react and I am trying to develop a simple web app with it but I get an error.
My Constructor is called twice when I load a class component can you help?
Home.js
import React from 'react'
import Land from "../Land";
function Home() {
return (
<div>
<h1>Home!</h1>
<Land/>
</div>
)
}
export default Home
Partial Land.js
import React, { Component } from 'react'
import Login from "./Login";
class Land extends Component {
constructor(props) {
super(props)
this.state = {
}
console.log("LAND")
}
the log LAND is hit twice.
In some of the components I wish to make an API call that hits a DB but I only want to hit it once.
In many instances using componentDidMount is not convenient because props only appear after componentDidMount therefor id like to do the call in render(I will not be using setState, that would cause a reload of render).
Thanks in advance
You are using <StrictMode/> and it's development mode
While in <StrictMode/>, react will detect unexpected side effects which will call lifecycle functions more than once duration the development mode, will not be trigger twice in production.
From docs:
why is called twice
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
will not call twice
Note:
This only applies to development mode. Lifecycles will not be double-invoked in production mode.
I didn't quite get what are you trying to say in the comment, but you totally can call function that fetches data within componentDidMount hook
Here's an example:
https://codesandbox.io/s/react-calls-constructor-twice-q5gzs

Why Is React Context.Provider Necessary (Or useful)?

The reason React has contexts is to allow for multiple sibling components to share a piece of state-data. It is the go-to method for allowing two unrelated components to read/write in shared variables. The reason it is necessary is that React has no way to easily source a data value to multiple screens without actually passing that data between screens. Instead, it allows each screen access to the data when it needs it.
So... The implementation requires that a component be created, called a Context.Provider component, and then you have to wrap the components who need access to the shared data inside the Context.Provider. But why? Why on earth is that a requirement? Contexts are designed sharing data between components who aren't hierarchally related, and were required to put the components within a heirarchy to do so?
It would be 100 times more straight forward and just as effective to simply drop the requirement of using a Context.Provider, simple have the useContext function give access to a set variable by default:
// In ctx.js
import React from 'react';
export default CTX = React.createContext({val: "value"});
// In compA.js
import CTX from './ctx.js';
import {useContext} from 'react';
function A(props) {
var [context, setContext] = useContext(CTX);
console.log(context); //Logs {val: 'value'};
setContext({val: "newValue"});
}
Then later on, assuming component B renders after A:
import CTX from './ctx.js';
import {useContext} from 'react';
function B(props) {
var [context, setContext] = useContext(CTX);
console.log(context); //Logs {val: 'newValue'};
}
The above usage, if it actually worked, solves the task of "sharing data between unrelated components", and is much much simpler than requiring an entire new component be defined in the context file. This solution is better because:
1. No required restructuring of the application. You don't need to wrap components in a provider.
2. Any Components can just ask for any shared state easily, and they can set the shared state easily.
3. Easier to understand with much less code involved (One line of code for import and one line to initiate the context).
4. Doesn't sacrifice anything. This method allows for easy sharing of state between components, which is the entire reason of contexts in the first place.
Am I crazy? Is there a legitamate reason that we'd absolutely need to wrap our components up in a special component to share data?.. Why can't the shared state just exist independently? Its like they chose a bad solution... Why make every developer wrap there components in another component before using shared state, why not just let the developer use the damned shared state when they need to use it instead of jumping through a hoop? Someone please educate me.
Edit: One answer said that with my described method we wouldn't be able to access multiple contexts with a single component. That is false. It is actually easier with my described method:
// In context.js
export const CTX = React.createContext({val: "val"});
export const CTX2 = React.createContext({val2: "val2"});
// In app.js
function App(props) {
const [state, setState] = useContext(CTX);
const [state2, setState2] = userContext(CTX2);
return (<></>);
}
Easy. No need for Context.Provider. This is multiple contexts being used in one component, requiring just two calls to useContext versus wrapping your entire application in two nested contexts, which is what is what you have to do with current Context.Provider method...
Mate, answer is simple. React component only re-renders when it's props or state changes. Without Context.Provider component react will never understand when to re-render child components, thus you will have stale, render-blocked components.
The purpose for having a Context Provider wrap around children is to keep track of state and props, read on how state and props between parents and children affect each other. If there was no way for the Context Provider to keep track of its children, how would the components that use the Context be able to update(Changing parent state affects children, so there may be rerendering).
It's also important to understand React's philosophy and it's focus on components, it is a component-based library after all.
Important thing to remember:
Parent state change will affect children, so if state changes in parent, children components will be reevaluated and depending on how your components, state, and data are optimized (memo, callback, etc.) a rerender may occur, thus updating those children components as well.
Contexts Are Made To Handle All Use Cases
I've since spent more time using Contexts in my applications and have come to realize that Context.Provider is quite useful in a variety of situations. My initial complaint has merit in that often times when using Context we are simply wanting a variant of state that can be shared between components. In this common use case, Context.Provider does indeed requires us to write a bit of unnecessary boilerplate code and requires us to wrap elements in the provider so that they have access to the context.
However any time our shared state becomes a little more complicated having a dedicated Context.Provider component can make our lives a lot easier. Here is a use case to consider
Shared Data From External Sources (Post, Get)
Contexts may allow us to store any code related to the initialization of the shared state within the context itself, resulting in more easily readable and maintainable code. For example, lets say we have some user text posts on our server that are displayed by multiple components within our application, and we would also like for our users to be able to add new posts. All of this can be handled quite neatly within the Context.Provider:
import React, {useContext, useEffect, useState} from 'react';
export const PostsContext = React.createContext([]);
export default PostsContextProvider({children}) {
const [posts, setPosts] = useState([]);
function fetchPosts() {
// Here we will fetch the posts from our API, and then set the state
// stored within the Context.Provider equal to the fetched posts.
fetch('https://www.fakewebsite.com/api/posts/get', {
method: 'GET',
headers: {'Content-Type': 'application/json'}
}).then((response)=>{
// Convert response to json
return response.json();
}).then((json)=>{
// json here is the posts we fetched from the server, so we set the state
// equal to this value. This will update the state within all components
// that are using the context.
setPosts(json.posts);
})
}
useEffect(function(){
// This function will run a single time when the application is started
fetchPosts();
},[])
function addNewPost(post) {
// This is the function that will be used by the components.
// First, we will send the new post to the server so that it can store it.
fetch('https://www.fakewebsite.com/api/posts/post', {
method: "POST",
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({post: post})
}).then((response)=>{
if(response.ok) {
// The server has updated its database with our new post.
// Now we just need to fetch the posts from the server again to get the updated data.
fetchPosts();
}
})
}
return (
<PostsContext.Provider
value={[posts, addNewPost]}
>
{children}
<PostsContext.Provider />
)
}
Notice that the value prop we are passing does not actually pass the state setter function directly. Instead, we pass the addNewPost function. So, when a component calls useContext(PostsContext) they will get the addNewPost function. This is extremely useful, it will allow our components to easily add a single post to the shared state, while also handling the server update! Very cool. With the solution I originally proposed, this would be impossible, because we would only ever get a simple state setting function from our useContext call.
Now, we must wrap our application in the provider to make it available to all components:
// App.js
import React from 'react';
import PostsContextProvider from './posts_context';
import MyComponent from './my_component';
import MyOtherComponent from './my_other_component';
export default function App() {
return (
<PostsContextProvider>
<MyComponent/>
<MyOtherComponent/>
</PostsContextProvider>
)
}
At this point, MyComponent and MyOtherComponent now have access to the context using the useContext hook. It is now extremely simple for the components to access the posts data and also update it with a new post.
import React, {useContext} from 'react';
import {PostContext} from './posts_context';
export default function MyComponent() {
const [posts, addPost] = useContext(PostsContext); // 'posts' will always be up to date with the latest data thanks to the context.
...
}
import React, {useContext} from 'react';
import {PostContext} from './posts_context';
export default function MyOtherComponent() {
const [posts, addPost] = useContext(PostsContext);
...
function handleAddPost(title, text) {
// Now when this component wants to add a new post,
// we just use the `addPost` function from the context.
addPost({title, text});
}
...
}
The beauty of this is that all the code related to the fetching and posting of data can be neatly contained within the provider, separated from the UI code. Each component has easy access to the posts data, and when either component adds a new post the other component will be updated with the new data.
Final Thoughts
This is just scratching the surface of the usefulness of Context.Provider. It's easy to imagine using a Context.Provider to handle persistent data storage using a method very similar to the above, replacing the fetch calls with function that store/fetch persistent data. Or even, some combination of persistent data and fetched data.
Upon revisiting my original question, it actually made me laugh. I was sort of right, there should perhaps be a way to handle simple shared state between components that does not require wrapping components in a provider and does not require any provider code at all. However, providers are just so dang useful in any kind of state management within an application that it is actually probably a good thing to force people to use them for simple shared state, because then they will have to learn about this wonderful tool.

React and Redux: Are there any performance issues/possible side effects with passing a parent component as a prop to its children

I'm reviewing some code in a React-Redux codebase, and there are quite a few cases where a parent smart component is being passed as a prop to a child component:
import React from 'react';
import Child from '../components/Child';
export default class Parent extends React.Component {
constructor(props) {
super(props);
}
//...
render() {
return <Child parent={this}/>;
}
}
At initial glance, it appears that the intention here is to expose the props/state/methods of the parent to the child component. This sort of goes against many of the design patterns I've used in the past with React, but I'm not sure if it's something that is worth bringing up in a code review (it's already deployed to QA). It technically works (the child is able to call this.props.parent[method], etc) and significantly reduces the lines of code otherwise required if you pass individual handlers/slices of props/(local)state to the child. I know there are downsides (in one case, the parent property shared the same name as a reducer, so in the child component, it is unclear if this.props['reducerName'] is referring to a React Component or a mapped slice of state), but I can't be sure that the downsides are anything more than surface level.
Does something like this run the risk of unnecessary rerenders/diff checks in the child component? Does React ever need to recursively serialize components, and thus incur a lot of overhead because of circular references? Anything I can point out besides I don't think it looks right?
There are a few things I can think of why this might not be a good Idea:
This creates a very tight coupling between the parent and the component. Further, we should try to provide the minimum amount of data to the abstract modules. We call it Principle of least privilege. Here, a lot of information is being passed to the child component which will not be used and can even be abused using a lot of ways.
One case where it can be very bad idea is when the child component changes something on the date object: eg :
render() {
return (
<React.Fragment>
<ChildOne parent={this}/>;
<ChildTwo parent={this}/>;
</React.Fragment>
)
}
Now the ChildOne component can do something like :
this.props.parent.clickHandler = this.anotherHandler
This will break ChildTwo functionality.

How to use custom functions in a React class after it was rendered?

Let's say that I create additional functions in my React components, something like this:
class Cart extends React.Component {
render() {
return <div id="cart">My Cart Items</div>
}
getItem(id) {
// magically return the item
}
addItem(id, name, type, price){
// you get the idea
}
}
Now, what is the best way to access getItem function from outside the class? For example, something that can be used as window.Cart.getItem. It seems to me that these functions are part of the prototype (non-initialized) of nodeName property of what ReactDOM.render returns.
What is the correct way for this? Thanks.
That's not really how react is supposed to be used.
What you are trying to do looks like MVC, and react is a completely different thing.
a React component like your <Cart> receives props from its parent (could be the root reactDOM.render() or another react component)
a React component may have internal and private method to retrieve additional data from elsewhere (but NOT from other react components)
with these inputs (and only these inputs), the component knows what and how to render itself and possibly also children components to the DOM
the stuff the component (or its children components) render may include interaction handlers (e.g. remove from cart button like `
these interactions could fire an internal method inside the component
and the internal method could call a method in a parent component, if it was passed down as a prop.
or call some remote function to remove item from cart on server side.
In react terms, the <Cart> component is not the owner of the cart contents. It gets them as props, or retrieves them from somewhere else.
React really wants you to ONLY update the cart by passing a new set of props to the component to render.
You could try to shortcut react design by trying to expose any methods from the component to the outside world, but this goes against react design principles.
React has a one way data flow: a component gets props, and renders.
For reference, I can recommend this page on react principles.
You can make a reference to the instance :)
<Cart ref={function(instance){ window.CartInstance = instance }} />
window.CartInstance.getItem(foo);
ES6
<Cart ref={(instance)=>window.CartInstance = instance} />
window.CartInstance.getItem(foo);

Categories

Resources