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.
Related
I'm new to react and I'm learning how to fetch data from an api once the user clicks on a button. Somehow, I've gotten everything to work, but I don't think I'm using the library properly.
What I've come up with:
class App extends React.Component {
state = {
recipe: null,
ingredients: null
}
processIngredients(data) {
const prerequisites = [];
const randomMeal = data.meals[0];
for(var i = 1; i <= 20; i++){
if(randomMeal['strIngredient' + i]){
prerequisites.push({
name: randomMeal['strIngredient' + i],
amount: randomMeal['strMeasure' + i]
})
}
}
this.setState({
recipe: data,
ingredients: prerequisites,
})
console.log(prerequisites[0].name)
}
getRecipes = () => {
axios.get("https://www.themealdb.com/api/json/v1/1/random.php").then(
(response) => {
this.processIngredients(response.data);
}
)
}
render() {
return (
<div className="App">
<h1>Feeling hungry?</h1>
<h2>Get a meal by clicking below</h2>
<button className="button" onClick={this.getRecipes}>Click me!</button>
{this.state.recipe ? <Recipe food={this.state.recipe}
materials={this.state.ingredients} /> : <div/>}
</div>
);
}
}
The way I'm checking the value of state.recipe in render() and invoking the Recipe component, is it correct? Or does it seem like hacked together code? If I'm doing it wrong, what is the proper way of doing it?
It's really a minor nit, but in this case you can use an inline && logical operator since there's nothing to render for the "false" case:
{this.state.recipe && <Recipe food={this.state.recipe} materials={this.state.ingredients} />}
Checkout https://reactjs.org/docs/conditional-rendering.html for more info.
I want to add two classes to a div, the first class should always apply but the second class only applies when the condition is true else it is not applied. I tried the piece of code below but I don't seem to get the desired result. something could be wrong with my approach.
<div className={[style.carousalItem, pics.indexOf(item) === this.state.current ? style.currentItem : ''].join(" ")}>
Using ES6:
className={`${style.carousalItem} ${pics.indexOf(item) === this.state.current ? style.currentItem : ""}`}
or
className={`${style.carousalItem} ${pics.indexOf(item) === this.state.current && style.currentItem}`}
You can do it like this in a functional component :
const handleClass = () => {
if(yourCondition){
return "class1"
}
// Otherwise return class2
return "class2"
}
const cls = handleClass()
<div className={`${cls}`}> ... </div>
In a class component :
handleClass = () => {
if(yourCondition){
return "class1"
}
// Otherwise return class2
return "class2"
}
const cls = handleClass()
<div className={`${cls}`}> ... </div>
I build a 3D product configurator and a problem appeared. I am pretty new to React wherefore I can't figure out how to solve it.
My app structure is as follows:
App.js
- Scene3D
- Options
- Scene3D
- CheckoutBar
All changes can be made by the user by clicking different buttons in the option section. One of them is to change the product model. Each product model provides different options. All options are stored in an object in App.js.
My thought was to create an event "change-model" which triggers the render function of App.js again with the choosen model. Unfortunately this doesn't work. The option section won't be updated. The new option data won't get passed to all component underlying App.js.
How is it possible to solve that?
I appreciate your help. Thank you!
App.js
constructor(props) {
super(props)
this.state = {
configurations: {
standard: require('./configurations/standard.json'),
second: require('./configurations/standard.json'),
third: require('./configurations/third.json')
}
}
}
componentDidMount() {
window.addEventListener('change-model', this.render)
this.loadScripts()
}
render = e => {
return (
<div className="proconf">
<Scene3d defaultConfig={this.getDefaultConfig()} />
<Controls configuration={this.state.configurations[e && e.detail.model ? e.detail.model : "standard"]} />
<CheckoutBar defaultConfig={this.getDefaultConfig()} prices={this.getPrices()} />
</div>
)
}
Controls.js
emitModelChangeEvent(modelName) {
let event = new CustomEvent('change-model', { detail: {
model: modelName
}})
window.dispatchEvent(event)
}
createOptions = (options) => {
let optionStorage = []
for (var x in options) {
this.emitModelChangeEvent(value)
this.setState(prev => ({
productConfig: {
...prev.productConfig,
model: value
}
}))
let buttonStorage = []
for (var y in options[x].values) {
buttonStorage.push(<div onClick={(e) => { e.preventDefault(); emitChangeEvent(this, valueKey) }}>{valueInfo}</div>)
}
optionStorage.push(<div>{buttonStorage}</div>)
return optionStorage
}
render() {
return (
<div>
<button>
{this.props.stepName}
</button>
<div>
{this.createOptions(this.props.options)}
</div>
</div>
)
}
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
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}
/>);
}