how can I dynamically change the variant of a react-bootstrap button?
https://react-bootstrap.github.io/components/buttons/
// highlight button with correct answer
var button = document.getElementsByTagName("button");
for (var i = 0; i < button.length; i++) {
if (button[i].innerHTML === this.state.question.answer) {
//How to do that?
react-bootstrap.Button btn = button[i];
btn.variant = "success";
}
}
}
Thanks and kind regards
Update with solution options:
Option 1 do not change the variant, simply add css class with CSS The !important rule.
// highlight button with correct answer
var button = document.getElementsByTagName("button");
for (var i = 0; i < button.length; i++) {
if (button[i].innerHTML === this.state.question.answer) {
//How to do that?
button[i].classList.add("btn-correct");
}
}
}
Option two use props and state:
<div>
<DynamicButton btnVariant={this.state.btnVariant} content="Primary" />
</div>
<div>
<Button variant="secondary" onClick={this.changePrimaryButton}>Secondary</Button>
</div>
changePrimaryButton = () => {
console.log("change Button");
this.setState({ btnVariant: "success" });
}
Component outside of class:
export const DynamicButton = (props) => {
return (<Button variant={props.btnVariant}>{props.content}</Button>);
}
You can dynamically pass the variant from the parent component to this DynamicButton component.
const DynamicButton = (props) => {
return <Button variant={props.variant}>{props.content}</Button>;
}
This is how you can call this component.
<>
<DynamicButton variant="primary" content="Primary" />
</>
Please import the respective Components.
I'm trying to create a search filter that will filter through facility names that lives in an array of objects.If I hard code an array into the state the filter works, but I need it to drab the info from props. The filtered list is being generated and showing all of the names on the screen but when I type it the textbox to filter nothing happens. What have I overlooked?
class FacilitySearch extends React.Component {
constructor(props) {
super(props);
this.state = {
search: ""
};
}
componentDidMount() {
this.props.dispatch(actions.getFacilitiesList());
}
//The subsr limits the # of characters a user can enter into the seach box
updateSearch = event => {
this.setState({ search: event.target.value.substr(0, 10) });
};
render() {
if (!this.props.facilityList) {
return <div>Loading...</div>
}
let filteredList = this.props.facilityList;
filteredList.filter(facility => {
return facility.facilityName.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1;
});
return (
<div>
<input
type="text"
value={this.state.search}
onChange={this.updateSearch.bind(this)}
placeholder="Enter Text Here..."
/>
<ul>
{filteredList.map(facility => {
return <li key={facility.generalIdPk}>{facility.facilityName}</li>;
})}
</ul>
</div>
);
}
}
const mapStateToProps = state => ({
facilityList: state.facilityList.facilityList
});
export default connect(mapStateToProps)(FacilitySearch)
The problem is that you are not storing the return value of filter in any variable.
You should do something like:
let filteredList = this.props.facilityList.filter(facility => {
return facility.facilityName.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1;
});
From MDN:
The filter() method creates a new array with all elements that pass the test implemented by the provided function.
I'm writing an image picker using react-native's CameraRoll API and rendering them in a FlatList inside CameraRollScreen component. This component takes a prop called maxPhotos, say 3, when a user has selected 3 photos, all other photos will be disabled (cannot be selected anymore), it looks like this (this is what I have right now, it works, but not performant):
As you can see, when I've selected 3 photos (which is the limit), all other photos are covered by a transparent view (disabled). This is not performant, doesn't seem so in the GIF, but when running on a real device, this problem can no longer be ignored. Selecting the first 2 photos doesn't cause any lag, however, upon selecting the last photo, since all other photos will have to be disabled, it becomes laggy. But I have no idea how else I could disable the other photos without disabling them 1 by 1. Here is the code I have for my image picker:
Since every image has different states, I also make each photo a PureComponent called CameraRollImage that has the following state:
{
uri: '',
index: -1 // if not selected, it's -1, if selected, it denotes
// the position of the photo in the 'selectedPhotos'
// array
disabled: false // Whether it should be disabled
}
CameraRollImage component:
class CameraRollImage extends PureComponent {
constructor(props) {
super(props);
this.state = {
uri: '',
index: -1,
disabled: false
};
this.onSelectPhoto = this.onSelectPhoto.bind(this);
}
componentWillMount() {
const { uri, index, disabled } = this.props;
this.setState({ uri, index, disabled });
}
componentWillReceiveProps(nextProps) {
const { uri, index, disabled } = nextProps;
this.setState({ uri, index, disabled });
}
onSelectPhoto() {
const { uri, index } = this.state;
this.props.onSelectPhoto({ uri, index });
// 'onSelectPhoto' is a method passed down to each photo
// from 'CameraRollScreen' component
}
render() {
const { uri, index, disabled } = this.state;
return (
<View style={{ ... }}>
<TouchableOpacity
disabled={disabled}
onPress={this.onSelectPhoto}
>
<Image
source={{ uri }}
style={{ ... }}
/>
</TouchableOpacity>
// If disabled, render a transparent view that covers the photo
{disabled && <View
style={{
position: 'absolute',
backgroundColor: 'rgba(0, 0, 0, 0.75)',
width: ... height: ...
}}
/>}
// render the index here
</View>
);
}
}
export default CameraRollImage;
Then, in CameraRollScreen Component:
class CameraRollScreen extends Component {
constructor(props) {
super(props);
this.state = {
allPhotos: [], // all photos in camera roll
selectedPhotos: []
};
this.onSelectPhoto = this.onSelectPhoto.bind(this);
this.renderPhoto = this.renderPhoto.bind(this);
}
componentWillMount() {
// Access the photo library to grab all photos
// using 'CameraRoll' API then push all photos
// to 'allPhotos' property of 'this.state'
}
onSelectPhoto({ uri, index }) {
let { selectedPhotos } = { ...this.state };
if (index === -1) {
// this means that this photo is not selected
// and we should add it to 'selectedPhotos' array
selectedPhotos.push(uri);
} else {
_.pullAt(selectedPhotos, index);
}
this.setState({ selectedPhotos });
}
renderPhoto({ item }) {
// item is the uri of the photo
const { selectedPhotos } = this.state;
const index = _.indexOf(selectedPhotos, item);
// A photo should be disabled when reach the limit &&
// it's not selected (index === -1)
return (
<CameraRollImage
uri={item}
index={index}
onSelectPhoto={this.onSelectPhoto}
disabled={index === -1 && selectedPhotos.length >= 3}
/>
);
}
render() {
const { allPhotos } = this.state;
return (
<FlatList
data={allPhotos}
extraData={this.state}
...
...
numColumns={3}
renderItem={this.renderPhoto}
/>
);
}
}
export default CameraRollScreen;
I have only 100 photos in my photo library and it's already causing lags, many people have way way way more photos than I do, this way will cause disaster, but how should I go about updating so many photos in FlatList? Or, should I use FlatList at all?
Found the solution, thanks to Pir Shukarullah Shah and RaphaMex.
If I scroll down fast enough, many images were not rendered and they are being rendered when I reach them. This seems right, why render them anyway when they're not on the screen? What I did was that I made use of onViewableItemsChanged of FlatList:
<FlatList
...
...
keyExtractor={(item) => item} // This is important!!!
onViewableItemsChanged={this.onViewablePhotosChanged}
initialNumberToRender={Math.ceil(SCREEN_HEIGHT / IMAGE_SIZE) * 3}
...
/>
Then, onViewablePhotosChanged method:
onViewablePhotosChanged({ viewableItems }) {
let viewablePhotos = [];
viewableItems.forEach((item) => viewablePhotos.push(item.key));
this.setState({ viewablePhotos });
// Here, every object in 'viewableItems' has a key, which
// is the key you provided in 'keyExtractor={(item) => ...}',
// I used the 'uri' of each photo as the key, that's why
// I am pushing viewable photos' uri's to 'viewablePhotos' array
}
Lastly, modify the renderPhoto function to pass a viewable prop
renderPhoto({ item }) {
...
...
return (
<CameraRollImage
...
...
viewable={_.include(this.state.viewablePhotos, item)}
/>
);
}
Then, in CameraRollImage component, where we render images, there is a prop called viewable, if viewable === false, we simply do not update it:
componentWillReceiveProps(nextProps) {
const { ..., ..., viewable } = nextProps;
if (!viewable) {
this.setState({ viewable: false });
return;
}
...
...
}
BETTER YET!!! if viewable is false, instead of rendering the image, we render an equal-sized empty view, you know, to save memory, which of course doesn't seem to be important if there're only 100 photos:
render() {
if (!this.state.viewable) {
return (
<View
style={{
width={IMAGE_SIZE}
height={IMAGE_SIZE}
}}
/>
);
}
return (
<Image
...
...
/>
);
}
Have a simple NS/TS page with a ListView - the list is driven by an ObservableArray.
I add 1 entry to the array and the onItemLoading event gets called 2x.
Here's my XML
<ListView items="{{ dataItems }}"
itemLoading="{{ onItemLoading }}"
itemTap="{{ onNoteTap }}"
itemTemplateSelector="'note'">
<ListView.itemTemplates>
<template key="note">
<StackLayout>
<Label id="label"/>
</StackLayout>
</template>
</ListView.itemTemplates>
</ListView>
Here's my model class
export class NotesModel extends observable.Observable
{
public _listItemArray: ObservableArray<NoteItem>;
constructor()
{
super();
this._listItemArray = new ObservableArray<NoteItem>();
let item = new NoteItem();
item.label = "test";
this._listItemArray.push( item );
}
get dataItems(): ObservableArray<NoteItem>
{
return this._listItemArray;
}
onItemLoading( args: listviewModule.ItemEventData )
{
console.log( "onItemLoading =" + args.index + " " + args.view );
}
onNoteTap( args )
{
}
}
After I run the code, I get:
JS: constructor
JS: onItemLoading =0 StackLayout(217)#file:///app/my-notes-page.xml:39:15;
JS: onItemLoading =0 StackLayout(217)#file:///app/my-notes-page.xml:39:15;
This is your xml file
<ListView items="{{ dataItems }}"
itemLoading="onItemLoading"
itemTap="{{ onNoteTap }}"
itemTemplateSelector="'note'">
<ListView.itemTemplates>
<template key="note">
<StackLayout>
<Label id="label"/>
</StackLayout>
</template>
</ListView.itemTemplates>
</ListView>
This is your main.ts file code, its not the model ts
import { EventData } from 'data/observable';
import { Page } from 'ui/page';
import { HelloWorldModel } from './main-view-model';
// Event handler for Page "navigatingTo" event attached in main-page.xml
export function navigatingTo(args: EventData) {
let page = <Page>args.object;
page.bindingContext = new HelloWorldModel();
}
export function onItemLoading(args) {
console.log('Calling here ');
}
I'm using React-Bootstrap Popover and I was wondering if there is any builtin property that I can add either to Popover itself or to OverlayTrigger so only one popover will display at a time.
You can try rootClose props which will trigger onHide when the user clicks outside the overlay. Please note that in this case onHide is mandatory. e.g:
const Example = React.createClass({
getInitialState() {
return { show: true };
},
toggle() {
this.setState({ show: !this.state.show });
},
render() {
return (
<div style={{ height: 100, position: 'relative' }}>
<Button ref="target" onClick={this.toggle}>
I am an Overlay target
</Button>
<Overlay
show={this.state.show}
onHide={() => this.setState({ show: false })}
placement="right"
container={this}
target={() => ReactDOM.findDOMNode(this.refs.target)}
rootClose
>
<CustomPopover />
</Overlay>
</div>
);
},
});
I managed to do this in a somewhat unconventional manner. You can create a class which tracks the handlers of all of your tooltips:
export class ToolTipController {
showHandlers = [];
addShowHandler = (handler) => {
this.showHandlers.push(handler);
};
setShowHandlerTrue = (handler) => {
this.showHandlers.forEach((showHandler) => {
if (showHandler !== handler) {
showHandler(false);
}
});
handler(true);
};
}
Then in your tooltip component:
const CustomToolTip = ({
children,
controller,
}: CustomToolTipProps) => {
const [showTip, setShowTip] = useState(false);
useEffect(() => {
if (!controller) return;
controller.addShowHandler(setShowTip);
}, []);
return (
<OverlayTrigger
onToggle={(nextShow) => {
if (!nextShow) return setShowTip(false);
controller ? controller.setShowHandlerTrue(setShowTip) : setShowTip(true);
}}
show={showTip}
overlay={(props: any) => <Overlay {...props}/>}
>
<div className={containerClassName}>{children}</div>
</OverlayTrigger>
);
};
It's not really a 'Reacty' solution but it works quite nicely. Note that the controller is completely optional here so if you wanted you could not pass that in and it would then behave like a normal popover allowing multiple tooltips at once.
Basically to use it you can create another component and instantiate a controller which you pass into CustomToolTip. Then for any tooltips which are rendered using that component, only 1 will appear at a time.
STEP 1: we declare a currentPopover variable that contain current popover id, so we are sure that there is only one popover at a time.
const [currentPopover, setCurrentPopover] = useState(null);
STEP 2: the OverlayTrigger from react-bootstrap has properties to set popover state manually. If the currentPopover variable is equal to popover id then we show the popover.
show={currentPopover === `${i}`}
STEP 3: the OverlayTrigger from react-bootstrap has properties to handle popover click manually. On click we update the currentPopover variable with the new id, except if we clicked on the current.
onToggle={() => {
if( currentPopover === `${i}` )
setCurrentPopover(null)
else
setCurrentPopover(`${i}`)
}}
RESULT:
const [currentPopover, setCurrentPopover] = useState(null);
<OverlayTrigger
trigger="click"
show={currentPopover == `${i}`}
onToggle={() => {
if( currentPopover == `${i}` )
setCurrentPopover(null)
else
setCurrentPopover(`${i}`)
}}
>
(I use ${i} as id cause my OverlayTrigger is in a loop where i is the index)