This is a part of Think in React.
Thinking in React is the hard part for me because I see many developers do React with different mindsets.
When I was writing code for the Notification component that will be used by developers, suddenly I've noticed that there are different experiences to use the component:
Passing many Props like Bootstrap
<Notification
title="New Feature"
body={message}
action={action}/>
Passing one Prop as an Object
const data = {
title:"",
subtitle:"",
message:""
}
<Notification data={data}/>
Passing nested Children
<Notification>
<Title></Title>
<Body><Body/>
<Action><Action>
</Notification>
I followed the passing nested Children because ( I guess) It seems if I scale the component, I don't need to provide a Bootstrap-like experience for the developers.
import React from "react"
import { Wrapper, Text } from "./Styled"
const Body = ({ message }) => (
<Wrapper>
<Text>{message}</Text>
</Wrapper>
)
export default Body
The problem is I'm thinking about it is when I want to scale the Component and let's say adding 3 additional features that require 3 additional props
I'm confused about the reasons why each approach might be chosen, and what's the "best" developer experience.
To answer this question let's review all possibilities given React Element and a Function Component:
const c1 = <div>React Element</div>;
const C2 = () => <div>Function Component</div>;
Notice that from performance perspective, every component can be memoized and not cause useless renders.
Pass React element through props
const ObjectProps = ({ component }) => {
console.log("render object props");
return component;
};
<ObjectProps component={c1} />
Pros
Simple.
Lets you define the exact usage of passed component (contract).
For example you might decide "messages" have to be on top of "actions"
Cons
No lazy rendering
Passing heavy component may cause performance issues.
Hard to inject props (need to use React.cloneElement).
As a library writer you would like to inject your styles and refs.
Passing Function Component
const FunctionComponentProps = ({ FunctionComponent }) => {
console.log("render function component");
return <FunctionComponent />;
};
<FunctionComponentProps FunctionComponent={C2} />
Pros
Lazy rendering.
Easy to pass props and inject props for inner implementation.
Lets you define the exact usage of passed component (contract).
Cons
Confusing
Children Render
const ChildrenRender = ({ children }) => {
console.log("render function component");
return children;
};
<ChildrenRender>
{c1}
<C2 />
</ChildrenRender>
Pros
No restriction for the developer (no contract)
Cons
Hard to inject children (need to use React.Children API + React.cloneElement combo)
No contract
The developer might pass "buttons" and then "messages" and break the view.
Implementing ChildrenRender usually comes with component instances which results a minimal "contract" mentioned above.
const ChildrenRender = ({ children }) => {...};
ChildrenRender.InnerComp1 = <SomeComponent .../>
ChildrenRender.InnerComp2 = <SomeComponent2 .../>
<ChildrenRender>
<ChildrenRender.InnerComp1>{c1}</ChildrenRender.InnerComp1>
<ChildrenRender.InnerComp2><C2/></ChildrenRender.InnerComp2>
</ChildrenRender>
In Conclusion
It heavily depends on the component's usage, usually the hybrid approach suits well - passing components through props and add an option for passing children too.
Another technique is Render Props.
Related
I am trying to create an architecture that in some way imitates the slots from VUE.
The idea is for the parent component to be able to inject some props into the component and the child can inject the rest of the props.
This is how I tried to approach this problem, unfortunately this approach will not work because the compontent will be "monut" every time the parent re-render takes place.
Filters = (prams) => {
useEffect(()=>{ //RENDER ALL THE TIME },[])
...
}
ParentComponent = () => <ChildComponent Filters={(props) => <Filters propA={"A"} />}
ChildComponent = (props) => {
const Filters = props.Filters;
render(<Filters probB="B" />)
}
I know, I can use useCallback for ((props) => <Filters propA={"A"} />), but only it will help only if what I want to pass to "propA" is steady.
I want to "manage" <Filters /> component in parent, so that the child does not have to handle Filters logic (props).
React gives you proper API to do most things. Using it forces you into certain paradigms that are proven to work well.
You should probably have a look at the Context and Memo APIs from React.
Or if you have to select and update state from multiple components, you might wanna have a look at libraries that provide global state, like Redux and Recoil.
Context example
// The shape
interface ContextProps {
myProp: string
}
// The context
export const MyContext = React.createContext<Partial<ContextProps>>({
myProp: 'nothing'
});
// The provider
<MyContext.Provider value={{ myProp: 'override' }}>
{children}
</MyContext.Provider>
// Consumer
const { myProp } = useContext(MyContext)
In some case you can also use useMemo or React.memo and use your own custom compare function if needed to prevent re-renders in very specific situations.
I have tried pass value from parent to grandchild component, and it works. While I am thinking if there is another simpler or other way of passing props in shorter path.
What I did is quite cumbersome in codesandbox
There may be a common problem in react world called prop drilling by passing data to children only using props.
I would recommend only 2-level passing, if you need pass data deeper then you probably doing something wrong.
Use one of popular state management library (if your project is big) or React context (which is awesome)
Create a folder called /contexts and put contexts there. The structure of files can be like shown below:
First you need to create a context itself
type ClientContextState = {
data: User;
set: (data: User) => void;
logout: () => void;
};
// empty object as a default value
export const UserContext = createContext<UserContextState>({} as UserContextState);
Then create a Provider wrapper component
export const UserProvider = ({ children }: Props) => {
const [data, setData] = useState<User>({});
const sharedState = {
data,
set: setData
logout: () => setData(null)
}
return <UserContext.Provider value={sharedState}>{children}</UserContext.Provider>
});
You may also want to have an alias of useContext hook:
export const useUser = () => {
return useContext(UserContext);
};
After all this whenever you wrap your components or app to <UserProvider>...</UserProvider> you can use our hook to access data and methods form sharedState from any place you want:
export LogoutButton = () => {
const {data, logout} = useUser();
return <Button onClick={() => logout()}>Logout from {data.name}</Button>
}
Whenever you want to pass props or data from Grandparent to child component, always use react-redux. This is useful to maintain the state and access the data from anywhere/any component.
Another way is to use useContext hooks which you can use to pass the props
Following are the steps to use useContext hooks
Creating the context
The built-in factory function createContext(default) creates a context instance:
import { createContext } from 'react';
const Context = createContext('Default Value');
The factory function accepts one optional argument: the default value.
Providing the context
Context.Provider component available on the context instance is used to provide the context to its child components, no matter how deep they are.
To set the value of context use the value prop available on the
<Context.Provider value={value} />:
function Main() {
const value = 'My Context Value';
return (
<Context.Provider value={value}>
<MyComponent />
</Context.Provider>
);
}
Again, what’s important here is that all the components that’d like later to consume the context have to be wrapped inside the provider component.
If you want to change the context value, simply update the value prop.
Consuming the context: Consuming the context can be performed in 2 ways.
The first way, the one I recommend, is to use the useContext(Context) React hook:
import { useContext } from 'react';
function MyComponent() {
const value = useContext(Context);
return <span>{value}</span>;
}
Generally it's helpful to consider whether moving state down the hierarchy would be the simplest route. That means lifting the component instantiation to a place closer to the state being used. In your example, that could mean Component_data is used inside Component and passed to its children there, removing one step in the nested data flow. Even better, would be that Child.a accesses Component_data.A directly.
In a real app with cases where accessing the data directly is less feasible, a solution I lean towards is using Context to set data in the parent that retrieves it, and then I can access it however deeply nested the component might be that needs it.
i.e. in App I would create the Context provider, and in ChildA I access it via useContext hook.
Further reading
https://reactjs.org/docs/context.html
https://overreacted.io/before-you-memo/#solution-1-move-state-down (this post is about an alternative to using useMemo but has an illustrative example of why moving state down is a good thing)
TL;DR: I'm wondering if a solution I've thought of will cause performance issues.
The title is a bit confusing so let me clarify. A standard way of defining my header button component would be something like this (the code is using typescript but it's not relevant to the problem so you can probably ignore it):
import React from "react";
const HeaderButton = (props: Client.Common.Header.HeaderButton.Import) => {
const service = useHeaderButton();
return <div>
//header component JSX here
<div>
};
export default HeaderButton;
I want to change it up a bit. I've found I would much rather be able to expose some internal component methods to the parent component. In this case, I would like to be able to provide a toggle method to the parent instead of using an "active" property to determine the active state of the button. My reasoning is that I'd rather avoid having to set up the toggle logic in every parent component I use my HeaderButton in if I can instead define it in my button component and then have the parents use that method.
I've done this and it works as I'd like it to (so far, at least). I'm relatively new to both React and programming in general and self taught so I have gaps in my knowledge. I'm not that knowledgeable about React under the hood and how it does its performance optimizations etc so I'm worried I've done something that will cause unpredictable issues. Here's what I've done:
//Header.tsx (this component is using the regular style)
import React, { useEffect } from "react";
import "./Header.scss";
import HeaderButton from "./Components/HeaderButton/HeaderButton";
const Header = () => {
const HeaderButtonService = HeaderButton({ renderProps: <div>TEST</div>, class: "languageSelectionButton" });
useEffect(() => {
setTimeout(() => {
HeaderButtonService.toggle();
}, 5 * 1000);
}, []);
return (
<div id="headerBar">
<div className="headerNavigationButtonsContainer"></div>
{HeaderButtonService.view}
</div>
);
};
export default Header;
//HeaderButton.ts
import HeaderButtonView from "./HeaderButtonView";
const HeaderButton = (props: Client.Common.Header.HeaderButton.Import) => {
const service = useHeaderButton();
return {
toggle: service.toggleActive,
view: HeaderButtonView({ ...props, service.active })
};
};
export default HeaderButton;
//HeaderButtonView.tsx
import React from "react";
import "./HeaderButton.scss";
const HeaderButtonView = (props: Client.Common.Header.HeaderButton.Import) => {
return (
<div className={"headerButton" + (props.active ? " active" : "")
+ (props.class ? " " + props.class : "")}
style={{ ...props.style }}>
{props.renderProps && props.renderProps}
</div>
);
};
export default HeaderButtonView;
In my solution, I import HeaderButton.ts instead of HeaderButton.tsx. The parent component passes the relevant props to HeaderButton.ts which passes them down to HeaderButtonView.tsx while adding the "active" prop which it gets from a custom hook, not the parent component. It then returns the result of invoking HeaderButtonView with these new props as well as the method to toggle the active state.
This is a simple example but I would potentially use this template to expose state values and multiple methods to parent components.
The code works, it renders what I want it to render and toggles the active state after 5 seconds.
My concern is that, not knowing all that much about how react works under the hood, I might have created an optimization problem. Is there any reason I shouldn't be using this pattern?
I have done some testing and it doesn't seem to break anything. I added a counter to the state of header.tsx and increment it every 3 seconds then watch for re-renders. I was concerned that react would not be able to recognize that the old and new HeaderButton components are the same but it did. Though react goes through the component tree, it doesn't re-render the button (except after the first 5 seconds when activity is toggled).
Also, should HeaderButton.ts be a hook? It's working as intended atm so I'm not sure what exactly I gain/lose from adding "use" in front of it.
Your approach here is 1) contrary to how 99% of people use React, 2) contrary to very way React is intended to be used, and 3) overcomplicated in a way that adds a level of abstraction to React that absolutely does not need to be there.
1) This is just not how people write React code. Sure it might work for you and make sense on some level but no one else follows this pattern. What about when you start importing and using other people's components? What about when you have to partner with someone else to write an app? What about when you hand off or are handed off a bunch of code that is patterned in a completely different way? There is absolutely a lot to be said for following prevailing (or at least common) coding patterns because it makes your code a lot more interoperable and easy to understand compared to the rest of the framework ecosystem, and vice-versa.
2) React at its very core is intended to be declarative. It is the number one adjective most people would use to describe it, and features heavily on the very front page of the React website. Your proposed pattern here is very un-declarative, and directly defeats not just the declarative nature of React but the inherent patterns of component state and props. I could link you to examples in the documentation as to how declarative coding, state management, and props feature heavily in React design patterns but the list would include practically every page in the website: Components and Props, State and Lifecycle, Lifting State Up, Thinking in React, etc etc.
3) Your proposed pattern is just... needlessly complicated and abstract. It adds a layer of confusion that does not actually make things easier. I can barely follow even your basic minimal example code!
Your core rationale seems to be this:
My reasoning is that I'd rather avoid having to set up the toggle logic in every parent component I use my HeaderButton in if I can instead define it in my button component and then have the parents use that method.
That's a great instinct - make things reusable and modular so that you don't have to repeat yourself too often. Well, you can do that beautifully in React while adhering to the tenants of React's declarative nature!
First let's rewrite your components in a way that is a more "traditional" React style - just make the <HeaderButton> a regular component that accepts an active prop, and storing that state in the parent, <Header>. This is called "lifting state up" and is a key concept in React - state should live at the lowest common denominator that allows the necessary components access to it. In this case the parent <Header> needs access to the state, because it needs to not only pass it into <HeaderButton> as a prop, but be able to modify that state:
const HeaderButton = ({active}) => {
return <div>{active ? 'Active' : 'Inactive'}</div>
};
const Header = () => {
const [active, setActive] = React.useState(false);
const toggleActive = () => {
setTimeout(() => {
setActive(oldActiveState => !oldActiveState);
}, 5 * 1000);
};
return (
<header>
<button onClick={toggleActive} >Toggle active state</button>
<HeaderButton active={active} />
</header>
);
}
Cool, now state lives in the parent, in can modify that state, and it passes that state as a prop to <HeaderButton>. It is very declarative, easy to understand, and it's clear where state lives and what component is rendering what.
Now on to your concern about reusing the toggle logic. What if we want to use <HeaderButton> somewhere else and have the same toggle logic? What if we want to have five header buttons inside of <Header>? Do we need to copy and paste the same logic many times?
React provides a great solution here with custom hooks. Custom hooks allow you to encapsulate logic and state in a clean way. And - this is very important - the state it encapsulates still lives inside of the component that calls the custom hook. This means we can encapsulate the state and logic but they will still "live" inside of <Header> so we have access to it to pass as a prop. Let's try it:
const useHeaderButtonState = () => {
const [active, setActive] = React.useState(false);
const toggleActive = () => {
setTimeout(() => {
setActive(oldActiveState => !oldActiveState);
}, 5 * 1000);
};
return [active, toggleActive];
}
const HeaderButton = ({active}) => {
return <div>{active ? 'Active' : 'Inactive'}</div>
};
const Header = () => {
const [active, toggleActive] = useHeaderButtonState();
return (
<header>
<button onClick={toggleActive} >Toggle active state</button>
<HeaderButton active={active} />
</header>
);
}
Now, the state and the toggle logic live inside of useHeaderButtonState(). When called, it returns both a value (active) and a function for updating that value (toggleActive). Inside of <Header>, we can deconstruct the result of the custom hook call and use it to render.
We could even extend this custom hook even further to return not just the state and updater function, but a component to render. Then, if we want to render multiple instances of a component, including all its associated state and logic, and still have access to the state and logic in the parent component (<Header>), we can do that:
const useHeaderButtonState = () => {
const [active, setActive] = React.useState(false);
const toggleActive = () => {
setTimeout(() => {
setActive(oldActiveState => !oldActiveState);
}, 5 * 1000);
};
const headerButtonComponent = (
<>
<button onClick={toggleActive}>Toggle active state</button>
<HeaderButton active={active} />
</>
);
return [headerButtonComponent, active, toggleActive];
};
const HeaderButton = ({ active }) => {
return <div>{active ? "Active" : "Inactive"}</div>;
};
const Header = () => {
const [
headerButtonComponent1,
active1,
toggleActive1
] = useHeaderButtonState();
const [
headerButtonComponent2,
active2,
toggleActive2
] = useHeaderButtonState();
const [
headerButtonComponent3,
active3,
toggleActive3
] = useHeaderButtonState();
return (
<header>
{headerButtonComponent1}
{headerButtonComponent2}
{headerButtonComponent3}
</header>
);
};
https://codesandbox.io/s/pensive-sutherland-2onx9
Now we're cooking with gas! We're reusing state and logic but doing it in a declarative way that makes sense inside of React.
Sorry to be so heavy handed but I really, really want to discourage you from using the imperative pattern you proposed above. I've been writing React code for 3+ years and trust me when I say that sticking to the established patterns of React will pay off in the long run. Not only is it legitimately easier to write and comprehend, if will help your career to write code that other devs can more easily understand and work with.
I conduct a lot of hiring interviews and if I saw someone submit the code you wrote above, I would think they have no idea how React works or is intended to work and would immediately disqualify them. If you find it difficult or counterintuitive to understand, I would suggest keep on learning and practicing (with a more declarative React-compatible style) until it clicks. Otherwise, perhaps React just isn't the framework for you and you'd be best served with a different framework that more closely matches your preferences, style, and mental models!
Good luck!
Edit: Oh and one last thing I'll touch upon. You mentioned concerns about performance. In this case the performance differences are actually completely negligible and not worth even considering. In general React is very well optimized on its own and you don't need to worry about performance except in very specific edge cases. You should typically only optimize if and when you actually run into a performance bottleneck, and you solve for that. As they say, premature optimization is the root of all evil.
My response instead addresses the core programming pattern that you are proposing here on the basis that it makes the process of developing, debugging, and understanding the code itself needlessly difficult.
I would like to create my own reusable components that can be used in other projects (as npm package).
Let assume that the reusable components are structured in this way:
ui
├───card.jsx
├───card-board.jsx
├───card-body.jsx
├───sliding.jsx
├───sliding-board.jsx
├───sliding-body.jsx
├───img.jsx
├───remove.jsx
├───title.jsx
└───index.jsx
Usage Example:
this.state.Cards.map(card => {
return (
<Card key={card.id}>
<Remove onRemove={() => this.handleCardRemove(card.id)} />
<Img />
<CardBody>
<Title>{card.title}</Title>
<p>{card.body}</p>
</CardBody>
</Card>
);
});
It worked great until the number of cards are overpopulated. All the update and delete function became really slow due to the waste re-rendering on the same static content.
And of course this can be optimized by checking the previous and the new props on the Card component as below:
import React from "react";
import { CardContainer } from "./card.style";
import { deepCompare } from "../../utils/";
const Card = ({ children }) => {
return <CardContainer>{children}</CardContainer>;
};
// if return true - Card component won't re-render
const arePropsEqual = (preProps, newProps) => {
return deepCompare(preProps, newProps); // <- function that compare the nested props.
};
export default React.memo(Card, arePropsEqual);
As you can see. I'm using React.memo to control the re-rendering and I also implemented the deepCompare function that will return true if both input props are the same.
So, I would like to know what is the best way to handle this kind of problem in the large scale where there are a lot of components (probably nested).
And, Should the above solution(React.memo) be applied for each individual ui component that I created?. Because some of the ui element can be used individually without depending on some kind of container eg: Img, Remove and Title component.
I'm learning React hooks (and I'm fairly new to React), and was thinking through use cases for useImperativeHandle. I came up with a pretty useful scenario.
I do know that this can be accomplished without useImperativeHandle, but I think there are some advantages here.
What I don't know...
Have I 'discovered the obvious' and this isn't really useful? ... or
Is this bad form, or an anti-pattern?
My code is working- but I'm looking for input about best practices. Also, since there's a dearth of information right now about useImperativeHandle, this example that goes beyond an input ref might be useful to others.
I have a minimal example posted on Github if you want to play with it:
https://github.com/ericsolberg/react-uih-hook
Using markup similar to:
const App = props => {
return (
<Foo>
<Bar>This is Bar 0</Bar>
<Bar>This is Bar 1</Bar>
<Bar>This is Bar 2</Bar>
</Foo>
);
};
What I've accomplished is:
Allow a parent component 'Foo' to provide state storage for it's children - so Foo can mount/dismount children yet allow them to restore state.
'Bar' uses useImperativeHandle to provide a 'call-in' so Bar can veto being dismounted, in case it is doing something important.
As I noted, this works perfectly. In React, data and props flow down the tree, while callbacks flow up. This gives you a way, for specific scenarios, to call down the tree. Is it advisable?
This is an anti-pattern: Inject props into the children without explicitly passing the props.
The idiomatic options are:
pass props explicitly
use context
Lifting state up to be able to do the above 2
render props
So if nothing simpler would be suitable for my business logic, I do something like following to avoid invisible tight coupling between Foo and Bar:
const App = () => (
<Foo>
{({selector, saveStateFactory}) => (<>
<Bar state={selector(0)} saveState={saveStateFactory(0)} />
<Bar state={selector(1)} saveState={saveStateFactory(1)} />
</>)}
</Foo>
)
const Foo = () => {
const [state, dispatch] = useReducer(...)
const selector = (id) => state[id]
const saveStateFactory = (id) => {
return (payload) => dispatch({type: id, payload})
}
// do something with whole state that cannot be done in App nor Bar
return children({selector, saveStateFactory})
}