How to simplify the conditions in template to avoid multiple repetitions - reactjs

I have a scenario like showing text as well adding class togathered. it look like i require to add multiple times with same elements nearly. what would be the correct approach for this kind of scenarios?
here is my template:
<span><a className={this.state.showMore ? 'active' : ''} onClick={this.showMore}>{this.state.showMore ? 'read less' : 'read more'}</a></span>
i have added the state showMore both a tag and the text inside. is there any simple way to handle same conditions across page?
Thanks in advance.

I'd create a component to handle read-more, and pass the props from where it's used if there's any, So same functionality is same across my application and if there's any improvements I can handle by it in one single place.
here is a demo
EX: functional component
export const ReadMore = ({ text, truncateLength = 10 }) => {
const [showMore, setShowMore] = useState(false);
const getText = () => {
if (showMore) {
return text;
}
const truncatedText = text.substring(0, truncateLength);
if (text.length > truncateLength) {
return `${truncatedText}...`;
}
return truncatedText;
};
return (
<span>
{getText()}
<a
className={showMore ? "active" : ""}
onClick={() => setShowMore(!showMore)}
>
{text.length > truncateLength && (showMore ? "read less" : "read more")}
</a>
</span>
);
};
and use it like this props could be:
text: is the text that should be read-less or more.
truncateLength: is the length that should show if the text length is
greater, and optional prop, if this isn't provided ReadMore
component will set the value to 10 by default, (check the props of
ReadMore)
<ReadMore
text="this is the text that should do the react-more and read-less"
truncateLength={10}
/>

{this.state.showmore ?
<span><a className={'active'} onClick={this.showMore}>read less</a></span>
:
<span><a onClick={this.showMore}>read more</a></span>
}
should be a more readable and clearer way of doing this. Basically when you have >1 thing depending on the same condition, take the condition outside would be my way to go!

Related

How to give html tags inside string interpolation in reactjs?

I have
const getConfirmMsg= (card, isDelete) => {
const valueType = getValue(card);
const confirmMessage = isDelete ? (
`You are about to delete the ${valueType}.This is the last value.`
) : (
`All selected values will be removed.`
);
return (
tr(confirmMessage, {valueType}
);}
I want ${valueType} as itallic font and a line break before the second line.
You can do something like this:
const confirmMessage = isDelete ? (
<p>
You are about to delete the <em>{valueType}</em>.
<br />
This is the last value.
</p>
) : (
`All selected values will be removed.`
);
Not entirely sure I understand your question, but if you need to render html tags inside a string you will need a markdown library, of which I would suggest react-html-parser.
Usage:
import ReactHtmlParser from "react-html-parser";
ReactHtmlParser(`You are about to delete the ${valueType}.This is the last value.`)
I did like this and the translation are working fine now. Because I am working on multilingual app. Translations are needed.
const getConfirmMsg= (card, isDelete) => {
const valueType = getValue(card);
const confirmMessage = isDelete ? (
<>
<p>{tr(`You are about to delete the`)} <i>{tr( valueType)}</i> .
</p>
<p>{tr(` This is the last value.`)}</p>
</>
) : (
tr(`All selected values will be removed.`)
);
return (confirmMessage);
};

Flickity cell selector in React

Basically I have a dropdown in which each option has an attribute corresponding to the ID of an image. My goal is to go to that image when an option is chosen. For that I am trying to use:
const myCustomNext = () => {
flkty.selectCell(somevar)
};
somevar is initially set to #someid, when I click my button, it goes to the cell with that ID perfectly.
The issue starts once I update the value of somevar. As soon as I do and then click the button, I get the following Error:
"Cannot read property 'selectCell' of null"
I logged both the initital somevar and the updated one. Other than the ID itself, they are absolutely identical so I have no clue where I am going wrong. I tried switchen the static and reloadOnUpdate settings but that didn't help.
Here a more complete example that might show better what I am trying to do:
const FlickTest = () => {
const [varimg, setVarimg] = useState("#cG9zdDoxNA");
let flkty = null;
function setVariant(e) {
let index = e.target.selectedIndex;
let optionElement = e.target.childNodes[index]
let option = optionElement.getAttribute('data-imgid');
setVarimg(`#${option}`);
}
const myCustomNext = () => {
flkty.selectCell(varimg)
};
return (
<>
<button onClick={myCustomNext}>My custom next button</button>
<select onChange={setVariant}>
{variants.map((variant) =>
<option data-imgid={variant.gallerie[0].id} value={variant.farbe} key={variant.farbe}>{variant.farbe}</option>
)}
</select>
<Flickity
flickityRef={c => (flkty = c)}
className={'carousel'}
elementType={'div'}
options={flickityOptions}
disableImagesLoaded={true}
reloadOnUpdate={true}
static={true}
>
{variants.map((galitem) =>
galitem.gallerie.map((galimg) =>
<div key={galimg.id} id={galimg.id.replace(/[^\w\s]/gi, '')}>
<span>{galimg.id}</span>
<Image fluid={galimg.localFile.childImageSharp.fluid} />
</div>
)
)}
</Flickity>
</>
)
}
Any ideas or pointers would be much appreciated :)
Switched from a dropdown to buttons just to simplify the whole thing and see where it goes wrong. Seems like flickity only accepts the value directly but not from state or any other variable.
This works:
const selectSlide = (e) => {
flkty.selectCell( `.${e.target.getAttribute("data-selector")}` )
};
...
<button onClick={selectSlide} data-selector={variant.gallerie[0].id} key={variant.farbe}>{variant.farbe}</button>
If anybody knows if this is a flickity thing or (more likely) I was doing something completely wrong I'd still appreciate some pointers so I know better next time :)

How to conditionally set HTML attributes in JSX using reason-react?

I want to render an HTML checkbox whose checked state is controlled by data.
Give a stateless component that receives an item type { label: string, checked: bool},
Like so:
let component = ReasonReact.statelessComponent("TodoItem");
let make = (~item, _children) => {
render: _self => {
<li> <input type_="checkbox" {/*looking for something like this*/ item.checked ? "checked" : "" /* doesn't compile */}/> {ReasonReact.string(item.label)} </li>
}
}
How do I add the presence of the attribute checked to the input tag based on the item.checked == true condition?
As #wegry said in a comment, it seems to fit your use case better to just pass the value directly since item.checked already is a boolean, and checked takes a boolean.
But to answer more generally, since JSX attributes are just optional function arguments under the hood, you can use a neat syntactic trick to be able to explicitly pass an option to it: Just precede the value with ?. With your example:
let component = ReasonReact.statelessComponent("TodoItem");
let make = (~item, _children) => {
render: _self => {
<li> <input type_="checkbox" checked=?(item.checked ? Some(true) : None) /> {ReasonReact.string(item.label)} </li>
}
}
Or, to give an example where you already have an option:
let link = (~url=?, label) =>
<a href=?url> {ReasonReact.string(label)} </a>
This is documented in the section titled Explicitly Passed Optional on the Function page in the Reason docs.

Render some text if array.filter returns 0 in React?

I have a filter on an array in the render function in a React component:
someArray.filter(item => {
if (item.name.includes(searchText))return true
}).map(item=>{
return <h1>{item.name}</h1>
});
How can I elegantly display some text along the lines of "No search results" when no items are being returned by the map function?
There are a few ways you can do this. You can use a ternary operator (and also shorten your callbacks):
const filtered = someArray.filter(item =>
item.name.includes(searchText)
);
//Then, in your JSX:
{
filtered.length > 0 ?
filtered.map((item, key) =>
<h1 key={key}>{item.name}</h1>
)
:
<h1>No search results</h1>
}
This checks if there are any filtered results. If so, it will map them to h1s that have the name of the item. If not, then it will simply render a single h1 with the text 'No search results'.
One possible way is, instead of putting this code directly inside JSX render method, put it inside a method and call that method from render.
Like this:
_filterItem(){
const arr = someArray.filter(item => item.name.includes(searchText))
if(!arr.length) return <div>No data found</div>;
return arr.map(item => <h1 key={/*some unique value*/}>{item.name}</h1>)
}
render(){
return(
<div>{this._filterItem()}</div>
)
}
Suggestion:
With filter and map you can use concise body of arrow function instead of block body, for more details check MDN Doc.
Short and concise:
someArray.map(({name}) => name.includes(searchText) && <h1>{name}</h1>)

React contenteditable cursor jumps to beginning

I am using the module react-simple-contenteditable to enable editing of a fill in the blank worksheet. The reason I must use a content editable element instead of an input element is because I want the text of the problem to wrap. For example, if a problem has one blank, it divides the text into three sections the part before the blank, the blank, and the part after. If I were to represent the outer two as separate divs (or input fields), then the text would not wrap like a paragraph. Instead, I must have a single contenteditable div that contains an input field for the blank and free text on either side.
The text is wrapping like I want it, but when I type text in the contenteditable field, the cursor jumps to the beginning. I don't understand why because I tried the example on the module's github site and it works perfectly, and although my implementation is a bit more complicated, it works essentially the same.
Here is my render function that uses <ContentEditable /> :
render() {
const textPieces =
<div className='new-form-text-pieces'>
{
this.props.problem.textPieces.map( (textPiece, idx) => {
if (textPiece.blank) {
return (
<div data-blank={true} className='blank' key={ textPiece.id } style={{display: 'inline'}}>
<input
placeholder="Answer blank"
className='new-form-answer-input'
value={ this.props.problem.textPieces[idx].text }
onChange={ (event) => this.props.handleTextPiecesInput(this.props.problemIdx, idx, event.target.value) }
/>
<button className='modify-blank remove-blank' onClick={ (event) => this.props.removeBlank(this.props.problemIdx, idx) }>-</button>
</div>
);
} else {
let text = this.props.problem.textPieces[idx].text;
const placeholder = idx === 0 ? 'Problem text' : '...continue text';
// text = text === '' ? placeholder : text;
if (text === '') {
text = <span style={{color:'gray'}}>{placeholder}</span>;
} else {
}
return (
this.props.isTextSplit ?
<TextPiece
key={ textPiece.id }
problemIdx={this.props.problemIdx}
textPieceIdx={idx}
dropBlank={this.props.dropBlank}
moveBlank={this.props.moveBlank}
>
<div style={{display: 'inline-block', }}>{text}</div>
</TextPiece>
: text
);
}
})
}
</div>;
return (
this.props.isTextSplit ? textPieces :
<ContentEditable
html={ReactDOMServer.renderToStaticMarkup(textPieces)}
className="my-class"
tagName="div"
onChange={ (event, value) => this.props.handleProblemChange(event, this.props.problemIdx, value) }
contentEditable='plaintext-only'
/>
);
}
Here is the onChange function:
handleProblemChange(event, problemIdx) {
const problems = cloneDeep(this.state.problems);
event.target.children[0].childNodes.forEach( (textPieceNode, idx) => {
if (textPieceNode.constructor === Text) {
problems[problemIdx].textPieces[idx].text = textPieceNode.wholeText;
} else {
problems[problemIdx].textPieces[idx].text = textPieceNode.childNodes[0].value;
}
});
this.setState({ problems });
}
And here is the state it refers to, just to make thing clear:
this.state = {
problems: [
{
id: shortid.generate(),
textPieces: [
{
text : "Three days was simply not a(n)",
blank : false,
id: shortid.generate(),
},
{
text : "acceptable",
blank : true,
id: shortid.generate(),
},
{
text : "amount of time to complete such a lot of work.",
blank : false,
id: shortid.generate(),
}
]
}
Thanks so much
Long story short, there is no easy way to do this. I have tried this myself and spent days trying. Basically you have to save the cursor position and reposition it yourself after the update. All of this can be achieved with window.getSelection()
https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection
But it can get really tricky depending on how much your content has changed.
I ended up using draftJS instead. Which is an abstraction over contenteditable div by facebook themselves.
https://draftjs.org/docs/overview.html#content
A bit longer to pick up but you will be able to do a lot more
I had a similar problem using VueJS.
Here is the component containing the contenteditable div :
<Text #update-content="updateContent" :current-item-content="item.html_content"/>
Here is the prop definition in Text.vue component :
const props = defineProps({
currentItemContent: {
type: String,
default: ''
}
})
Here is the contenteditable div in Text.vue component :
<div
id="text-editor"
ref="text_editor"
class="mt-3 h-full w-full break-words"
contenteditable="true"
#input="updateContent"
v-html="currentItemContent"
>
Here is the method triggered on #update-content event
const item = computed(() => { ... })
(...)
function updateContent(content) {
item.value.html_content = content
}
The problem here is injecting the item.html_content value as a props triggers a re-render of the contenteditable div.
Because it's mutating it's value in the updateContent method and as the computed (item) is beeing updated, so does the prop value, v-html detects the updated value and triggers a re-render.
To avoid this, i removed the v-html binding :
<div
id="text-editor"
ref="text_editor"
class="mt-3 h-full w-full break-words"
contenteditable="true"
#input="updateContent"
>
And initialized the value of the contenteditable div in a onMounted hook :
const text_editor = ref(null)
(...)
onMounted(() => {
if (props.currentItemContent !== '') {
text_editor.value.innerHTML = props.currentItemContent
}
})
I don't know if there is a better solution for this but it's working fine for me. Hope this helps someone

Resources