I have a ParentComponent with mildly complex state and logic (mostly to make UI much easier), it can be described as a some sort of form.
Now, after user has entered all the inputs, he needs to press a button.
Button and its action depend on the current state:
If user hasn't logged in, button suggests user to do so.
If they "used account from another platform", button asks him to change the account.
If account has no permissions to perform "main" action, button advises to request required permission.
And so on...
Naturally, I could dump all this logic and validation into ParentComponent and be done with it (and also with maintainability and readability). But I'd like to factor out each step described above into its own component (that may use local hooks and some global state from Redux) since calling hooks conditionally is frowned upon and hooks I need in one component conflict with hooks from another.
So I'm faced with the following problem:
I have multiple child components, each of which returns
button, if user needs to do something.
null, otherwise (user meets condition, so everything is fine).
I want to render said components one-by-one in predefined order.
If child component renders to something, show it.
If component renders to null, I want to render the next child.
This resembles chain of responsibility which is exactly what I'd like to do, but I don't understand how this approach maps to React components and rendering.
I've managed to come up with the implementation that allows to do the following:
<ChainOfResponsibility>
<EnsureUserHasLoggedIn>
<EnsureBalanceIsLoaded>
<BuyItemButton>
</ChainOfResponsibility>
If <EnsureUserHasLoggedIn> figured that user needs to sign in, it will render only the "sign in" button and nothing else. Otherwise, it will render the next "piece" in chain.
interface Props extends ChainOfResponsibilityPiece {}
const EnsureUserHasLoggedIn: React.FC<Props> = ({ next }) => {
const userHasLoggedIn = /* Arbitrary condition. */ true
if (!userHasLoggedIn) {
return <Button>Sign In</Button>
}
// Render the next piece in chain.
return <>{next}</>
}
Nice thing about this approach is that rerender of previous children will trigger render of the parent component (ChainOfResponsibility). That, in turn, will render the whole chain again, ensuring all invariants from the beginning.
Here is the code if you need it:
import React from 'react'
export interface ChainOfResponsibilityPiece {
next?: React.ReactNode
}
interface ChainOfResponsibilityProps {
children: React.ReactNode
}
const ChainOfResponsibility: React.FC<ChainOfResponsibilityProps> = ({ children }) => {
// Safety.
const childrenArray = React.Children.toArray(children).filter((child) => React.isValidElement(child))
// No children -> nothing to render.
if (childrenArray.length === 0) {
return null
}
// Build children with references to the next child.
const childrenWithNext = []
childrenArray.reverse().forEach((child, childIndex) => {
if (!React.isValidElement(child)) {
throw new Error(`Children must be valid React elements, got ${child}`)
}
childrenWithNext.push(
React.cloneElement(child, {
// SAFETY: We want either next child or `undefined` – out-of-bounds index returns `undefined`.
next: childrenWithNext[childIndex - 1], // Reversed index.
}),
)
})
// Render. Children are reversed, so start with the last one.
return <>{childrenWithNext[childrenWithNext.length - 1]}</>
}
export default ChainOfResponsibility
Related
I want to show/hide element present in a component, based on role level permission,
here is the code which I tried so far
My Component:
<ShowForPermission permission="My Studies" configuration={configuration}>
<div>Mystudies</div>
<ShowForPermission>
HOC:
import React from 'react'
export const ShowForPermission = ({ permission, configuration}) => {
const isAllowed = checkPermission(permission, configuration);
return isAllowed
}
export const checkPermission = (permission, configuration) => {
return configuration&&configuration.filter((item)=>item.name===permission)[0].permission.read
}
Here in My Component I'm passing key as My Studies and role config of that particular component as configuration to ShowForPermission
And in HOC I'm checking the given key i.e permission "My studies" equals to configuration.filter((item)=>item.name==="My Studies") so what I'm checking s suppose if this value is true I want render div present in my Component or else no. How to achieve this. Please help me on this
if permission.read==true, return true else false and render div based on condition.
Thanks
The thing you've described is not a HOC. That's fine, component composition(when you nest component to show in another component that provides control) suits even better than HOC here.
So your ShowForPermission makes some check and renders whatever nested component provided(nested components are passed as children prop)
export const ShowForPermission = ({ permission, children }) => {
const isAllowed = ..... // do some permission check
if (isAllowed) {
return children; // rendering nested elements
} else {
/*
it also might be false, empty string or empty array
(and for React 18 it can be `return;` or `return undefined` as well);
also you even can omit explicit `return undefined;` for `else` branch
but this way intention is more clear
*/
return null;
}
}
But how do you get configuration? Definitely not passing it as a prop. Why? Because you will need to pass it every time you use ShowForPermission. And to pass it in 3rd-, 5th- or 10th-level nested component you would have to pass configuration to every its parent up the line. That has name of "prop drilling" and Context API is exactly what been created to solve this.
Context API is a way to inject data into component without passing it explicitly into every parent as a prop. Take a look into examples in docs, finally your app should load configuration and create <YourContextProvider value={configurationData}> somewhere at the very root app element or close to the root. I'm intentionally skip question how you can load that configuration before passing to context(a large separate topic and also it's up to your requirements).
Finally, your ShowForPermission would access data from context(preferably with useContext hook) to check the permission:
export const ShowForPermission = ({ permission, children }) => {
const configuration = useContext(YourPermissionContext);
const isAllowed = configuration
.filter(
(item) => item.name===permission
)[0].permission.read;
if (isAllowed) {
return children; // rendering nested elements
} else {
return null;
}
}
I have a need to pull some content out of a child component such that the parent (or just generally higher-level) component can dispatch an action creator with the data of the inner child component.
Previously I was invoking a function on the child ref like this.refs.child.doStuff() until I found it was becoming difficult to keep track of the ref in whatever parent component I needed the method data in. Everytime there is a HOC or some other kind of compositing wrapper I need to add more code to pass the ref up the chain that I need. Not to mention there was a lot of duplicate code in each child function that was standard to all.
// child.js
class InnerComponent extends React.PureComponent {
doStuff = () => {
calculateFrom(this.state.content)
// and then do stuff with it..
// I've since made this more redux-y by just returning the data
// out to an action dispatcher
}
}
const SomeHOC = (args) => {
return (Component) => class extends React.Component {
proc(wrappedComponentInstance) {
// I went with using the getWrappedInstance func here
// to mimic conect(.., .., .. { withRef: true }) for
// basic compatibility with a higher level function that
// dives through the wrappedInstances to get to the bottom one
this.getWrappedInstance = () => wrappedComponentInstance;
}
render() {
const props = Object.assign({}, this.props, { ref: this.proc.bind(this) });
return <Component {...props} {...this.state} />
}
}
}
// I actually don't need the connect() here but will on other components of the same style when they're not in their wrapped form
export default connect(..., ..., null, { withRef: true })(SomeHOC()(InnerComponent);
// parent container (there are multiples of these in my app)
class Container extends React.PureComponent {
determineContent = (...) => {
return React.createElement(this.state.content, {
// so I can get to the composited inner element...
ref: (element) => { this._compositeElement = element; },
...viewletProps
});
}
componentWillMount() {
// ...
System.import(`${dynamic}.jsx`).then((content) => {
this.setState({ content });
});
}
renderButtonContainer = () => {
// Here's where things get weird...
if (!this.doStuff) {
this.doStuff = ((() => {
// hook into my ref
const compositeElement = this._compositeElement;
// deep-dive through to get to the base
let base;
if (compositeElement && typeof compositeElement.getWrappedInstance === 'function') {
base = this._compositeElement.getWrappedInstance();
while (typeof base.getWrappedInstance === 'function') {
base = base.getWrappedInstance();
}
if (typeof compositeElement.doStuff === 'function') {
this.doStuff = base.doStuff;
}
}
})());
}
return (
<div hidden={!this.doStuff}>
<span><i class="myIcon" onClick={this.doStuff}></i></span>
</div>
);
}
render() {
<div>
{ this.renderButtonContainer() }
<div>
{ this.determineContent(...) }
</div>
</div>
}
}
I've since pulled all of that out and am now dispatching an action per Redux-style and letting my reducer handle what needs to happen (just a synchronous internal call that executes immediately using the data in the action; I'm still unsure if this being in the reducer is bad form)
However, since I need my child components to still return the invocation of their doStuff() (getStuff() instead) at the time of the parent component's choosing, I find myself stuck with my same ref issues.
Am I going about this all wrong? It almost seems to me like for each child component I have I need to be storing this always-changing data from getStuff() inside my state model and just pass it down into the component? But I'd anticipate that would be too disassociating from the actual component and the rest of my app doesn't really care about that.
Frankly, none of that seems like a good idea at all. Even if it's technically possible, it completely goes against the intended usage of both React and Redux.
Refs in general are an escape hatch, and should be used only if necessary. Refs to DOM nodes are more useful, because you may need to do things like determining if a click is inside a DOM node, or read a value from an uncontrolled input. Refs to components have a lot fewer use cases. In particular, directly calling methods on components is definitely not idiomatic React usage, and should be avoided if at all possible. From there, groveling into the guts of React's implementation is a really bad idea.
If you need to make use of a nested child's data in a parent component, and those are widely separated, then you should either pass some kind of callback prop down through all those children, or you should dispatch a Redux action to put the data into the store and have the parent subscribe to that data.
So yeah, it's hard to tell exactly what you're actually needing to get done from that description and example code, but I can safely say that what you've got there is not the right way to do it.
#markerikson there is valid reasons for doing this. React unidirectional data flow is a very important fundamental rule, but just like how the best music breaks some rules (jazz improv), so too do the best React apps break some conventions in very crafty places. For example, Redux and React router must break convention by using context. So too is there valid use cases for using refs and accessing components with refs.
For example: Imagine you have a very complicated text editor component as a parent. You have many child components responsible for the fields such as a contact field a website field and a description field etc. Now, inside each of these child components you frequently update state. Specifically, on every single click of a button you update the state. The product ask is to have all of the fields alive at the same time so you cannot have individual submit buttons on the fields. You must take all of the current state of all fields at the same time. Now, you could manage the state by lifting it to the complicated parent component, but then that means every single time you press a button to re-render any of these fields you are going to have a callback that causes the complex parent to rerender and therefore all other children components.
On the other hand, with the crafty use of the nifty trick component refs we can update the state of all fields completely independently without triggering rerenders in everything, then when the user submits the form we can trigger one function inside the parent that checks the current/final state of all children through refs and submit that value to the database.
The idea of antipatterns comes up in React a lot. You are right to stick to the rules most of the time, but like a jazz pianist takes the standard Saints Go Marching In and breaks some rules to make something more enjoyable to some people, we can take React, a rigid set of conventions and improv on them if it creates a better experience for certain contexts.
I'm having view with list of Steps. Each step can be "viewed" and "removed" from within the "view" screen.
My StepDetails component binds to redux store by fetching corresponding step from steps part of store with simple steps.find(...):
const mapStateToProps = (state, ownProps) => {
let stepId = ownProps.params.stepId;
let step = state.steps.find(s => s.id === stepId);
return {step};
};
Now when (from within "details") I hit "Delete step" I want this step to be removed from store and I want to navigate to list view.
There is redux action invoked on delete that returns new steps list without this one removed and redirect is called afterwards:
const deleteStep = (stepId) => {
return dispatch => {
return stepsApi.deleteStep(stepId).then(steps => {
dispatch(action(STEPS_LIST_CHANGED, {steps}));
// react-router-redux action to redirect
dispatch(redirectToList());
})
}
};
This is fine and does what I want, with one drawback: when STEPS_LIST_CHANGED action is called and step gets removed from the list, my component's mapStateToProps gets called before this redirect. Result is that mapStateToProps cannot obviously find this step anymore and my component gives me errors that step is undefined etc.
What I can do is to check if step provided to component is defined, and if not render nothing etc. But it's kind of defensive programming flavor I don't really like, as I don't want my component to know what to do if it gets wrong data.
I can also swap actions dispatch order: redirect first and then alter state, but then it doesn't feel right too (logically you first want to delete and then redirect).
How do you handle such cases?
EDIT:
What I ended up with was to put this null/undefined-check into container component (one that does redux wiring). With that approach I don't clutter my presentational components with unnecessary logic. It also can be abstracted out to higher order component (or ES7 decorator probably) to render null or <div></div> when some required props are not present.
I can think of two approaches:
Delegating the list changed to the redirect? For example:
dispatch(redirectToList(action(STEPS_LIST_CHANGED, {steps})));
Handling the null step component to ignore rendering:
shouldComponentUpdate: function() {
return this.state.steps != null;
}
You can change your return statement into an array
const mapStateToProps = (state, ownProps) => {
let stepId = ownProps.params.stepId;
let step = state.steps.find(s => s.id === stepId);
return step ? [{step}] : []
};
This way on your component you can do a step.map() and render it.
The problem you are facing is that you want an atomic action that will do both the deletion and redirect. This is not possible because the Redux store and URL are two separate states that cannot be updated in one run.
As a result, the user can see for a brief moment either:
an empty entity detail page before the redirect or
the deleted entity in the list after redirect.
One way to deal with that is to use React.memo (or shouldComponentUpdate) and prevent component re-render after entity has vanished:
const entityWasDeleted = (prevProps, nextProps) =>
prevProps.entity !== undefined && nextProps.entity === undefined
export default React.memo(EntityDetailComponent, entityWasDeleted)
It's a little bit hacky, because React.memo should not be used like this, but the component gets unmounted in the next step so who cares.
I have a React component that dispatches a redux state change in its componentWillMount function. The reason is that when the component is loaded, it needs to get the id from the url (powered by react-router), and trigger an action that sets up the state with that id's data.
Here is the component:
class Editor extends React.Component {
componentWillMount() {
const { dispatch, params } = this.props
dispatch(editItem(params.id))
}
render() {
const item = this.props.item
console.log("Editing", item)
}
}
export default connect(state => ({item: state.item}))(Editor)
Here's the catch: render is getting called twice. item is undefined on the first call, and valid on the second. Ideally, it should only be called once this.props.item actually exists (after the editItem action has been dispatched and run).
According to the React docs: "If you call setState within this method, render() will see the updated state and will be executed only once despite the state change."
In redux, dispatch is the equivalent of calling setState, as it results in a state change. However, I'm guessing something in the way connect works is still causing render to be called twice.
Is there a way around this besides adding a line like if (!item) return; ?
One thing you might do is create a higher order component that handles the basic pattern of loading a different component (or no component) before the required props are loaded.
export const LoaderWrapper = function(hasLoaded, Component, LoaderComponent, onLoad) {
return props => {
if (hasLoaded(props)) {
return <Component {...props} />
}
else {
if (onLoad) onLoad(props)
return { LoaderComponent ? <LoaderComponent /> : null }
}
}
}
Then you can wrap your component before connecting it to get the desired behaviour.
export default connect(state => ({item: state.item}))(LoaderWrapper(
((props) => !!props.item),
Editor,
null,
(props) => props.dispatch(editItem(props.params.id))
))
You might want to add some currying magic to make sure you can compose these kinds of wrapper functions more nicely. Take a look at recompose for more info.
It looks like there's already an issue in the react-redux library.
https://github.com/rackt/react-redux/issues/210
What does editItem do? Does it add item to the redux state or is it there already?
If it is adding I imagine what is happening is that a render cycle happens with the current props, ie item being blank.
Then it gets rendered again when the props have changed, via setting the item.
One approach to fixing this sort of thing is to create a higher order component that wraps Editor and calls the dispatch action the rendering though is set either to a loading screen or and empty div until item is set. That way you can be assured that Editor will have an item.
But without knowing what editItem does it's sort of hard to know. Maybe you could paste the code for that?
I have a listview component which consists of a number of child listitem components.
Each child listitem have a showSubMenu boolean state, which display a few extra buttons next to the list item.
This state should update in response to a user event, say, a click on the component DOM node.
childcomponent:
_handleClick() {
... mutate state
this.props.onClick() // call the onClick handler provided by the parent to update the state in parent
}
However, it feels somewhat wrong to update state like, as it mutates state in different places.
The other way i figured i could accomplish it was to call the this.props.onClick directly, and move the child state into the parent as a prop instead, and then do change the state there, and trickle it down as props.
Which, if any, of these approaches is idiomatic or preferable?
First of all, I think that the question's title doesn't describe very well what's your doubt. Is more an issue about where the state should go.
The theory of React says that you should put your state in the higher component that you can find for being the single source of truth for a set of components.
For each piece of state in your application:
Identify every component that renders something based on that state.
Find a common owner component (a single component above all the
components that need the state in the hierarchy).
Either the common
owner or another component higher up in the hierarchy should own the
state.
If you can't find a component where it makes sense to own the
state, create a new component simply for holding the state and add it
somewhere in the hierarchy above the common owner component.
However, a Software Engineer at Facebook said:
We started with large top level components which pull all the data
needed for their children, and pass it down through props. This leads
to a lot of cruft and irrelevant code in the intermediate components.
What we settled on, for the most part, is components declaring and
fetching the data they need themselves...
Sure, is talking about data fetched from stores but what im traying to say is that in some cases the theory is not the best option.
In this case i would say that the showSubMenu state only have sense for the list item to show a couple of buttons so its a good option put that state in the child component. I say is a good option because is a simple solution for a simple problem, the other option that you propose means having something like this:
var GroceryList = React.createClass({
handleClick: function(i) {
console.log('You clicked: ' + this.props.items[i]);
},
render: function() {
return (
<div>
{this.props.items.map(function(item, i) {
return (
<div onClick={this.handleClick.bind(this, i)} key={i}>{item} </div>
);
}, this)}
</div>
);
}
});
If, in a future, the list view has to get acknowledge of that state to show something for example, the state should be in the parent component.
However, i think it's a thin line and you can do wathever makes sense in your specific case, I have a very similar case in my app and it's a simple case so i put the state in the child. Tomorrow maybe i must change it and put the state in his parent.
With many components depending on same state and its mutation you will encounter two issues.
They are placed in component tree so far away that your state will have to be stored in a parent component very high up in the render tree.
Placing the state very high far away from children components you will have to pass them down through many components that should not be aware of this state.
THERE ARE TWO SOLUTIONS FOR THIS ISSUE!
Use React.createContext and user context provider to pass the data to child elements.
Use redux, and react-redux libraries to save your state in store and connect it to different components in your app. For your information react-redux library uses React.createContext methods under the hood.
EXAMPLES:
Create Context
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// Use a Provider to pass the current theme to the tree below.
// Any component can read it, no matter how deep it is.
// In this example, we're passing "dark" as the current value.
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
class ThemedButton extends React.Component {
// Assign a contextType to read the current theme context.
// React will find the closest theme Provider above and use its value.
// In this example, the current theme is "dark".
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
}
// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// Assign a contextType to read the current theme context.
// React will find the closest theme Provider above and use its value.
// In this example, the current theme is "dark".
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
REDUX AND REACT-REDUX
import { connect } from 'react-redux'
const App = props => {
return <div>{props.user}</div>
}
const mapStateToProps = state => {
return state
}
export default connect(mapStateToProps)(App)
For more information about redux and react-redux check out this link:
https://redux.js.org/recipes/writing-tests#connected-components