WordPress block update resets attribute array - wordpress

I working on a WordPress Gutenberg block that has the following attributes:
attributes: {
panels: {
type: "array",
default: [{
id: uuid(),
h2: "",
h3: "",
backgroundImage: "",
highlightText: "",
featureList: [""],
buttonText: "",
buttonUrl: "",
buttonColor: ""
}
}
}
As you can see, it is an array of objects (panels[]). The problem I'm having is this: when there are 2 or more panels and I start typing into one of the panels (the h2, for example) all other panels disappear.
Here is the JSX that is returned:
return (
<div {...blockProps}>
<h2 className="tpp-heading">Tariff Product Panel</h2>
{props.attributes.panels.map((panel, panelIndex) => {
return (
<div className="tpp-product" id={panel.id}>
<h2 className="tpp-sub-heading">Product {panelIndex + 1}</h2>
<Button
onClick={() => removePanel(panelIndex)}
className="tpp-remove-panel"
title="Delete Panel">
<Icon
icon={
"remove"
}
/>
</Button>
<Flex>
<FlexBlock className="tpp-attribute">
<TextControl
value={panel.h2}
label="Heading"
onChange={(value) => updateAttribute(panelIndex, value, "h2", panel.id)}
/>
</FlexBlock>
<FlexBlock>
<TextControl
value={panel.h3}
label="Sub Heading"
onChange={(value) => updateAttribute(panelIndex, value, "h3", panel.id)}
/>
</FlexBlock>
</Flex>
<Flex>
<FlexBlock className="tpp-attribute">
<TextControl
value={panel.backgroundImage}
label="Background Image"
onChange={(value) => updateAttribute(panelIndex, value, "backgroundImage", panel.id)}
/>
</FlexBlock>
<FlexBlock>
<TextControl
value={panel.highlightText}
label="Highlight Text (if filled in, panel will have highlight)"
onChange={(value) => updateAttribute(panelIndex, value, "highlightText", panel.id)}
/>
</FlexBlock>
</Flex>
<Flex>
<FlexBlock className="tpp-attribute">
<TextControl
value={panel.buttonText}
label="Button Text"
onChange={(value) => updateAttribute(panelIndex, value, "buttonText", panel.id)}
/>
</FlexBlock>
<FlexBlock>
<SelectControl
label="Button Color"
value={panel.buttonColor}
options={[
{ label: 'Default', value: 'default' },
{ label: 'Primary', value: 'primary' },
{ label: 'Secondary', value: 'secondary' },
]}
onChange={(value) => updateAttribute(panelIndex, value, "buttonColor", panel.id)}
/>
</FlexBlock>
</Flex>
<Flex>
<FlexBlock className="tpp-attribute">
<TextControl
value={panel.buttonUrl}
label="Button URL"
onChange={(value) => updateAttribute(panelIndex, value, "buttonUrl", panel.id)}
/>
</FlexBlock>
</Flex>
<label class="components-base-control__label css-1v57ksj">Features:</label>
{panel.featureList.map((feature, featureIndex) => {
return (
<Flex>
<FlexBlock>
<TextControl
value={feature}
onChange={(feature) => updateFeaturelist(panelIndex, featureIndex, feature)}
/>
</FlexBlock>
<FlexBlock>
<Button
onClick={() => removeFeature(panelIndex, featureIndex)}
className="tpp-delete-feature"
title="Delete Feature">
<Icon
icon={
"trash"
}
/>
</Button>
</FlexBlock>
</Flex>
)
})}
<Button
onClick={() => addNewFeature(panelIndex)}
className="tpp-add-feature">
Add feature
</Button>
</div>
)
})}
<Button
isPrimary
onClick={() => addNewProduct()}>
Add product
</Button>
</div>
)
}
And here is the function that updates the panel:
function updateAttribute(panelIndex, value, el, id) {
let panel = props.attributes.panels.find((item) => item.id == id)
panel[el] = value
props.setAttributes({
panels: props.attributes.panels.splice(panelIndex, 1, panel)
})
}
I'd really appreciate a bit of help with this. Everything else is working fine.
In the updateAttribute function I've tried various ways to get this to work but without any luck. Obviously, what I'm expecting is that when typing into a panel, the other panels do not disappear.

OK... I've managed to figure it out - but it took quite a while!
So it turns out that I needed to update the a copy of the whole array (panels) first before calling setAttribute.
function updateAttribute(panelIndex, value, el, id) {
let panel = props.attributes.panels.find((item) => item.id == id)
panel[el] = value
const newPanels = [...props.attributes.panels]
newPanels[panelIndex] = panel;
props.setAttributes({
panels: newPanels
})
}
I'm still a bit confused as to why my earlier attempts were not working but at least I've managed to solve my issue.

Related

Link from react router dom change the align of my Card from Boostrap

I have some Cards made with Boostrap, they look like this.
function TimeLine(props) {
return props.data.map(
({ screenName, name, imageProfile, description, timeAgo }) => (
<TwitterCard
screenName={screenName}
name={name}
imageProfile={imageProfile}
description={description}
timeAgo={timeAgo}
/>
)
);
}
Nevertheless if I add the component Link from react router dom, I got this.
function TimeLine(props) {
return props.data.map(
({ screenName, name, imageProfile, description, timeAgo }) => (
<Link to={`/username${screenName}`}>
<TwitterCard
screenName={screenName}
name={name}
imageProfile={imageProfile}
description={description}
timeAgo={timeAgo}
/>
</Link>
)
);
}
How could I keep the align and avoid these underlined text? Is there some alternative to Link? Or a correct approach to do this? Notice that I'm interested in make the Card clickeable so I can use link there
Apply CSS/style to make them not block-level elements.
Example:
function TimeLine(props) {
return props.data.map(
({ screenName, name, imageProfile, description, timeAgo }) => (
<Link
key={`/username${screenName}`}
to={`/username${screenName}`}
style={{ display: "inline" }}
>
<TwitterCard
screenName={screenName}
name={name}
imageProfile={imageProfile}
description={description}
timeAgo={timeAgo}
/>
</Link>
)
);
}
Since the TwitterCard component is the component enforcing the flex/grid layout though the link should be moved into the TwitterCard component inside the Col component.
Example:
const TwitterCard = ({
screenName,
name,
imageProfile,
description,
timeAgo
}) => {
return (
<>
<style type="text/css">
{`
.bg-customBlack {
background-color: #26262C;
color: white;
},
`}
</style>
<Col md={4} className="p-2">
<Link to={`/username${screenName}`}> // <-- link here inside column component
<Card bg="customBlack" className="text-center">
<Card.Header>
#{screenName} - {name}
</Card.Header>
<Card.Body>
<Card.Title>
<Image width={65} height={65} roundedCircle src={imageProfile} />
</Card.Title>
<Card.Text>{description}</Card.Text>
</Card.Body>
<Card.Footer className="text-muted">{timeAgo}</Card.Footer>
</Card>
</Link>
</Col>
</>
);
};
function TimeLine(props) {
return props.data.map(
({ screenName, name, imageProfile, description, timeAgo }) => (
<TwitterCard
key={screenName}
screenName={screenName}
name={name}
imageProfile={imageProfile}
description={description}
timeAgo={timeAgo}
/>
)
);
}

KeyboardAvoidingView not working properly on iOS react native, expo even if I tried all solutions including the documentation

I have a react native Expo project. Unfortunately I am blocked by an annoying problem.
This is how the view looks like:
When I click on the second input (Quantity) an empty blank space is added :(
Furthermore, when I click the second time on the input, the keyboard overlaps again
And this is the code:
<KeyboardAvoidingView style={{ flex: 1 }} behavior={"height"}>
<View style={style.container}>
<Header
title={t("shoppingList.title")}
view={AppView.SHOPPING_CART}
onDelete={() => setConfirmMultipleDeleteModalVisible(true)}
onSettings={() => setSettingsModalVisible(true)}
/>
<SettingsModal
visible={settingsModalVisible}
onSave={onSaveSettings}
onClose={() => setSettingsModalVisible(false)}
/>
<View style={{ flex: 1 }}>
<FlatList
data={shoppingList}
keyExtractor={(item, index) => index + ""}
ListHeaderComponent={
<ShoppingListTableHeader
columns={columns}
selectedColumn={selectedColumn}
direction={direction}
sort={sortTable}
/>
}
stickyHeaderIndices={[0]}
renderItem={({ item, index }) => {
return (
<GestureHandlerRootView>
<ConfirmMultipleDeleteModal
visible={confirmMultipleDeleteModalVisible}
items={shoppingList.filter(
(item) => selectedItemIds.indexOf(item.id) !== -1
)}
onDelete={multipleDeleteAndCloseConfirmModal}
onClose={() => setConfirmMultipleDeleteModalVisible(false)}
/>
<Pressable
onPress={() => {
handleOnPress(item);
mark(index);
}}
onLongPress={() => {
selectItems(item);
}}
>
<Card
style={{
...generalStyles.card,
backgroundColor: index % 2 == 1 ? "#F0F0F0" : "white",
}}
>
<View style={generalStyles.displayOnRow}>
{selectedItemIds.length > 0 && (
<Checkbox
status={
selectedItemIds.indexOf(item.id!) !== -1
? "checked"
: "unchecked"
}
onPress={() => handleOnPress(item)}
color={"#585858"}
uncheckedColor={"black"}
/>
)}
<ShoppingItem
item={item}
index={index}
marked={shoppingList[index].bought}
/>
</View>
{selectedItemIds.indexOf(item.id!) !== -1 && (
<View style={generalStyles.overlay} />
)}
</Card>
</Pressable>
</GestureHandlerRootView>
);
}}
/>
</View>
{addModalVisible ? (
<AddShoppingListItem
onSave={onSaveItem}
onClose={() => setAddModalVisible(false)}
/>
) : (
<MaterialCommunityIcons
name={"cart-plus"}
size={40}
onPress={() => setAddModalVisible(true)}
style={style.addIcon}
/>
)}
<StatusBar style="auto" />
</View>
</KeyboardAvoidingView>
export const style = StyleSheet.create({
container: {
flex: 1,
paddingTop: 40,
width: "100%",
}
});
I also tried to put the component inside
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.container}>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
but it didn't work :(
I am wondering ifit is related to the fact that I am using
createMaterialBottomTabNavigator();
Any suggestions, pleaseee?
The docs suggest using behavior='padding' on iOS. https://docs.expo.dev/versions/latest/react-native/keyboardavoidingview/
If that doesn't work for you, try experimenting with a negative keyboardVerticalOffset on the KeyboardAvoidingView.

MUI - Open datepicker when clicking anywhere in the TextField

I have a date picker that I want to show when the users click anywhere in the field not just on the calendar icon.
Here's the picker
export function DatePickerField(props) {
......
return (
<Grid container>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<KeyboardDatePicker
{...field}
{...props}
disableToolbar
inputVariant="outlined"
value={selectedDate}
onChange={_onChange}
error={isError}
autoOk
invalidDateMessage={isError && error}
helperText={isError && error}
/>
</MuiPickersUtilsProvider>
</Grid>
);
}
I need to do this because if date entered manually, it throws no errors but I get invalid date in the form data.
How can show the picker when the field is clicked?
MUI v5 added the DatePicker component in the #mui/lab package. If you want a picker to be opened after when the user clicked inside the TextField, use MobileDatePicker, this doesn't have the calendar icon though, see this answer if you want to have one.
<MobileDatePicker
{...}
renderInput={(params) => <TextField {...params} />}
/>
The DesktopDatePicker however does have the calendar icon, but you have to write additional code to control the open state and tell the picker to open when TextField is clicked:
<DatePicker
open={open}
onOpen={() => setOpen(true)}
onClose={() => setOpen(false)}
renderInput={(params) => {
return (
<TextField
{...params}
onClick={(e) => setOpen(true)}
/>
);
}}
/>
Original Answer
You can control the KeyboardDatePicker's open state and set it to true when the TextField is clicked:
const [open, setOpen] = useState(false);
return (
<KeyboardDatePicker
open={open}
onOpen={() => setOpen(true)}
onClose={() => setOpen(false)}
TextFieldComponent={(props) => (
<TextField {...props} onClick={(e) => setOpen(true)} />
)}
{...other}
/>
);
Live Demo

Material UI Selected Option Not Showing in Select Box

I have the following:
--- before render ---
const fontArray = [
["Standard", "Standard"], ["Abril FatFace", "'Abril Fatface', cursive"],
["Alfa Slab One", "'Alfa Slab One', cursive"],
["Chonburi", "'Chonburi', cursive"], ["Comfortaa", "'Comfortaa', cursive"],
["Lobster", "'Lobster', cursive"], ["Pacfico", "'Pacifico', cursive"]
]
--- in render ---
<FormControl style={{margin: '10px'}}>
<InputLabel htmlFor="select-font">Font</InputLabel>
<Select
value={this.state.font[0]}
onChange={(evt)=>this.handleFontChange(evt)}
inputProps={{
name: 'font',
id: 'select-font',
}}
>
{fontArray.map((font, index)=>{
return(
<MenuItem key={font} value={font}>
<div style={{fontFamily: `${font[1]}`}}>
{font[0]}
</div>
</MenuItem>
)
})}
</Select>
</FormControl>
And as you can guess the current font is held in state.
--- Here is how I handle select change ---
handleFontChange = (event) => {
this.setState({ font: event.target.value })
};
So what I want is to be able to have a font select, where the font is shown. It almost works. For example, when I click the select I get:
However, the select itself is empty (even when I've confirmed that state is populated:
What am I doing wrong? Maybe material-ui can't handle stylized default text?
EDIT: The two answers below seem close, but not quite right for what I'm trying to do.
If you replace
<MenuItem key={font} value={font}>
with
<MenuItem key={font} value={font[0]}>
It does replace the font with the correct selected value. Great!
...but it also then replaces this.state.font with this.state.font[0]. I'm currently attempting to get this to work by changing the handle function like this:
handleFontChange = (event, fontArray, stateData) => {
let newFont = fontArray.filter(i=>{
if(i[0]==event.target.value){
return i
}
})
this.setState({ font: newFont })
};
Which seems to set this.state.font correctly, but it again doesn't
seem to want to make the select box show the selected font.
Hmmm....
SOLVED
Here is a modification of a solution below:
Using
renderValue = (value) => {
return(
<div style={{fontFamily: `${value[1]}`}}>
{value[0]}
</div>
)
}
and
<...>
<Select
value={this.state.font}
renderValue={() => this.renderValue(this.state.font)}
<...>
Gives...
You can use renderValue to solve this.
renderValue = (value) => {
return value && value[0];
}
in render method
<FormControl style={{margin: 10}}>
<InputLabel htmlFor="select-font">Font</InputLabel>
<Select
value={this.state.font}
renderValue={() => this.renderValue(this.state.font)}
onChange={evt => this.handleFontChange(evt)}
inputProps={{
name: "font",
id: "select-font"
}}
>
{fontArray.map((font, index) => {
return (
<MenuItem key={index} value={font}>
<div style={{fontFamily: `${font[1]}`}}>
{font[0]}
</div>
</MenuItem>
);
})}
</Select>
</FormControl>
<...>
<Select
value={this.state.font?this.state.font :defaultvlue}
renderValue={() => this.renderValue(this.state.font)}
<...>
you can use ternary operator ,if you have data show data else default value

How to print values of Material-Ui redux form to Material-Ui Table after pressing Submit button?

I have a FieldArray input in my redux-form and want to print the values of that form on the table. I am new to React but have some JavaScript experience. I want to have the values of the form to print on the table . .......................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
APP.JS -
const renderMembers = ({ fields, meta: { error, submitFailed } }) => (
<ul>
<li>
<FloatingActionButton onClick={() => fields.push({})}>
<ContentAdd />
</FloatingActionButton>
{submitFailed && error && <span>{error}</span>}
</li>
{fields.map((member, index) => (
const {
handleChange,
handleSubmit,
pristine,
values,
reset,
submitting
} = props
return (
<MuiThemeProvider>
<form onSubmit={handleSubmit}>
<FieldArray name="members" component={renderMembers}/>
<div>
<RaisedButton disabled={submitting}
type="submit"
label="Submit"
labelPosition="after"
primary={true}
icon={<ActionAndroid />}
/>
/>
</div>
</form>
<div>
<Table>
<TableHeader>
<TableRow>
<TableHeaderColumn>ID</TableHeaderColumn>
<TableHeaderColumn>First Name</TableHeaderColumn>
<TableHeaderColumn>Last Name</TableHeaderColumn>
<TableHeaderColumn>List of Hobbies (1 - 5)</TableHeaderColumn>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableRowColumn>want to print the value here</TableRowColumn>
<TableRowColumn>want to print the value here</TableRowColumn>
<TableRowColumn>want to print the value here</TableRowColumn>
<TableRowColumn>want to print the value here</TableRowColumn>
</TableRow>
</TableBody>
</Table>
</div>
</MuiThemeProvider>
);
};
class App extends Component {
render() {
return (
<div className="App">
<SignInForm onSubmit={this.submit} />
</div>
);
}
}

Resources