Convert to if/else from nested ternary in lit-html - reactjs

how to write below code lit html
I am new to lit html , i try to convert the below code to if/else instead of nested ternary
render() {
return this.html`
${this.data ? this.html`<comp data={this.data}></comp>`
: data1 && data1.test === 'valid' && valueCheck ? this.html`
<comp2 data={this.data2} ></comp2>
`
: this.html`
<comp1 data={this.data1} ></comp1>
`} `;}
Need to convert simple if else

I understand you want to use this to render components. Normally, when I have complex validations if/elses to do, I do them outside the render part and attribute it to a variable.
let dataToRender = undefined;
if(this.data){
dataToRender = this.html`<comp data={this.data}></comp>`
} else if(data1 && data1.test === 'valid' && valueCheck){
dataToRender = <comp2 data={this.data2} ></comp2>
} else {
dataToRender = <comp1 data={this.data1} ></comp1>
}
And, then, on the render function simply:
render(
${dataToRender}
)

You usually want to enumerate the value based on the logic, it's more readable and maintainable in the future if your logic grows.
You can also combine the built-in choose directive to render one of many templates based on the key value.
import { LitElement, html } from "lit";
import { choose } from "lit/directives/choose.js";
enum RenderOptions {
COMP,
COMP_1,
COMP_2
}
class MyElement extends LitElement {
// mocks
data = {}
data1 = { test: "valid" }
data2 = {}
valueCheck = true
render() {
return html`
${choose(this._conditionalRender(), [
[RenderOptions.COMP, () => html`<comp data={this.data}></comp>`],
[RenderOptions.COMP_1, () => html`<comp1 data={this.data1}></comp1>`]
[RenderOptions.COMP_2, () => html`<comp2 data={this.data2}></comp2>`],
],
() => html`<h1>Error</h1>`)}
`;
}
private _conditionalRender(): RenderOptions {
if (this.data) {
return RenderOptions.COMP
}
if (this.data1 && this.data1.test === 'valid' && this.valueCheck) {
return RenderOptions.COMP_2
}
return RenderOptions.COMP_1
}
}
// implementations of...
// comp
// comp1
// comp2

Related

Sorting utility function not working as intended

So I'm creating a web app using react with typescript that calls an api endpoint (it's a yugioh card database https://db.ygoprodeck.com/api-guide/). I should probably also point out that I'm using RTK Query, it might be a caching things I don't know. Specifically for my problem, it's a query that makes a fuzzy name search to see if a user's input text matches any card in the database. The returned array from the api however is not sorted in a way that I desire, so I wanted to fix that with a utility function as I can see myself sorting this way in multiple places.
Given the search "Dark" for example, the api returns all cards where dark appears in the name, regardless of when in the name the word appear. A card could for example be called "Angel of Dark" and it would come before a much more likely search "Dark Magician" because the api returns it in alphabetical order. But I want it to be sorted in a way that cards that starts with the word appears before cards where the word appears later.
I think I've done it in a way that should return it correctly, but alas it doesn't, so I figured the experts could maybe tell me where the code is wrong, if it's in the utility function or if it's with RTKQ or somewhere else where I'm just completely wrong.
Here's the utility function:
import { CardData } from "../models/cardData.interface";
const SortNameBySearch: Function = (search: string, cardData: CardData) => {
const sortedData = [...cardData.data];
sortedData.sort((a, b) => {
// Sort results by matching name with search position in name
if (
a.name.toLowerCase().indexOf(search.toLowerCase()) >
b.name.toLowerCase().indexOf(search.toLowerCase())
) {
return 1;
} else if (
a.name.toLowerCase().indexOf(search.toLowerCase()) <
b.name.toLowerCase().indexOf(search.toLowerCase())
) {
return -1;
} else {
if (a.name > b.name) return 1;
else return -1;
}
});
return sortedData;
};
export default SortNameBySearch;
I've sliced the array to only show the top 3 results and it looks like this, but it's weird because the first one should't even be in this array as it doesn't contain the searched name, which makes me think it might be a caching thing with RTKQ but I'm honestly clueless as how to fix that.
For good measure and better context here's how I'm using it in context
import { FC, useEffect, useRef, useState } from "react";
import { useGetCardsBySearchQuery } from "../../../../services/api";
import { SearchResultsProps } from "./SearchResults.types";
import SortNameBySearch from "../../../../utils/SortNamesBySearch";
import { Card } from "../../../../models/card.interface";
const SearchResults: FC<SearchResultsProps> = ({ search }) => {
const [searched, setSearched] = useState("");
const [typing, setTyping] = useState(false);
const searchRef = useRef(0);
useEffect(() => {
clearTimeout(searchRef.current);
setTyping(true);
if (search !== null) {
searchRef.current = window.setTimeout(() => {
setSearched(search);
setTyping(false);
}, 100);
}
}, [search]);
const { data, isLoading, isError, isSuccess } = useGetCardsBySearchQuery(
searched,
{ skip: typing }
);
const results =
!typing && isSuccess
? SortNameBySearch(searched, data)
.slice(0, 3)
.map((card: Card) => {
return (
<p key={card.id} className="text-white">
{card.name}
</p>
);
})
: null;
if (isError) {
return <div>No card matching your query was found in the database.</div>;
}
return (
<>
{isLoading ? (
"loading..."
) : typing ? (
"loading..."
) : (
<div className="bg-black relative">{results}</div>
)}
</>
);
};
export default SearchResults;
Given that I'm still new to this whole thing there are likely other issues with my code and of course I want to improve, so if there's something in there that just doesn't make sense please do let me know.
Looking forward to see your answers.
-Benjamin
Here is your code changed to do as you said in the last comment.
Not fully tested, but it should be something like this, so I guess this should be enough to get you going.
import { CardData } from "../models/cardData.interface";
const SortNameBySearch: Function = (search: string, cardData: CardData) => {
const sortedData = [...cardData.data];
const dataWithSearchAtIndex0: string[] = [];
const dataWithSearchAtOtherIndexes: string[] = [];
for(const data of sortedData) {
const searchIndex = data.toLowerCase().indexOf(search.toLowerCase());
if (searchIndex === 0) {
dataWithSearchAtIndex0.push(data);
} else if (searchIndex > 0) {
dataWithSearchAtOtherIndexes.push(data);
}
}
dataWithSearchAtIndex0.sort((a, b) => sortIgnoringCase(a, b));
dataWithSearchAtOtherIndexes.sort((a, b) => sortIgnoringCase(a, b));
return [...dataWithSearchAtIndex0, ...dataWithSearchAtOtherIndexes];
}
const sortIgnoringCase: Function = (a: string, b: string) => {
const lowerCaseA = a.toLowerCase(); // ignore upper and lowercase
const lowerCaseB = b.toLowerCase(); // ignore upper and lowercase
if (lowerCaseA < lowerCaseB) {
return -1;
}
if (lowerCaseA > lowerCaseB) {
return 1;
}
// a and b must be equal
return 0;
};
export default SortNameBySearch;

How to show dinamically a react component

I'm working in a React project and I need to render some Components based on a layout.
--asumme you have an array that tells you the components you need to render:
Layout1 = ['Events', 'Photo', 'News']
--And a function that, depends on the module, render the especific component with some data:
layout1.forEach(function(layout) {
someFuncions(layout, somedata);
});
someFunction = (layout, data) => {
layout.forEach( function(Comp) {
if(Comp == 'Events') {
return (<Events module-data={data} />)
} else if(Comp == 'Photo') {
return (<Photo module-data={data} />)
} else if(Comp == 'News') {
return (<News module-data={data} />)
}
});
}
-- Since it is possible to have many components I want to find a way to avoid the "if" function to determine what component should I render.
How can I achieve this? Thanks a lot guys
You can use a switch similar syntex but maybe a little cleaner.
switch(comp) {
case 'Events':
return (<Events module-data={data} />)
case 'Photo':
return (<Photo module-data={data} />)
case 'News':
return (<News module-data={data} />)
}
You can pass the component in the array directly.
import Events from 'path/to/Events'
import Photo from 'path/to/Photo'
import News from 'path/to/News'
Layout1 = [Events, Photo, News]
And then wherever you call the function.
From your original question, it seems like the layout1 in your question is an array of Layout1 like arrays.
layout1.forEach(function(layout) {
someFuncions(layout, somedata);
});
someFunction = (layout, data) => {
// returning from forEach won't result in anything. you probably want to map
layout.forEach( function(Component) {
return (<Component module-data={data} />)
})
}
I would approaching it by creating a map for your components, then doing a lookup when rendering.
const components = {
Events, // Shorthand for 'Events': Events
Photo,
News
};
const layout = ['Events', 'Photo'];
const someFunction = (layout, data) =>
layout.map((name) => {
const Component = components[name];
return <Component module-data={data} />;
});
What about using a javascript object that maps the layout to the desired component? This way, it's clear that you intend to render a different component depending on the layout.
See the following example:
const someFunction = (layout, data) => {
const componentMap = {
'Events': <Events module-data={data} />,
'Photo': <Photo module-data={data} />,
'News': <News module-data={data} />
}
return componentMap[layout];
}

Managing React states correctly giving strange error

I'm trying to make a hangman game using React.
Here is the code: https://pastebin.com/U1wUJ28G
When I click on a letter I get error:
Here's AvailableLetter.js:
import React, {useState} from 'react';
import classes from './AvailableLetter.module.css';
const AvailableLetter = (props) => {
const [show,setShow]=useState(true);
// const [clicked, setClicked]=useState(false);
// const [outcome,setOutcome]=useState(false);
// if (show)
// {
// setClicked(true);
// }
const play = (alphabet) => {
const solution = props.solution.split('');
if (solution.indexOf(alphabet)<0)
{
return false;
}
else
{
return true;
}
}
if (!show)
{
if (play(props.alphabet))
{
props.correct();
// alert('correct');
}
else
{
props.incorrect(); // THIS CAUSES ERROR!!!!
// alert('wrong');
}
}
return (
show ? <span show={show} onClick={()=>{setShow(false)}} className={classes.AvailableLetter}>{props.alphabet}</span> : null
);
}
export default AvailableLetter;
I suspect the error is caused by not managing state properly inside AvailableLetter.js. But I don't know why the error is showing pointing to Hangman.js.
Here's what's pointed to by props.incorrect:
Game.js:
guessedIncorrectHandler = (letter) => {
const index = this.state.availableLetters.indexOf(letter);
let newAvailableLetters = [...this.state.availableLetters];
newAvailableLetters.splice(index,1);
let newUsedLetters = [...this.state.usedLetters];
newUsedLetters.push(letter);
const oldValueLives = this.state.lives;
const newValueLives = oldValueLives - 1;
this.setState({
usedLetters: newUsedLetters,
availableLetters: newAvailableLetters,
lives: newValueLives
});
};
Applied fix kind user suggested on
lives: newValueLives < 0 ? 0 : newValueLives
But now when I lick on a letter I get multiple letters get added randomly to correct letters and incorrect letters on a single click.
If I interrupt guessedIncorrectHandler with return true:
guessedIncorrectHandler = (letter) => {
const index = this.state.availableLetters.indexOf(letter);
let newAvailableLetters = [...this.state.availableLetters];
newAvailableLetters.splice(index,1);
let newUsedLetters = [...this.state.usedLetters];
newUsedLetters.push(letter);
const oldValueLives = this.state.lives;
const newValueLives = oldValueLives - 1;
console.log('[newValueLives] ',newValueLives); return true; // HERE INTERRUPTED
this.setState({
usedLetters: newUsedLetters,
availableLetters: newAvailableLetters,
lives: newValueLives < 0 ? 0 : newValueLives
});
};
Now when I click 'a' for example (which is correct since solution is 'apple') it behaves correctly.
When app is running I get warning:
Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
in AvailableLetter (at Letters.js:8)
...
Probably the cause for this warning is inside AvailableLetter.js inside return:
show ? <span show={show} onClick={()=>{setShow(false)}} className={classes.AvailableLetter}>{props.alphabet}</span> : null
Tried to set up codesandbox here: https://codesandbox.io/s/fast-breeze-o633v
The issue can occur if the lives value is a negative number.
Try to secure that possibility:
this.setState({
usedLetters: newUsedLetters,
availableLetters: newAvailableLetters,
lives: newValueLives < 0 ? 0 : nevValueLives, // if lives is negative, assign 0
});

How to place return code in a function: React

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}
/>);
}

how to render components reading it from json

Example I have some json data and I want to render components as described in json
const myJson = {
hbox : {icon1 : "image",file_name : "text", icon2 : "image" }
}
import React from 'react';
import { Image, Text, View } from 'react-native';
const components = {
icon: Image,
text: Text
};
function SimpleComponent(componentType) {
const SpecificStory = components[componentType];
return <SpecificStory />;
}
how to render this components inside View component recursively
function readJson (json) {
let arr = Object.keys(json);
for (let i in arr){
if(typeof json[arr[i]] == 'string'){
SimpleComponent(json[arr[i]]);
} else { readJson(json[arr[i]])}
}
}
render () {
return ( <View>{readJson(myJson)}</View>)
}
is it possible like I wrote above
You could make a landing component, suppose GeneralComponent.
Use something like this: <GeneralComponent type="ComponentType" data={allDataYouNeedInCop}/>
In onCreate of the GeneralComponent, check what is written in props.type, and act accordingly.
You can do it but you will have a lot of cases.
1- Recursive function
So about recursive function (readJson) you can use map function instead.for exemple :
let result = myJson.map( (elem) => {
return SimpleComponent(elem);
});
render () {
return ( <View>{result}</View>)
}

Resources