React : cannot add property 'X', object is not extensible - javascript

I am receiving props in my component. I want to add a property 'LegendPosition' with the props in this component. I am unable to do that. Please help me with this.
I have tried this code yet but no success:
var tempProps = this.props;
tempProps.legendPosition = 'right';
Object.preventExtensions(tempProps);
console.log(tempProps);

You can't modify this.props. Here tempProps is reference of this.props so it does not work. You should create a copy of the props using JSON.parse() and JSON.stringify()
var tempProps = JSON.parse(JSON.stringify(this.props));
tempProps.legendPosition = 'right';
Object.preventExtensions(tempProps);
console.log(tempProps);
For a better and efficient way to deep clone object see What is the most efficient way to deep clone an object in JavaScript?

props is not mutable, you cant "add" anything to them. if you want to "copy" them then you need to do
const tempProps = {...this.props};
And the only reason i can see you needing to add more props is to pass it down to a child, but you can do that without adding it to the props object.
EDIT: add props with extra prop
<Component {...this.props} legendPosition="right" />

I want to send the updated props to a child component, If it is possible without copying or cloning to a new object, Please help me how can I achieve this.
Solution is as simple as:
<ChildComponent {...this.props} legendPosition="right" />
Of course legendPosition will be available in ChildComponent by this.props.legendPosition.
Of course earlier this.props can contain already legendPosition property/value which will be overwritten by defined later - order matters.
Of course there can be many spread operators - for multiple properties, logic blocks ... whatever:
const additonalProps = {
legendPosition: 'right',
sthElse: true
}
return (
<ChildComponent {...this.props} {...additonalProps} />
)

below in tempProps object spread operator copy your this.props object and after spread operator we can add new object property or we can update existing object property.
var tempProps = {
...this.props,
tempProps.legendPosition = 'right' //property you want to add in props object
};

Your answer is in this line.
var tempProps = this.props;
this.props is immutable that means you can not change the property value in function.
you can use a this.state so you can modify it in your function
props --- you can not change its value.
states --- you can change its value in your code, but it would be active when a render
happens.

For anyone getting this error with jest, make sure to mock your component this way:
jest.mock('component/lib', () => ({
YourComponent: jest.fn()
}))
Then in your test you can mock the implementation:
(YourComponent as jest.Mock).mockImplementation(() => <div>test</div>);
Hope this works

You can define default values for your props properly by assigning to the special defaultProps property:
https://reactjs.org/docs/typechecking-with-proptypes.html
class Greeting extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
// Specifies the default values for props:
Greeting.defaultProps = {
name: 'Stranger',
};

Related

React clone element to modify a child component and keep ref in a functional component

I used to have refs in my component when rendering, and it worked:
// props.children is ReactElement<HTMLDivElement>[]
const [childRefs] = useState<RefObject<any>[]>(props.children.map(() => createRef()));
// working code, all the variables (props, childRefs etc) are defined earlier in scope
return <div {...props}>
{
props.children.map((c, i) => <div key={i} ref={childRefs[i]}>{c}</div >)
}
</div>
Basically I'm using the refs to imperatively directly set some transforms to style as mouse moves on JS mousemove event.
However, I now need to inject some CSS class into the passed component automatically. I've created a component (named Layer) that takes the child element, clones it, sets the CSS class, and returns it:
function Layer(props:LayerProps){
const elem = cloneElement(props.children, {...props.children.props,
className: styles['layer']
});
return elem;
}
I've updated the main component like this too:
return <div {...props}>
{
props.children.map((c, i) => <Layer key={i} ref={childRefs[i]}>{c}</Layer>)
}
</div>
However now my refs aren't passed now, understandibly, as the Layer functional component can't have a ref (as it's a function). When I try to set the ref to Layer it can't, and have this error (understandably):
(property) ref: React.RefObject<any>
Type '{ children: ReactElement<HTMLDivElement, string | JSXElementConstructor<any>>; key: number; ref: RefObject<any>; }' is not assignable to type 'IntrinsicAttributes & LayerProps'.
Property 'ref' does not exist on type 'IntrinsicAttributes & LayerProps'.ts(2322)
If I try to forward the ref using forwardRef it doesn't have anything to set that ref to as I'm just modifying the passed child element and returning it, not returning a new element like <div>...</div> that I could forward ref to like <div ref={forwardedRef}>...</div>.
How can I modify the CSS class and keep a ref to the object? I know how to do each one (if I just need to add class I cloneElement, if I just need to ref it I use forwardRef and pass it to the child component in JSX) yet I couldn't figure out being able to do both at the same time.
How can I do it?
Okay, after a bit digging and experimenting I've realized I can give ref "prop" (which isn't technically a real prop, but anyway) in cloneElement just like any prop.
I've ended up forwarding ref to the functional component, and provided ref as a prop to the newly cloned element, and it worked.
Yet, the TypeScript definitions are incorrectly flagging ref property as non-existent while it works perfectly. I needed it to cast the props to any to silence the linter error though:
const Layer = forwardRef((props:LayerProps, ref:any) => {
const elem = cloneElement(props.children, {...props.children.props,
className: `${props.children.props.className ?? ''} ${styles['layer']}`,
ref: ref
} as any); // as any fixes ref complaining
return elem;
});
And in many component:
return <div {...props}>
{
props.children.map((c, i) => <Layer key={i} ref={childRefs[i]}>{c}</Layer>)
}
</div>

Why does props in props.functionName only need to be called sometimes?

In react, specifically referencing the hooks/functional paradigm, when do you need to use props.functionName? As far as I can tell, if the functions are named the same, you can omit props as follows:
Parent.js
...
<Child functionName={functionName}/>
...
Child.js
...
functionName();
...
However, if the name changes, props must be referenced as follows:
Parent.js
...
<Child otherName={functionName}/>
...
Child.js
...
props.otherName();
...
Am I correct? If so, why use the second design pattern? (Perhaps just to call out that the function comes from a parent and isn't defined at the child level. Or maybe some other reason?)
No. The name you use depends entirely on how the child component processes the props it is passed. How the parent component determines what value to pass is completely irrelevant.
If the props are placed in a variable named props then you need to access them through the object stored in the variable.
const Child = (props) => {
props.functionName();
...
}
If the first argument is destructured, then each property is stored in its own variable.
const Child = ({functionName}) => {
functionName();
...
}
If the value is copied to another variable inside the component then you can also use the variable it was copied to.
const Child = (props) => {
const functionName = props.functionName();
functionName();
...
}
False, It is Destructuring Props in React;
Destructuring is a convenient way of extracting multiple values from data stored in (possibly nested) objects and Arrays
props is an object so we can use the Destructuring
Destructuring gives access to the use of props in a more readable format and discards the need for props for every property.
<Child functionName={functionName} name={name}/>;
const child = (props)=>{
// you need to write props.functionName
props.functionName();
const thisName = props.name
};
const child =({functionName,name})=>{
// you dont need to write *props.*
functionName();
const thisName = name
};
When interfacing with a child component, the name that you use for something in your parent component may be different from the prop that the child component requires.
Just for example, say that your parent component has something to do with school, and you have:
const [studentId, setStudentId] = useState();
const [parentId, setParentId] = useState();
But you also have a child component for students that expects an id prop. Then, to pass down the studentId to it, you'd do:
<Student id={studentId} />
And in that child component, you'd reference it by doing props.id.
You wouldn't want to do
const [id, setId] = useState();
in the parent component, and then
<Student id={id} />
when passing it down because then it wouldn't be clear in the parent component which ID that stateful value referred to - you'd probably have to add a comment.
If so, why use the second design pattern?
Sometimes, like in the situation I just described, the child component isn't designed with all identifiers used in the parent component in mind - which is perfectly reasonable, that's what allows for so many things in programming to be modular (and adaptable and useful). As a result, sometimes you need a prop or variable to have one name in the parent component, and another in the child component, which requires the
<Child otherName={functionName}/>
approach.

Is defaultProps valid in ReactJs

I am following a tutorial and they set a static object named defaultprops and after setting it this.props could be used in the same component, Why is this possible, I mean what's the function of the static defaultProps and is it a bulit in function in React.
class TestComponent extends Component {
static defaultprops = {
food: ["goatmeat", "yam"]
}
render() {
let categories = this.props.defaultprops.food.map(foods => {
return <option key={foods}>{foods}</option>
});
let {test} = this.props;
return (
<p>
{this.props.test}
</p>
);
};
}
Default props are nice to not have to specify all of the props when passing them to a component. Just as the name implies, it allows you to set nice defaults for your props which will be used in the even that overriding values are not passed in. Please keep in mind that leaving out the prop will result in the default value being used, whereas passing in null will result in the null value to be used.
More info may be found here.
Edit
To answer the questions asked more explicitly:
This is possible because this is how React works. This is in-built functionality for programmer and logical convenience.
For the TestComponent in your example, imagine that it is used in another component. If you just use <TestComponent />, the component will have a food value of ["goatmeat","yam"]. However, you may always override this as you wish by passing in a different value for the prop when calling it. For example, you could use <TestComponent food={["cheese", "eggs", "cabbage"]}/>. This will result in this instance of the component having the food value of ["cheese", "eggs", "cabbage"].
I think it is also a good point to note that it should be defaultProps and not defaultprops because I am fairly certain capitalization matters but if anyone would like to correct me I would be happy to redact this point.

React - Remove prop from child

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.

Local copy of React prop is read-only

Let's say I have two Components. The parent is passed an Object as a property, which it then copies into local data store. It has a function to update this local store, which gets passed down to a child. Here is my parent Component:
const Parent = ({stuff}) => {
const store = {
stuff: Object.assign({}, stuff);
}
const updateStuff = (thing, property) => store.stuff[thing].property = thing;
return <Child stuff={stuff} updateStuff={updateStuff} />
}
The Child Component has a similar structure -- it makes a copy of stuff, and mutates its copy of stuff on an <input>'s onChange. It then passes its own updated copy of stuff to the updateStuff function it received, in order to mutate the Parent's copy of the prop. Here is the Child.
const Child = ({stuff, updateStuff}) => {
const stuff = {
thing1: Object.assign({}, stuff.thing1),
thing2: Object.assign({}, stuff.thing2)
}
const setProp = event => {
const update = event.target.value;
stuff.thing1.prop = update;
updateStuff(thing1, stuff.thing1.prop)
}
return (
<div>
<input id="thing1" onChange={setProp} />
<input id="thing1" onChange={setProp} />
</div>
)
}
Notice, I've used Object.assign to basically clone the stuff prop or its child properties, as the case necessitates. The reason for this: a React prop is read-only, and thus I need to create a clone to make changes on before passing it back to mutate the app's state (not shown here)/
Now, this works on the Child component -- setProp mutates the correct property of stuff, confirmed by logging to the console. However, when the method gets to updateTeam, I get an error message : Uncaught TypeError: Cannot assign to read only property 'side' of object '#<Object>'
Yet, both Components use the same principle: I am not mutating the prop, but rather I am mutating a locally-stored clone of the prop. Why does this work for Child, but not for Parent?
Object.assign only does a shallow copy of the prop Object.assign Reference. In order to make a true deep copy of the prop (and get rid of the error) you can do a deep copy with newStuff: JSON.parse(JSON.stringify(stuff)). Glad this helped!
The real reason behind this is given in an example:
let original = {
name: 'Test',
nestedObj: {
(...some properties)
}
}
In the example above, the original object property 'name' is a new copy but the nested object is still a reference to the original. This way when you try and edit the part of the nested object it references the original and yells that it is immutable.
Try changing const to let for your local copies

Categories