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

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.

Related

.filter() function creating loop in delete function - React

I've got a 'list' component, which allows you to add an item to a state array, and then display it from the state afterwards. These list items can then be removed by the user afterwards (or should be able to).
There's four state props in this component:
currentList: content in the input that's to be added to the list array
setCurrentList: what's used to change the content in the input
fullList: the full array list
setFullList: used to add the currentList content to the array, and removed
I'm using .filter() to create a copy of the state array, and then set the state afterwards in this function:
const deleteFromList = (e) => {
console.log("Delete button pressed")
console.log(e)
let fullList = props.fullListState
let setFullList = props.setFullListState
let filteredArray = fullList.filter(item => item)
setFullList(filteredArray)
}
However, every time I execute this function (i.e. when the delete button is pressed), it just creates a loop and the first two console.logs are just repeatedly done.
This is the full return function itself:
<>
<label className="setup-jobs-label">{props.label}</label>
<div className="setup-jobs-input-container">
<input className="setup-jobs-alt-input" type="text" onChange={onChange} value={props.currentListState} />
<button className="setup-jobs-add-button" onClick={addToList}>Add</button>
</div>
{ props.fullListState === [] ? null : props.fullListState.map(x => {
return <div className="setup-jobs-input-container" key={props.fullListState[x]}>
<p className="setup-jobs-input-paragraph">{x}</p>
<button className="setup-jobs-delete-button" onClick={deleteFromList(x)}>Delete</button>
</div>
}) }
</>
The important bit is the bottom conditional render, which checks to see if the state array is empty, and if so, not display anything. If it isn't, then it returns null.
Any advice would be appreciated - not sure what I'm doing wrong in the filter function.
In your onClick handler, you pass the result of the execution of deleteFromList, you should pass a reference to this function instead :
// note the '() =>'
<button className="setup-jobs-delete-button" onClick={() => deleteFromList(x)}>Delete</button>
See https://reactjs.org/docs/handling-events.html for more details about this.
Beside this, your filter logic does not seem right :
// this line only removes falsy values, but not the "e" values
let filteredArray = fullList.filter(item => item)
// you should implement something like this
let filteredArray = fullList.filter(item => [item is not "e"])
// this should work as we work on objects references
let filteredArray = fullList.filter(item => item !== e)

Passing a function to select html in React

I mapping over data which displays different select option dropdowns. I need my onChange function to be on the select tag to capture the selected option, but the select tag is before the function is initialized so I'm not sure how to go about passing the function into it.
<select onChange={onChange}> <--- onChange function cant be passed here
{options.map((attribute) => {
function onChange(event) {
const newSelected = { ...selected };
newSelected[attribute.title] = event.target.value
}
return (
<option value={attribute.index}>{attribute.value}</option>
)
})}
</select>
Define your onChange so that it uses the information on the event object it receives to find the right entry in the list, something like this:
<select onChange={event => {
const attribute = options[event.currentTarget.value];
const newSelected = {
...selected,
[attribute.title]: event.currentTarget.value,
};
// ...
}}>
{options.map((attribute, index) => {
return (
<option key={attribute.value} value={index}>{attribute.value}</option>
)
})}
</select>
It's hard to be sure the details of that are right without knowing the structure of options or what you want newSelected to be, but I think I've mostly inferred it from the question's code. Note that I changed attribute.index to index in the map call, and that I added a key (you need key on any array you render), assuming that attribute.value is unique.
That said, if attribute.value is unique, I think I'd use it instead of index or attribute.index:
<select onChange={event => {
const attribute = options.find(({value}) => value == event.currentTarget.value);
const newSelected = {
...selected,
[attribute.title]: attribute.index,
};
// ...
}}>
{options.map(({value}) => {
return (
<option key={value} value={value}>{value}</option>
)
})}
</select>
I think I sort of see why you're doing it the way you're doing it, so let me try to address how to handle it differently:
First of all, you're doing it because the onChange function needs access to the attribute.title value. So for you, you're thinking the easiest way to get that value is to define the function in the .map function, where you have access to each individual attribute as it's coming through.
You can't, though. As I said in my comment: It conceptually doesn't make sense. <select onChange={onChange}> implies a singular onChange function, but you're defining a new onChange function in every iteration of .map.
So what you need to do is define onChange before your render function, and then inside that you have to find the matching attribute inside the options array, so that you can then use attribute.title as you like.
[edit] TJ Crowder gives a specific code example of how to do that.

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 JSX - dynamic html attribute

In JSX, we can indicate attribute value dynamically like:
<div className={this.state.className}>This is a div.</div>
Is it possible to indicate attribute (including attribute name and attribute value) dynamically? Like:
const value = emptyValue ? "" : "value='test'";
<input {value} />
That means, once emptyValue is true, "input" tag should not include "value" attribute (value="" is different from no value attribute, as one is show empty in input field, another is show existing text in input field).
ES6 object expansion only works for objects. Therefore to generate a dynamic attribute, try something like this:
const value = emptyValue ? {} : { value: 'test' }
<a {...value} ></a>
Note value will always be an object.
you can insert whole element in if statement in render function, but before return like this:
render() {
var input = (<input />);
if (!emptyValue) {
input = (<input value='test'/>)
}
return (
<div>
{input}
</div>
)
}

Dynamic attribute in ReactJS

I want to dynamically include/omit the disabled attribute on a button element. I have seen plenty of examples of dynamic attribute values, but not of attributes themselves. I have the following render function:
render: function() {
var maybeDisabled = AppStore.cartIsEmpty() ? "disabled" : "";
return <button {maybeDisabled}>Clear cart</button>
}
This throws a parse error because of the "{" character. How can I include/omit the disabled attribute based on the (boolean) result of AppStore.cartIsEmpty()?
The cleanest way to add optional attributes (including disabled and others you might want to use) is currently to use JSX spread attributes:
var Hello = React.createClass({
render: function() {
var opts = {};
if (this.props.disabled) {
opts['disabled'] = 'disabled';
}
return <button {...opts}>Hello {this.props.name}</button>;
}
});
React.render((<div><Hello name="Disabled" disabled='true' />
<Hello name="Enabled" />
</div>)
, document.getElementById('container'));
By using spread attributes, you can dynamically add (or override) whatever attributes you'd like by using a javascript object instance. In my example above, the code creates a disabled key (with a disabled value in this case) when the disabled property is passed to the Hello component instance.
If you only want to use disabled though, this answer works well.
I'm using React 16 and this works for me (where bool is your test):
<fieldset {...(bool && {disabled:true})}>
Basically, based on the test (bool) you return an object with the conditional attributes or you don't.
Also, if you need to add or omit multiple attributes you can do this:
<fieldset {...(bool && {disabled:true, something:'123'})}>
For more elaborate attribute managed I suggest you prefab the object with (or without) the attributes outside of JSX.
You can pass a boolean to the disabled attribute.
render: function() {
return <button disabled={AppStore.cartIsEmpty()}>Clear cart</button>
}
function Test() {
return (
<div>
<button disabled={false}>Clear cart</button>
<button disabled={true}>Clear cart</button>
</div>
);
}
ReactDOM.render(<Test />, document.querySelector("#test-container"));
console.log(Array.from(document.querySelectorAll("button")));
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="test-container"></div>
Far cleaner than the accepted solution is the solution which AnilRedshift mentioned, but which I'll expand on.
Simply put, HTML attributes have a name and a value. As a shorthand, you can use the name only for "disabled", "multiple", etc. But the longhand version still works, and allows for React to work in it's preferred way.
disabled={disabled ? 'disabled' : undefined} is the most legible solution.
The version I used was:
<button disabled={disabled ? 'disabled' : undefined}>
Click me (or dont)
</button>
More cleaner way of doing dynamic attributes which works for any attributes is
function dynamicAttributes(attribute, value){
var opts = {};
if(typeof value !== 'undefined' && value !== null) {
opts['"'+attribute+'"'] = value;
return opts;
}
return false;
};
Call in your react component like following
<ReactComponent {...dynamicAttributes("disabled",false)}
{...dynamicAttributes("data-url",dataURL)}
{...dynamicAttributes("data-modal",true)} />
Tips :
You could have dynamicAttributes function in a common place/utilities and
import it to use it across all components
you could pass value as null to not render dynamic attribute
A simple and clean way of doing it
<button {...disabled && {disabled: true}}>Clear cart</button>
disabled should come from props like this
<MyComponent disabled />
You can find something similar to this at the documentation.
https://facebook.github.io/react/docs/transferring-props.html
In your case could be something like this
function MyComponent(props) {
let { disabled, ...attrs } = props;
if (disabled) {
// thus, it will has the disabled attribute only if it
// is disabled
attrs = {
...attrs,
disabled: true
}
};
return (
<button {...attrs}>MyLabel</button>
)
}
This code is using ES6, but I thing you got the idea.
This is cool because you can pass many others attributes to this component and it will still work.
First you can simply check
<button disabled={true}>Button 1</button>
<button disabled={false}>Button 2</button>
Note: **disabled value is not String, it should be Boolean.
Now for dynamic. You can simply write
<button type="button" disabled={disable}
onClick={()=>this.setSelectValue('sms')} >{this.state.sms}</button>
As you can see I am using disabled property and in curly brackets it can be local variable/state var.
The variable disable contains values true/false.
In case others come here for attributes other than disabled, e.g., custom attributes like data-attr, one can assign empty string "" as the value in the object to be spread to eliminate the value of the attribute. With the attribute name only available on the resulted HTML.
For example:
<div {...(trueOrFalse && { [`data-attr`]: "" })}></div>
Furthermore, if you wish the name of the attribute being dynamic too. Due to template strings support string interpolation, you can put state into it to make the name of attribute dynamic.
<div {...(trueOrFalse && { [`${state}`]: "" })}></div>
This could work, problem with disabled is one could not simply set boolean for it.
if(AppStore.cartIsEmpty()){
return "<button disabled>Clear cart</button>"
}
else
{
return "<button>Clear cart</button>"
}

Resources