React - Remove prop from child - javascript

I need to remove a prop from a child.
I have a container element which uses a property on it's children to perform some enhancements on the children. That property should be removed from the child before rendering.
<AsyncContainer>
<Button onClick={this.asyncStuff} asyncHandler="onClick"/>
</AsyncContainer>
The asyncHandler property should be removed from the button before rendering.
AsyncContainer uses React.cloneElement(child, properties).
I've tried nulling the asyncHandler property, setting it to undefined and deleting the property from the child.props. It seems that it is impossible to get rid of this property again.

I just ran into this issue. You can just create a new element and use the old element's type and props you want to pass through. I'm not sure if this an anti-pattern or not, I just stumbled on it and it seems to be working well so far.
It should look something like this:
function AsyncContainer(props) {
const child = React.Children.only(props.children)
const { asyncHandler, ...childProps } = child.props
// do asyncHandler stuff
return React.createElement(child.type, childProps)
}

function AsyncContainer(props) {
const child = React.Children.only(props.children);
return React.cloneElement(
child,
{ asyncHandler: undefined }
);
}
How it works
You clone element using React.cloneElement because element is immutable and only way to change its props is to create clone.
Use second React.cloneElement argument to add new props and remove old props. Unneeded props should be assigned with undefined. You need to do this because by default cloned element is cloned with all its props.

As per the comments you cannot modify the props directly as they are immutable.
However, I think I have a simple solution to this problem. I have no idea what library that is or how it works, so this may or may not work. However, this is a general answer to how you would remove a prop before a component gets mounted.
That being said, I would try to create my own component which renders a <Button />:
class MyButtonComponent extends React.Component {
...
render() {
return <Button onClick={this.props.onClickHandler} />;
}
}
Then in the component you want to do your enhancements:
render() {
<AsyncContainer>
<MyButtonComponent onClickHandler={this.asyncStuff} asyncHandler="onClick"/>
</AsyncContainer>
}
This way you maintain your onClick eventlistener on the <Button /> component but you don't pass the illegal asyncHandler prop.
Edit:
Alternatively, you could also do:
class MyButtonComponent extends React.Component {
...
componentWillMount() {
let newProps = this.props;
delete newProps.asyncHandler;
this.setState({properties: newProps}):
}
render() {
return <Button {...this.state.properties} />;
}
}
This will apply all the props (with the spread operator) to <Button /> except for asyncHandler which we delete prior to the component being mounted by creating a copy of the props in state but with asyncHandler removed.
Also check this answer I gave to a similar question.

Related

Access list of siblings in React

Parent element (Board) creates list of children and passes them method to access this list, like this:
export default class Board extends React.Component {
constructor(props) {
super(props);
this.getList = this.getList.bind(this);
const nodes = this.props.data.map(item => (
<BoardItem key={item.id} next={item.next} accessSiblings={this.getList} />
));
this.state = {data: this.props.data, nodes: nodes}
}
getList() {
return this.state.nodes;
}
render() {
return (
<div>
{this.state.nodes}
</div>
);
} }
Then I call update() method and receive this list, filter it and correctly get the required object:
update() {
console.log(this.props.accessSiblings().find(x => x.key == this.props.next));
}
However it returns Symbol(react.element), and I am trying to get such properties as "offsetTop", "offsetHeight" of already rendered element.
Basically, I want to call an event in one element that gets some DOM properties of sibling element(e.g. offsetTop) and changes the state of this sibling.
Is this the correct approach? It feels very hacky and doesn't work at the moment.
You need to use "refs" (see also: how to access a dom element in react).
However, you should avoid working with DOM objects, if possible.
You do need a ref for accessing offsetTop etc., but apart from that you should not pass DOM or ReactElements, but you should only work with state (like "plain javascript" objects) as far as possible, and then render ReactElements (JSX, like <BoardItem ...) as the last step, and never care about DOM elements (React does it for you).
It is also usually not necessary to store ReactElements in variables or state, I suggest to try if you can focus a little bit more on state, and understand JSX more as a way to view the state.

Can somebody explain what makes this code?

I've hot React component which returns input tag. Can you please explain what is going on at the eighth line ref={element => element && (element.onChange = onChange)}? I
import React from 'react';
export default function MyInput({
onChange,
...rest
}) {
return (
<input
{...rest}
ref={element => element && (element.onChange = onChange)}
/>
);
}
React's ref is used to access the DOM directly, and in general is recommended to use as less as possible. The point of functional refs, and keep in mind that they're deprecated, is to assign the element into a class component's variable. e.g.:
Class MyComponent extends Component {
constructor(props) {
super(props);
this.inputRef = null;
}
...stuff
render() {
...stuff
<input ref={element => this.inputRef = element} />
}
}
Then, you could do something like:
this.inputRef.current.style.color = 'blue';
In your case, there is no need for this. If you want to assign the onChange you get from props, just do this:
<input {...stuff} onChange={onChange} />
Read more about React refs here.
As for element && element.onChange, it's designed to make sure that element exists before accessing it's onChange property. Another way to do it, using optional chaining (only avaliable in react-scripts v3.3 and above), is this:
element?.onChange
Refs are used to access DOM elements
The value of ref differs depending on the type of node:
When the ref attribute is used on an HTML element, the ref created
in the constructor with React.createRef() receives the underlying
DOM element as its current property.
When the ref attribute is used on a custom class component, the ref
object receives the mounted instance of the component as its
current.
They are used in cases where we want to change the value of a child component, without making use of props and all. But in your case, i think you have no need to use ref because you simply wants to assign onChange that you received from props.

Style a styled-components within a wrapper

I would like to add an animation and a specific height to a Button that is styled. The thing is, my StyledButton is a wrapper that can render one of multiple pre-styled buttons according to a type prop which are styled React Semantic UI Buttons.
See the CodeSandbox with reproduction here :
https://codesandbox.io/embed/practical-haibt-oz9sl
The thing is it does get the styles from the ActionButton but it does not apply whatever style I put on the const AnimatedButton = styled(StyledButton).
But, if I try the same thing without the wrapper, directly by importing the BaseButton, and creating a AnimatedBaseButton, this one works but
removes the modularity of having a type prop that returns a pre-styled button.
I searched here and on google / github, but there's no issue that reflects this one. I know I could add an animation property on the StyledButton and pass it, but with the real codebase, it's not possible.
Thanks in advance !
EDIT : Added a Codesandbox instead of code example.
Quick fix:
In StyledButton.js:
render() {
const {
content,
icon,
iconPosition,
onClick,
type,
...otherProps // take base props passed through wrapper
} = this.props;
// ...
return (
<ButtonToDisplay
{...otherProps} // spread it firstly here so below props can override
onClick={onClick}
content={content}
/>
);
}
Why it works:
As you can see, styled(comp)'' syntax we use to style our component is actually a HOC component under the hood, which takes in a component and returns another component.
So when you make a wrapper that intercepts between a styled component and the real component, you need to allow props that generated by the library go through that wrapper.
You forgot the ... (spread operator) while destructing this.props
export default class StyledButton extends React.Component {
render() {
// added ... (spread operator)
const {type, ...additionalProps} = this.props
if (type === 'normal') return <NormalButton {...aditionalProps} />
else if (type === 'action') return <ActionButton {...aditionalProps} />
}
}
What is happening here is that styled-component pass the styles in the style prop, but with out the spread operator, you aren't passing it, you are just getting a prop that is called additionalProps.

Accessing props in React constructor

I have a problem when I try to access parent's props value in the child class.
I am trying to initialize the child props with the parent's props.
But the following code shows me empty string of value.
export default class Child extends React.Component {
constructor(props) {
super(props);
this.state = { value: props.value };
}
...
In other class I call this Child as follows:
const issue = this.state.issue;
...
<Child value={issue.number} />
...
I checked the number value before Child class is constructed (it shows me correct values), while it is construced, the value becomes empty..
For test, I used primitive number value, and it works.
Seems like some kind of link to issue.number is disconnected when Child is contructed.
Any clue?
This could easily happen. For example, issue.number becomes a number after AJAX re-rendering. In this case you have to do follow (in your parent component):
render() {
const issue = this.state.issue
if (issue.number) {
return <Child value={issue.number} />
}
}
Ah, I just figured it out.
It is related to Lifecycle.
The number value is not loaded when I render Child.
So, I need to figure it out how to update the number in Child with my intention.

how to pass props to children with React.cloneElement?

I'm trying to pass props to my component children but I have this error : Unknown prop 'user' on tag. Remove this prop from the element.
When looking at documentation and questions, I think I understood that props given to React.cloneElement (second argument) must be DOM recognized properties.
So my question is how to pass props to the component children and make them accessible in this.props ?
Here is my code :
render() {
const { children } = this.props
const { user } = this.state
const childrenWithProps = React.Children.map(children, child =>
React.cloneElement(child, { user })
)
return (
<div>
{ childrenWithProps }
</div>
)
}
edit : the children component's propTypes
ChildrenPage.propTypes = {
user: PropTypes.object
}
export default ChildrenPage
Your code looks fine for me. Usually, React give this warning when you are trying to render DOM element(Not a React Component) with invalid/non-standard DOM attribute.
In your case, this might happen if your children collection has a DOM element. Since user is not a standard DOM attribute, it might fire this warning when you are trying to clone the element with user prop.
You can read more about this error here.
Hope this helps!
Make sure that you are not passing down the children key as props. Below code removes children key from props before passing it down to the children.
let key = 'children';
let {[key]: _, ...newProps} = state;
{React.Children.map(this.props.children, child =>
React.cloneElement(child, {...newProps}))}
This is one of the possible reasons. and, let me know if this solution works. For more, https://facebook.github.io/react/warnings/unknown-prop.html

Categories