I want to be able to generate a certain number of components (all similar) according to a value chosen in a select element.
I tried to generate this by creating an array of components whose size is the selected number. When I select a number it launch a handle function which changes the selected number in the creation.
handle = selected => {
this.state.number= selected.value;
this.forceUpdate();
};
render() {
return(
<Select onChange={this.handle} options = { ... }/>
{[...Array(this.state.number)].map(e => { return ( <TestComponent />
); })}
)
}
It loads the first component because the number is initialized to 1 but when I select another number 2,3,4,5,... it doesn't render the new components.
You can create a function like below:
makeComponentList=(num)=>{
let i=0,arr=[];
for(;i<num;i++){
arr.push(<TestComponent key={i} />)
}
return arr;
}
handle = selected => {
this.setState({number:selected.value});
}
render() {
return(
<React.Fragment>
<Select onChange={this.handle} options = { ... }/>
{this.makeComponentList(this.state.number)}
</React.Fragment>
);
}
You don't update the state so this.state.number value is always set to its initialized value:
handle = selected => {
this.setState({ number: selected.value }); // Will re-render the component
this.forceUpdate();
};
render() {
return(
<>
<Select onChange={this.handle} />
{[...Array(this.state.number)].map((e, i) => (
<TestComponent key={i} />
// ^ Add a unique key
))}
</>
)
}
Related
I would like to delete selected item from list.
When I click on delete the right item get deleted from the list content but on UI I get always the list item fired.
I seems to keep track of JSX keys and show last values.
Here's a demo
const Holidays = (props) => {
console.log(props);
const [state, setState] = useState({ ...props });
useEffect(() => {
setState(props);
console.log(state);
}, []);
const addNewHoliday = () => {
const obj = { start: "12/12", end: "12/13" };
setState(update(state, { daysOffList: { $push: [obj] } }));
};
const deleteHoliday = (i) => {
const objects = state.daysOffList.filter((elm, index) => index != i);
console.log({ objects });
setState(update(state, { daysOffList: { $set: objects } }));
console.log(state.daysOffList);
};
return (
<>
<Header as="h1" content="Select Holidays" />
<Button
primary
icon={<AddIcon />}
text
content="Add new holidays"
onClick={() => addNewHoliday(state)}
/>
{state?.daysOffList?.map((elm, i) => {
console.log(elm.end);
return (
<Flex key={i.toString()} gap="gap.small">
<>
<Header as="h5" content="Start Date" />
<Datepicker
defaultSelectedDate={
new Date(`${elm.start}/${new Date().getFullYear()}`)
}
/>
</>
<>
<Header as="h5" content="End Date" />
<Datepicker
defaultSelectedDate={
new Date(`${elm.end}/${new Date().getFullYear()}`)
}
/>
</>
<Button
key={i.toString()}
primary
icon={<TrashCanIcon />}
text
onClick={() => deleteHoliday(i)}
/>
<span>{JSON.stringify(state.daysOffList)}</span>
</Flex>
);
})}
</>
);
};
export default Holidays;
Update
I'm trying to make a uniq id by adding timeStamp.
return (
<Flex key={`${JSON.stringify(elm)} ${Date.now()}`} gap="gap.small">
<>
<Header as="h5" content="Start Date" />
<Datepicker
defaultSelectedDate={
new Date(`${elm.start}/${new Date().getFullYear()}`)
}
/>
</>
<>
<Header as="h5" content="End Date" />
<Datepicker
defaultSelectedDate={
new Date(`${elm.end}/${new Date().getFullYear()}`)
}
/>
</>
<Button
primary
key={`${JSON.stringify(elm)} ${Date.now()}`}
icon={<TrashCanIcon />}
text
onClick={() => deleteHoliday(i)}
/>{" "}
</Flex>
);
I was hoping that the error disappear but still getting same behaviour
Issue
You are using the array index as the React key and you are mutating the underlying data array. When you click the second entry to delete it, the third element shifts forward to fill the gap and is now assigned the React key for the element just removed. React uses the key to help in reconciliation, if the key remains stable React bails on rerendering the UI.
You also can't console log state immediately after an enqueued state update and expect to see the updated state.
setState(update(state, { daysOffList: { $set: objects } }));
console.log(state.daysOffList);
React state updates are asynchronous and processed between render cycles. The above can, and will, only ever log the state value from the current render cycle, not the update enqueued for the next render cycle.
Solution
Use a GUID for each start/end data object. uuid is a fantastic package for this and has really good uniqueness guarantees and is incredibly simple to use.
import { v4 as uuidV4 } from 'uuid';
// generate unique id
uuidV4();
To specifically address the issues in your code:
Add id properties to your data
const daysOffList = [
{ id: uuidV4(), start: "12/12", end: "12/15" },
{ id: uuidV4(), start: "12/12", end: "12/17" },
{ id: uuidV4(), start: "12/12", end: "12/19" }
];
...
const addNewHoliday = () => {
const obj = {
id: uuidV4(),
start: "12/12",
end: "12/13",
};
setState(update(state, { daysOffList: { $push: [obj] } }));
};
Update handler to consume id to delete
const deleteHoliday = (id) => {
const objects = state.daysOffList.filter((elm) => elm.id !== id);
setState(update(state, { daysOffList: { $set: objects } }));
};
Use the element id property as the React key
{state.daysOffList?.map((elm, i) => {
return (
<Flex key={elm.id} gap="gap.small">
...
</Flex>
);
})}
Pass the element id to the delete handler
<Button
primary
icon={<TrashCanIcon />}
text
onClick={() => deleteHoliday(elm.id)}
/>
Use an useEffect React hook to log any state update
useEffect(() => {
console.log(state.daysOffList);
}, [state.daysOffList]);
Demo
Note: If you don't want (or can't) install additional 3rd-party dependencies then you can roll your own id generator. This will work in a pinch but you should really go for a real proven solution.
const genId = ((seed = 0) => () => seed++)();
genId(); // 0
genId(); // 1
I am still new to React Native and I struggle a bit with the programming paradigm. What I am trying to do (by following the structure of another React.js project) is to create a container (parent component) which contains a number of other components. My ultimate goal is to pass and handle all of the props in the parent component. Child component only shows them. My structure looks something like this:
export default class TemporaryCardRequestScreen extends React.Component {
constructor(props) {
super(props);
this.showDateTimePicker = this.showDateTimePicker.bind(this);
this.hideDateTimePicker = this.hideDateTimePicker.bind(this);
this.handleDatePicked = this.handleDatePicked.bind(this);
this.state.fromDateTime = {
isVisible: false,
value: new Date()
}
}
showDateTimePicker = () => { /*body*/ };
hideDateTimePicker = () => { /*body*/ };
handleDatePicked = date => { /*body*/ };
render() {
return (
<DateTimePickerComponent
isVisible={this.state.fromDateTime.isVisible}
onConfirmPressed={this.handleDatePicked}
onCancelPressed={this.hideDateTimePicker}
showDateTimePicker={this.showDateTimePicker}
value={this.state.fromDateTime.value}
/>
);
}
}
and the, my child component looks something like this:
// npm ref.: https://github.com/mmazzarolo/react-native-modal-datetime-picker
export default class DateTimePickerComponent extends Component {
constructor(props) {
super(props);
}
render() {
const {
isVisible,
onConfirmPressed,
onCancelPressed,
showDateTimePicker } = this.props;
return (
<>
<Button title="Show DatePicker" onPress={showDateTimePicker} />
<DateTimePicker
isVisible={isVisible}
onConfirm={onConfirmPressed}
onCancel={onCancelPressed}
mode='datetime'
is24Hour={false}
date={new Date()}
/>
</>
);
}
}
My focus now is on
onConfirmPressed={this.handleDatePicked}
currently, this.handleDatePicked accepts a single argument but I'd like it to accept one additional which is passed to it at the place being used in child component.
So, my ultimate goal would be to have something similar to this:
render() {
const {
isVisible,
onConfirmPressed,
onCancelPressed,
showDateTimePicker,
dateTimePickerId } = this.props;
return (
<>
<Button title="Show DatePicker" onPress={this.showDateTimePicker} />
<DateTimePicker
isVisible={isVisible}
onConfirm={onConfirmPressed(dateTimePickerId)}
onCancel={onCancelPressed}
mode='datetime'
is24Hour={false}
date={new Date()}
/>
</>
);
}
So, in this way, in my parent component I could have a single method which can handle the updates for a number of date time pickers in my container (This is actually my use-case). Instead of having the same type of handlers (with different property name) for pretty much the same thing.
UPDATE: Snack expo
You can capture 'onConfirm' of DateTimePickerComponent, then call parent function and pass in the dateTimePickerId.
TemporaryCardRequestScreen
// Modify to accept 2 arguments
handleDatePicked = (id, date) => {
if (id == "1") {
// code here
}
else if (id == "2") {
// code here
}
};
render() {
return (
<div>
<DateTimePickerComponent
isVisible={this.state.fromDateTime.isVisible}
onConfirmPressed={this.handleDatePicked}
onCancelPressed={this.hideDateTimePicker}
showDateTimePicker={this.showDateTimePicker}
value={this.state.fromDateTime.value}
dateTimePickerId="1"
/>
<DateTimePickerComponent
isVisible={this.state.fromDateTime.isVisible}
onConfirmPressed={this.handleDatePicked}
onCancelPressed={this.hideDateTimePicker}
showDateTimePicker={this.showDateTimePicker}
value={this.state.fromDateTime.value}
dateTimePickerId="2}
/>
</div>
);
}
DateTimePickerComponent
onMyCustomConfirmPressed = (date) => {
// Parent onConfirmPressed() shall accept 2 arguments
this.props.onConfirmPressed(this.props.dateTimePickerId, date)
}
render() {
const {
isVisible,
onConfirmPressed,
onCancelPressed,
showDateTimePicker,
dateTimePickerId } = this.props;
return (
<>
<Button title="Show DatePicker" onPress={this.showDateTimePicker} />
<DateTimePicker
isVisible={isVisible}
onConfirm={this.onMyCustomConfirmPressed}
onCancel={onCancelPressed}
mode='datetime'
is24Hour={false}
date={new Date()}
/>
</>
);
}
I have 4 different divs each containing their own button. When clicking on a button the div calls a function and currently sets the state to show a modal. Problem I am running into is passing in the index of the button clicked.
In the code below I need to be able to say "image0" or "image1" depending on the index of the button I am clicking
JS:
handleSort(value) {
console.log(value);
this.setState(prevState => ({ childVisible: !prevState.childVisible }));
}
const Features = Array(4).fill("").map((a, p) => {
return (
<button key={ p } onClick={ () => this.handleSort(p) }></button>
)
});
{ posts.map(({ node: post }) => (
this.state.childVisible ? <Modal key={ post.id } data={ post.frontmatter.main.image1.image } /> : null
))
}
I would suggest:
saving the button index into state and then
using a dynamic key (e.g. object['dynamic' + 'key']) to pick the correct key out of post.frontmatter.main.image1.image
-
class TheButtons extends React.Component {
handleSort(value) {
this.setState({selectedIndex: value, /* add your other state here too! */});
}
render() {
return (
<div className="root">
<div className="buttons">
Array(4).fill("").map((_, i) => <button key={i} onClick={() => handleSort(i)} />)
</div>
<div>
posts.map(({ node: post }) => (this.state.childVisible
? <Modal
key={ post.id }
data={ post.frontmatter.main.[`image${this.state.selectedIndex}`].image }
/>
: null
))
</div>
</div>
);
}
}
This is a good answer which explains "Dynamically access object property using variable": https://stackoverflow.com/a/4244912/5776910
I want to generate a sliders dynamically according to user input, and don't know how to save values on change. Following is the code given of my implementation.
The problem is that I can't get value via event.target.value
// priceCities is an array of objects:
handlePrices(priceCities){
return priceCities.map( (cstate, index) => (
<li key={index} >{cstate.name} <Slider key={index} min={3} max={500} step={1} style={{height: 100}} axis="y"
defaultValue={5} id ={cstate.id} onChange={ this.handleSlider.bind(this,cstate.id )} value={this.state.values[cstate.id] } /> <span>{this.state.values[cstate.id]}</span> </li>
));
}
this.state = {
values: []
}
and onChange() method here:
handleSlider ( event,i ) {
// this.state.sliderValue[event.target.id] = event.target.value;
//console.log('handlerslider'+event.target.id+' '+event.target.value);
let values = [...this.state.values];
values[i] = event.target.value;
this.setState({ values });
}
Finally I found solution by defining the onChange method like this :
onChange={(event,value) => this.handleSlider(event, value,currState.id )}
and the code of handleSlider function :
handleSlider (event, value,id) {
let values = [...this.state.sliderValue];
values[id] = value;
this.setState({sliderValue: values });
console.log('handlerslider'+value+' '+id);
}
2020 Answer with Hooks
Something simple like this will work:
<Slider
onChange={(_, value) =>
setState(value)
}
step={1}
min={1}
max={50}
value={value}
valueLabelDisplay="auto"
/>
Using ReactJS + Material-UI, I have an array called colors and contains strings of different colors. Say for example the array colors has 3 color strings: "white", "blue", "green. Then I would like to render each color string has a <MenuItem/> inside a <DropDownMenu/> (http://www.material-ui.com/#/components/dropdown-menu). And once a <MenuItem/> is selected, I'd like to console log that particular color like, say chose "white": console.log("white").
So I used .forEach yet the does not show any strings and it is empty. What could I be doing wrong?
Here is the code:
constructor() {
super()
this.state = {
value: 1,
}
}
dropDownColorChange(event, index, value) {
this.setState({value: value})
//Not sure how to implement here dynamically based on array size. Would like to console.log the color string of the selected
}
render() {
var colors = ["white", "blue", "green"] //would be able to handle any array size
return (
<div>
<DropDownMenu
value={this.state.valueTwo}
onChange={this.dropDownColorChange}
>
{
<MenuItem value={1} primaryText="Select" />
colors.forEach(color => {
<MenuItem primaryText={color}/>
})
}
</DropDownMenu>
</div>
)
}
Thank you
You've almost got it right. You have to map over available colors and return a MenuItem for each color:
const colors = ['white', 'blue', 'green'];
class ColorChanger extends Component {
constructor() {
super();
this.state = {
selectedColorValue: 1,
};
}
handleColorChange(event, index, value) {
console.log(`You have selected ${colors[value]} color`);
this.setState({
selectedColorValue: value
});
}
render() {
return (
<div>
<DropDownMenu value={this.state.selectedColorValue} onChange={this.handleColorChange}>
{colors.map((color, index) =>
<MenuItem key={index} value={index} primaryText={color} />
)}
</DropDownMenu>
</div>
);
}
}
map (contrary to forEach) returns an array where each element is the return value of predicate function. In your case it returns a <MenuItem />.
I used the react hook to set the menu items on clicking my menu icon and I also set the value I want to pass to my action method.
const [menuItems, setMenuItems] = React.useState<IMenuItem[]>();
const [menuValue, setMenuValue] = React.useState<IMenuValue>();
const handleClickMenu = (
event: React.MouseEvent<HTMLElement>,
value: IMenuValue,
) => {
setMenuItems(value.menuItems);
setMenuTransaction(value);
setMenuAnchorEl(event.currentTarget);
};
return (
// ... code ...
<PositionedVertMenu
data-testid={`menu`}
open={Boolean(menuAnchorEl)}
anchorEl={menuAnchorEl}
onClick={(event: React.MouseEvent<HTMLElement>) => handleClickMenu(event, value)}
onClose={handleCloseMenu}
>
{menuValue &&
menuItems?.map((option, menuIndex) => (
<MenuItem
data-testid={`menu-item-${menuIndex}`}
onClick={() => option.action(menuValue, handleCloseMenu)}
>
<Typography>{translate(option.text)}</Typography>
</MenuItem>
))}
</PositionedVertMenu>
)