render with different child components from props - javascript

I am currently working in a scenario, where I need to be able to import a component from library, but tell it to choose different components for some of its child components to render with. In this case, it needs to choose different button components, for example. Now I already got this working, as in, it does what it needs to do, but I am wondering if there is maybe a more fitting/appropriate way of doing it.
export const container = ({component, children}) => {
const ButtonComponent = component?.button ?? Button;
return (
<div>
<ButtonComponent size="large">Do something</ButtonComponent>
</div>
)
}
In this case, Buttons are defined in this same library, but on the side of the application where the library is consumed, the buttons are modified, variants are added, some properties are added that are not part of the original component of the library. And I am telling the component to use a different component like this:
<container component={FancyButton} />
As I said this works, but it feels like there might maybe be a more elegant solution to this. This is relevant, because the library uses an atomic design methodology approach, where some of the more complex components use less complex components, but they are being modified for a specific usecase. So all the buttons are being modified to have additional variants, etc. But if I then go a head and for example use the modal, it uses the regular buttons, not the modified buttons. This is the solution that I came up with, that allows me to tell the component to use these modified buttons instead.
Does this make sense? Is this an anti-pattern? Is there a more efficient/elegant solution to this?
€1: Here's a codesandbox demonstrating what this does: https://codesandbox.io/s/practical-ives-jxk3v

const defaultComponents = {
button: BaseButton
}
export const Card = ({ components=defaultComponents, children }) => {
return (
<div className="card">
<h2>Thing</h2>
{children}
<components.button className="card__button">Click me</components.button>
</div>
);
};
I don't know is this help. I will do like this.
use = when you passing props in a function mean the default value of that prop.

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.

useState element not rerendering when triggered by an event handler in a child component

I have a parent component with a useState that is being displayed. What I want to do is have a child component be able to update this state to change what the parent is displaying. I currently have the following:
function Parent() {
const [myWindow, setMyWindow] = useState(null)
useEffect(() => {
setMyWindow(<Child updateWindowFunc={() => setMyWindow(someNewWindow)} />)
}, []}
return (
<div>
{myWindow}
</div>
)
}
function Child({updateWindowFunc}) {
//Somehow calls updateWindowFunc
}
(Note that my actual code is set up somewhat differently, so don't mind the syntax as much as the general concept.)
I've found that I can get the value of myWindow to change, but the actual display doesn't show any change. I've tried forcing a re-render after the change, and adding a useRef to display useRef.current, but nothing seems to update which window is actually being rendered.
What am I doing wrong?
Edit:
I've found that it works if I switch to a different type of component, but if its just a different element of the same component then there is no re-render. I've been using React.createElement(), so I would think the 'objects' are distinct, but maybe I just misunderstand how this works.
Can you provide more details or working\not working code example that has more info?
Currently it's hard to get, cause that doesn't make any sense since you can just render it like below and that code will have same result as one that you are trying to run
function Parent() {
return (
<div>
<Child />
</div>
)
}
So for better understanding and fixing a problem \ finding a different approach, please, provide more details

Is it a good idea to use React.Context to inject UI-Components?

I plan to build a react component library. The react components are UI-Components but should only implement a specific logic. I want the user to be able to define a set of atoms (basic react components) that are used to compose the actual components. My main goal is to make the library independent of a specific UI-Component-Library like MaterialUI, ChakraUI, etc.
My idea was to use a React.Context to inject the components like this:
// Button Atom
const Button: FC = ({ children }) => (<button>{children}</button>)
const atoms = { button: Button }
const AtomContext = createContext(atoms);
// "higher" component
const HigherComponent: FC = () => {
const atoms = useContext(AtomContext)
// Logic ...
return (
<atoms.button>click me</atoms.button>
)
}
export default function App() {
return (
<AtomContext.Provider value={atoms}>
<HigherComponent />
</AtomContext.Provider>
);
}
This solves my problem. But I'm not sure if it is a good idea. Are there better ways to inject UI-dependencies? What may be problems with my approach?
This is a question that can result in multiple answers based on personal experience.
But overall the idea to pass component trough context is a bit of misuse of that concept.
Also it does not bring much benefit from making a standalone library that can be imported via package.json or making a folder with components and importing them.
If you need to pass some specific things to your components you can have a custom provider as you did there, but as far as the component themselves, there is no common sense to use context.
If you end goal is to shorten the list of imports of component with custom context hook and just getting them like that, I think that is a bad tradeoff overall.
I just read this article about using react context for dependency injection. The authors opinion is, that react context for injecting non-react dependencies into components is good practice. However, I'm not sure if that applies to injecting a specific set of react components. Since, react components are nothing more than functions I think it should be alright to use reacts context for that.

Pass data to one of the same components

<Comp1 />
<div>
<Comp1 />
<Comp2 />
</div>
I am new to React. I want to pass data from Comp2 to its sibling Comp1 only. I know using a parent component to pass props but in this case I have to rewrite Comp1 to get state from its parent, which will affect all the Comp1. How can I make only chosen Comp1 receive the data and don't bother the else?
There is not a straightforward solution to this, but you do have a couple of options:
Option 1
The most direct way would be as you described - having Comp2 pass data up to its parent using an event listener, then having the parent pass it back down to Comp1. This can be an optional prop being passed to Comp1, so it doesn't matter that your outer Comp1 won't receive that prop.
For example:
import React from 'react';
const Comp1 = ({data='Default Value'}) => (
<p>{data}</p>
)
const Comp2 = ({onData}) => (
<button onClick={e => onData(Math.random())}>Change Value</button>
)
export default function App() {
let [data, setData] = React.useState(null);
return (
<div>
<Comp1/>
<div>
<Comp1 data={data}/>
<Comp2 onData={setData}/>
</div>
</div>
);
}
This is probably your best option, and by the sound of things, it might be good to find a way to refactor your app so that this option becomes more viable. There's usually a way to change your app structure to make this work better.
If you really want siblings to have a more direct line of communication with each other, you could give Comp1 a ref of Comp2, but I wouldn't encourage this.
Option 2
Another option would be to use contexts. This gives anyone the power to communicate with anyone who uses the same context. There is a lot of power in this feature. Some people set up a Redux-like system using contexts and reducers to let any part of the application (or larger component they put the context provider in) communicate with any other part. See this article for more information on using contexts to manage application state.
import React from 'react';
let context = React.createContext()
const Comp1 = () => {
let ctx = React.useContext(context) || {};
return <p>{ctx.data || 'Default Value'}</p>
}
const Comp2 = () => {
let ctx = React.useContext(context);
return <button onClick={e => ctx.setData(Math.random())}>Change Value</button>
}
export default function App() {
let [data, setData] = React.useState();
return (
<div>
<Comp1/>
<div>
<context.Provider value={{data, setData}}>
<Comp1/>
<Comp2/>
</context.Provider>
</div>
</div>
);
}
Option 3
For completeness, A third option would be using something like Redux to help share state. Only use this option if you are already using Redux, or if you really want/need it and understand what you're getting into. Redux is not for every project, everyone does not need it.
Side Note
I realize you said you were new to React. For brevity and for other Googlers, I used a lot of React hooks in my examples (The functions like React.useState, React.useContext, etc). These can take a little bit to understand, and I don't expect you to learn how to use them just to solve your problem. In fact, if you're new to React, I would strongly encourage you to just go with option 1 using the class syntax you've learned how to use already. As you get some more practice and start feeling the limits of the first option, then you can start trying the other things out.
In react, data always moves from top to down, so there is no true way to pass information sibling to sibling without going through some higher structure. You could use context, but again, its provider has to wrap around both sibling components, meaning it has to be implemented in the parent component(App). It is also intended for passing data between deeply nested sibling components to avoid passing props multiple levels deep. In your case where props only have to be passed one level deep, it is best to just store state in the parent component(App).
Here is what context would look like for your App (its more trouble than its worth at this point):
https://codesandbox.io/s/objective-hellman-sdm55?file=/src/App.js
For this use case I would suggest using the useState hook in the parent component and passing down a value & function to the specific child components.
pseudo code:
<Parent>
const [value, setValue] = useState();
<Comp1 onClick={setvalue} />
<Comp2 value={value} />
</Parent>
In my opinion, for your use case, Redux and the Context API are a bit overkill.
You can research about state and props.
References: https://flaviocopes.com/react-state-vs-props

Correct way to reuse a component in React?

This is a simplified code.
Here is a button component:
import React from 'react'
import styles from './Button.module.scss'
const Button = (props) => {
return (
<button type={props.type} className={styles.btn} onClick={props.handleClick}>{props.text}</button>
)
}
export default Button
I'd like to reuse this component in multiple places. My issue is that i have a form where i don't need to provide any handleClick logic. So i just simply omit it:
<form onSubmit={handleFormSubmit} >
*form related code*
<Button text="Submit" type="submit" />
<form/>
And in places where i need some handleClick logic i pass it:
<Button text="Do Something" type="button" handleClick={()=> console.log('clicked')} />
Is it a correct way to reuse a component or should i have 2 different components in this case? It does not feel ok to make it reusable by not passing props. Thank you.
It's perfect, in the matter of fact, making a component re-usable via it's props is the best practice, and unless this way makes a component unnecessarily complicated then don't use it, and go for the Two Components approach instead.
Tip: we have a component A, it's already re-usable, but, we want to add another feature to it, to support an other use case, but it'll make the component complicated, in this case, building a new component A2 is preferable.
I believe that it is completely fine to not pass props to components. In a large number of libraries, there are default props. In the PropTypes library, you can use defaultProps to provide default values so the developer does not have to specify them every time.
Sometimes it is difficult to decide. So I always go with intent. Every component has an intent/purpose. If we use a property to customize the behavior slightly, then it is completely fine. Button with an optional handle for click binding.
But when we do with some important properties which could make the component behave like a different component (different intent), then it is better to go with two components to avoid ambiguity in usage. like ClickableButton and SimpleButton.

Categories

Resources