I'm new to react and its design pattern, so i tried writing a two way binding and create an onChange for my class and its child, and i was wondering if this is a good thing to do and if its the correct design pattern
I have done something like this :
class A extends Component {
state = {
BClassProperties: {
goldAmount: '1',
silverAmount: '2'
}
}
onBChange = (goldAmount, silverAmount) => {
const oldBClassProperties = this.state.BClassProperties;
let newBClassProperties = {...oldBClassProperties};
newBClassProperties.goldAmount = goldAmount;
newBClassProperties.silverAmount = silverAmount;
this.setState({BClassProperties: newBClassProperties})
}
render () {
return (
<B
goldAmount = {this.state.BClassProperties.goldAmount}
silverAmount = {this.state.BClassProperties.silverAmount}
onChange={this.onBChange}
/>
)
}
}
class B extends Component {
onGoldChange = (event) => {
this.props.onChange(event.target.value, this.props.silverAmount);
};
onSilverChange = (event) => {
this.props.onChange(this.props.goldAmount, event.target.value);
};
render () {
return (
<React.Fragment>
<input placeholder={"gold"} value={this.props.goldAmount} onChange={this.onGoldChange} />
<input placeholder={"silver"} value={this.props.silverAmount} onChange={this.onSilverChange} />
</React.Fragment>
)
}
}
Is this okay?, are there any better alternatives ?
Thanks.
Yes. You are doing it correctly, but your are passing goldAmount & silverAmount values incorrect.
<B
goldAmount = {this.state.goldAmount}
silverAmount = {this.state.silverAmount}
onChange={this.onBChange}
/>
Correct way is,
<B
goldAmount={this.state.BClassProperties.goldAmount}
silverAmount={this.state.BClassProperties.silverAmount}
onChange={this.onBChange}
/>
Also in your B component, make your input controlled,
<input
placeholder={'gold'}
value={this.props.goldAmount} //This makes your input controlled
onChange={this.onGoldChange}
/>
Demo
Related
Here is my component into which component is rendered
const BasicInfo=({
product,
handleChange,
handleTags,
productData,
deleteTags,
categories,
customProduct,
handleCustomChange,
setCustomProduct,
})=>{
function increasenumber() {
var hello = numberoffields;
hello = hello + 1;
setNumberoffields(hello);
setCustomProduct((arr) => [...arr, { typeoffield: "", label: "" }]);
}
const DisplayCustomInput = () => {
if (product.customCategory.includes(product.category)) {
return (
<div>
<div>
<button onClick={increasenumber}>Add input field</button>
</div>
<DisplayInputs
numberoffields={numberoffields}
customProduct={customProduct}
handleCustomChange={handleCustomChange}
/>
</div>
);
} else {
return null;
}
};
return (
<DisplayCustomInput />
)
}
My DisplayInputs looks like
export default function DisplayInputs({
numberoffields,
customProduct,
handleCustomChange,
}) {
const rows = [];
for (let index = 0; index < numberoffields; index++) {
console.log(index);
rows.push(
<div key={index}>
<label htmlFor="label">
Enter label for input field for accepting data
</label>
<input
value={customProduct[index] ? customProduct[index].label : ""}
onChange={handleCustomChange(index, "label")}
id="label"
/>
</div>
)
}
return rows
}
Whenever a character if fed to the input field it loses focus and if I use DisplayCustomInput as jsx instead of component then on increasing numberoffields the whole page gets reload.
You can use a ref in React. You initial her in the child and you call something like nomRef.current.focus on useEffect in the parent, useEffect with dependency the value that the input change.
One thing more :
onChange={() => handleCustomChange(index, "label")}
and not onChange={handleCustomChange(index, "label")}
(because without () => you execute the function on mount.
If you need about ref : https://fr.reactjs.org/docs/refs-and-the-dom.html
Say me if it's good for you
I have developed a Component clickable.
and I want to make it disabled sometimes.
const MyComponent = ({disabled}) => {
return (
<button onClick={!disabled&&()=>console.log("CLICKED")}>btn</button>
)
}
const MyApp = () => {
return (
<MyComponent disabled={true} />
)
}
but it doesn't work.
and I have one solution what I don't want to use with.
const MyComponent = ({disabled}) => {
function handleClick(){
if(!disabled){
console.log("CLICCKED");
}
}
return (
<button onClick={handleClick}>btn</button>
)
}
Is there any solution better than what I found?
Why don't you use simple disabled property of button,
<button onClick={()=>console.log("CLICKED")} disabled={disabled}>btn</button>
So I am practicing React and wanted to display a "Arya's kill list" ], I wanted to make it possible to update it. So in my ToKill component when you double click on a character it shows inputs with values. But it is not possible to update them.
I wrote a function in my main App component it looks like this :
const toKillPpl = { ...this.state.toKill }
toKillPpl[index] = updatedToKill
this.setState({ toKillPpl })
}
next I pass it to ToKillList component with a state :
doubleClick = {this.doubleClickHandler}
deleteToKill = {this.deleteToKillHandler}
backBtn = {this.backBtnHandler}
state = {this.state}
toKillState = {this.state.toKill}
update = {this.toKillUpdate}
/>
in my ToKillList component I map over my state and I pass this function with a state of a person (toKillPerson) :
const ToKillList = (props) => props.state.toKill.map((toKill, index) => {
return <ToKill
double ={() => props.doubleClick(index)}
formDouble={toKill.formDouble}
click ={() => props.deleteToKill(index)}
backBtn ={() => props.backBtn(index)}
key={index + toKill.name}
index={index}
toKillPerson ={props.toKillState[index]}
update={props.update}
name={toKill.name}
cause={toKill.cause}
img={toKill.img}
/>
})
Finally in my ToKill component I write a function "handleChange" :
handleChange = (e) => {
const updatedToKill = {
...this.props.toKillPerson,
[e.currentTarget.name]: e.currentTarget.value
}
this.props.update(this.props.index, updatedToKill)
}
And here are inputs:
<input
type="text"
name="name"
className="hero-name"
onChange={this.handleChange}
value={this.props.name}
/>
<input
type="text"
name="img"
onChange={this.handleChange}
value={this.props.img}
/>
<input
type="text"
name="cause"
className="hero-cause"
onChange={this.handleChange}
value={this.props.cause}
/>
And it doesn't work. Is it a good approach, or I messed it up completely?
In case I wasn't clear here is a github repo: https://github.com/jakubmas/Aryas-Kill-List
Two correction in update method in your code.
1) You are not correctly copying over object,
const toKillPpl = { ...this.state.toKill }
This creates a shallow copy, you need deep cloning for this. You could either use JSON.strigify or lodash deepClone method.
2) You are not updating toKill state which is being passed to child components.
Here is the updated method:
toKillUpdate = (index, updatedToKill) => {
// const toKillPpl = JSON.parse(JSON.stringify(this.state.toKill)); // use this
const toKillPpl = _.cloneDeep(this.state.toKill); // or this
toKillPpl[index] = updatedToKill;
this.setState({ toKill: toKillPpl });
};
Here is the working codesandbox link
Hope that helps!!!
Another way to do this is that you could import immutability-helper (https://github.com/kolodny/immutability-helper), and use it to update state.toKill without mutating it:
import update from 'immutability-helper';
// Assuming 'updateToKill' input is an object...
handleUpdate = (index, updatedToKill) => {
this.setState(prevState => ({
toKill: update(prevState.toKill, {
[index]: {
$set: updatedToKill,
},
}),
}));
};
I currently have a react project I'm working on. My render method looks like this going into my return method:
render() {
let elements = [];
this.dropdownCounter().forEach(item => {
if(item != "attributeProduct") {
console.log('setting');
elements.push(
<Dropdown
title={this.state[item][0]['title']}
arrayId={item}
list={this.state[item]}
resetThenSet={this.resetThenSet}
/>
);
}
});
this.state.attributeProduct.map(attributeItem => {
elements.push(
<Dropdown
title={attributeItem.name}
arrayId='attributeMetaProduct'
list={
this.state.attributeMetaProduct.filter(metaItem => metaItem.attribute_id == attributeItem.ID)
}
resetThenSet={this.resetThenSet}
/>
);
});
return (
I have a lot of code going on in the render area due to different drop downs dependent on other methods. Is there a way that I can do something like this instead?
render() {
allMyPrereturnStuff()
return()
}
Then just place all this code in allMyPrereturnStuff()? I've tried creating this function and passing everything there but it doesn't work due to all the "this". Any ideas?
Yes, you can easily drop in normal javascript expressions into JSX:
return (
<div>
{this.renderStuff()}
{this.renderOtherStuff()}
{this.renderMoreStuff()}
</div>
);
You can even base it on flags:
const shouldRenderMoreStuff = this.shouldRenderMoreStuff();
return (
<div>
{this.renderStuff()}
{this.renderOtherStuff()}
{shouldRenderMoreStuff ? this.renderMoreStuff() : null}
</div>
);
Do note that it is often an anti-pattern to have render* methods in your components other than the normal render method. Instead, each render* method should probably be its own component.
Don't forget to bind your allMyPrereturnStuff() method in the constructor so "this" will work inside it.
constructor(props) {
super(props);
// ... your existing code
this.allMyPrereturnStuff = this.allMyPrereturnStuff.bind(this);
}
allMyPrereturnStuff = (params) => {
// ... all the code
}
However, you might want to consider breaking out the code to components, which is more Reacty way to do things.
For example, you could refactor this
this.state.attributeProduct.map(attributeItem => {
elements.push(<Dropdown
title={attributeItem.name}
arrayId='attributeMetaProduct'
list={
this.state.attributeMetaProduct.filter(metaItem => metaItem.attribute_id == attributeItem.ID)
}
resetThenSet={this.resetThenSet}
/>);
});
To something like (somewhat pseudocody):
const DropdownList = (props) => {
return (<Dropdown
title={props.attributeItem.name}
arrayId='attributeMetaProduct'
list={props.list}
resetThenSet={props.resetThenSet}
/>);
}
And in the original component's render function, have something like
render() {
return (this.state.attributeProduct.map(attributeItem => {
<DropdownList attributeItem={attributeItem}
list={ this.state.attributeMetaProduct.filter(metaItem => metaItem.attribute_id == attributeItem.ID) }
resetThenSet={this.resetThenSet}
/>);
}
I have a render function as shown below:
private render(): JSX.Element {
return (
<div>
{this.props.x && this.state.y &&
<DefaultButton onClick = { this.onDeleteButtonClick } >
Delete
</DefaultButton>
}
</div>
);
}
Only when the 2 conditions this.props.x and this.state.y are true, then display the button.
Similarly,
class TitleContainer extends React.Component {
private isPageEnabled: boolean = false;
private isSubmitter: boolean = false;
render() {
return (
<div>
{this.isPageEnabled && this.isSubmitter &&
<div>
<br />
<SubmitAppFormContainer />
</div>
}
</div>
);
}
}
Only when the 2 conditions this.isPageEnabled && this.isSubmitter are true, then display SubmitAppFormContainer.
How do I write a test for the same? Please let me know. Thanks!
I assume that you know how to write tests in general. The tricky part here is to write classes, that are easily testable. In your first case it could be simple:
it('shows DefaultButton', () => {
const shallowRenderer = new ShallowRenderer();
shallowRenderer.render(<Component
x={true}
/>);
const result = shallowRenderer.getRenderOutput();
expect(result).toMatchSnapshot();
});
Create 2 tests, one time passing x = true, the other one passing x = false, so you test both cases. You just have to somehow set the y-state to true.
In your second case TitleContainer it seems more complicated. How can the values of isPageEnabled and isSubmitter be modified? If it's not possible from the outside, it can not be tested properly. Use props if possible.