React change values of clicked array item - reactjs

In my React component I have an array which will render linked social icons:
icons: { [
{ iconName: 'twitter', url: '#'},
{ iconName: 'facebook', url: '#'},
{ iconName: 'instagram', url: '#'},
] },
In render I'm showing all the social icon links from the array:
icons.map( ( icon, index ) => (
<a key={ index }
href='#'
onClick={() => {
setAttributes({ iconClicked: index });
}}
>
<Icon icon={ icon.iconName } /> /* this just renders an svg icon */
</a>
) )
When I click on a social icon, I want to be able to populate a form with the icon name and url and be able to change the values.
To do that, I'm first setting itemClicked to the index of the icon I clicked on (using onClick seen above).
Note: itemClicked as well as the icons array are state variables which update on change.
Next I'm trying to display the icon name in a select list and the url in a text input:
<SelectControl
label="Icon Name"
value={ icons[iconClicked].iconName }
options={ [
{ label: __( 'Twitter' ), value: 'twitter' },
{ label: __( 'Facebook' ), value: 'facebook' },
{ label: __( 'Instagram' ), value: 'instagram' },
{ label: __( 'YouTube' ), value: 'youtube' },
] }
onChange={( value ) => {
const newIcon = icons[iconClicked].iconName;
setAttributes({ newIcon: value });
}}
/>
<TextControl
label={ __( 'Enter URL' ) }
value={ icons[iconClicked].url }
onChange={( value ) => {
const newUrl = icons[iconClicked].url;
setAttributes({ newUrl: value });
}}
/>
When I click any icon, the select and text control populate with the correct values but they do not update when I try to change the value. I do not get errors, but the initial values don't change when onChange is triggered.
How can I make this work?

Related

How to assign fields to each tab in WP Gutenberg TabPanel

I know it's a simple question, but I am new in the React and WP Gutenberg world.
I am trying to create a plugin settings page using Gutenberg Components.
The tabs working fine and i know how to render fields, but i can't figure out how to assign to each tab the fields i want.
Here is the sample code:
import { TabPanel } from '#wordpress/components';
const onSelect = ( tabName ) => {
console.log( 'Selecting tab', tabName );
};
const MyTabPanel = () => (
<TabPanel
className="my-tab-panel"
activeClass="active-tab"
onSelect={ onSelect }
tabs={ [
{
name: 'tab1',
title: 'Tab 1',
className: 'tab-one',
},
{
name: 'tab2',
title: 'Tab 2',
className: 'tab-two',
},
] }
>
{ ( tab ) => <p>{ tab.title }</p> }
</TabPanel>
);
The Developer guide for Tab Panel mentions that
tabs is an array of objects, which new properties can be added to. By adding a new property, eg content to each of your tab objects, additional components can be set to render for each tab. The simple example uses ( tab ) => <p>{ tab.title }</p> but this could also include your own custom/existing components defined in content, eg:
import { Button, TabPanel } from '#wordpress/components';
const onSelect = (tabName) => {
console.log('Selecting tab', tabName);
};
const MyTabPanel = () => (
<TabPanel
className="my-tab-panel"
activeClass="active-tab"
onSelect={onSelect}
tabs={[
{
name: 'tab1',
title: 'Tab 1',
className: 'tab-one',
content: <p>Some content goes here</p>
},
{
name: 'tab2',
title: 'Tab 2',
className: 'tab-two',
content: <MyCustomComponent />
},
]}
>
{({ title, content, className }) => <div className={className}><h3>{title}</h3>{content}</div>}
</TabPanel>
);
const MyCustomComponent = () => {
return <><p>Here is a button</p><Button variant="secondary">Click me!</Button></>;
}
The children function (given as anonymous function, above </TabPanel>) renders the tabviews by passing in the active tab object, which now includes the extra content for the tab. The output of the example above is:
Aside from the Developer Guide, the Gutenberg source code is a good place to look for other examples of how TabPanel component can be implemented.. Hope this helps you with adding fields to your tabs..

How to change the url dinamically when redirecting on click? React

How to dinamically change the url? This url 'www.example.com' is changing depending on where i use it as you can see it below, also the query string is changing depending on the component and on the card-id.
Here is simple example of my code:
return (
props.news.map((content, index) =>
<NewsCard
key={index}
title={content.title}
text={content.text}
onClick={() => {
location.href = 'www.example-news.com/?a=query';
}}
/>
<JurnalistCard
key={index}
person={content.person}
name={content.name}
onClick={() => {
location.href = 'www.example-jurnaist.com/?a=query';
}}/>
))
Json file:
export const news: News[] = [
{
title: 'Title1',
text: 'Text test',
queryString1: 'queryString',
queryString2: 'queryString',
person: 'Full Name',
name: 'John'
},
{
title: 'Title1',
text: 'Text test',
queryString1: 'queryString',
queryString2: 'queryString',
person: 'Full Name',
name: 'John'
},
{
title: 'Title1',
text: 'Text test',
queryString1: 'queryString',
queryString2: 'queryString',
person: 'Full Name',
name: 'John'
}
]
I am new in React, please help.
You could send the url in props and do something like this
return (
props.news.map((content, index) =>
<NewsCard
key={index}
title={content.title}
text-{content.text}
onClick={() => {
location.href = content.newsUrl;
}}
/>
<JurnalistCard
key={index}
person={content.person}
name={content.name}
onClick={() => {
location.href = content.journalistUrl;
}}/>
))
If you can't store the url in props and you use other values to form the query, you could structure it like this:
{"'www.example-news.com/?a=" + content.title}

How to make some values default vaslues and enable and other no default and disbale in an admin dialog which activate contact form fields

I have a React app with an admin panel and one of the dialogs is for contact form fields.
Essentially in the contact form dialog there are switches which all by default are enable and those turn on the fields in the main app.
The screenshot shows the fields how are in the default way all turned on.
Right now the issue here is that the last field preferredContactWay should be off by default.
My solution to this problem was as follow but is a bad solution will be needed a more efficient one and less redundant code. A better solution is what I seek to my problem.
I created a second object called availableContactFields and removed from the original defaultContactFields preferredContactWay field.
const defaultContactFields = {
name: {
name: 'name',
type: 'name',
},
email: {
name: 'email',
type: 'email',
confirm: true,
},
phone: {
name: 'phone',
type: 'phone',
},
preferredContactHours: {
name: 'preferredContactHours',
type: 'select',
options: ['8-20', '8-12', '12-16', '16-20'],
defaultValue: '8-20',
},
};
const availableContactFields = {
name: {
name: 'name',
type: 'name',
},
email: {
name: 'email',
type: 'email',
confirm: true,
},
phone: {
name: 'phone',
type: 'phone',
},
preferredContactHours: {
name: 'preferredContactHours',
type: 'select',
options: ['8-20', '8-12', '12-16', '16-20'],
defaultValue: '8-20',
},
preferredContactWay: {
name: 'preferredContactWay',
type: 'select',
options: ['Phone call', 'SMS', 'Email'],
defaultValue: 'Phone call',
},
};
Then I have the next part function and I'm passing the default fields to have as default what should on and so the preferredContact way remains off initially
function EditContactFormDialog({ closeDialog, handleEdit, editItem }) {
const classes = useStyles();
const [disabled, setDisabled] = useState(false);
const [internalValue, setInternalValue] = useState(
get(editItem, 'value') || Object.values(defaultContactFields),
);
console.log({ internalValue });
function toggleEnabled(name, enabled) {
console.log(name, enabled);
const onlyEnabled = key =>
(internalValue.some(val => val.name === key) && key !== name) ||
(key === name && enabled);
const toConfig = key =>
internalValue.find(val => val.name === key) ||
availableContactFields[key];
setInternalValue(
supported
.map(itm => itm.name)
.filter(onlyEnabled)
.map(toConfig),
);
}
function toggleConfirm(name, confirm) {
setInternalValue(
internalValue.map(val => (val.name === name ? { ...val, confirm } : val)),
);
}
function toggleOptions(name, options) {
setInternalValue(
internalValue.map(val => (val.name === name ? { ...val, options } : val)),
);
}
the last part is the return of the component the map is taking this
const supported = [
{ name: 'name' },
{ name: 'email', confirmable: true },
{ name: 'phone', confirmable: true },
{ name: 'preferredContactHours' },
{ name: 'preferredContactWay' },
];
Then here the switches are rendered
return (
<Dialog open>
<DialogTitle className={classes.dialogTitle}>
Customize contact form fields
</DialogTitle>
<DialogContent>
<Table>
<TableBody>
{supported.map(({ name, confirmable }) => {
const value = internalValue.find(item => item.name === name);
console.log({ internalValue });
const isEnabled = Boolean(value);
console.log('boolean ->', { value }, ': ', { name }, ' : ', {
isEnabled,
});
const isConfirm = isEnabled && value.confirm;
const { options } = availableContactFields[name];
const valueOptions = get(value, 'options', []);
console.log(name, value);
return (
<TableRow key={name}>
<TableCell>
<FormControlLabel
control={
<Switch
color="primary"
checked={isEnabled}
onChange={({ target: { checked } }) =>
toggleEnabled(name, checked)
}
/>
}
label={name}
/>
</TableCell>
<TableCell>
{confirmable && (
<FormControlLabel
disabled={!isEnabled}
control={
<Checkbox
checked={isConfirm}
onChange={({ target: { checked } }) =>
toggleConfirm(name, checked)
}
/>
}
label="Confirm"
/>
)}
{options && (
<FormControl
disabled={!isEnabled}
className={classes.fullWidth}
>
<InputLabel>Options</InputLabel>
<Select
multiple
value={valueOptions}
onChange={({ target }) =>
toggleOptions(name, target.value)
}
renderValue={selected => selected.join(', ')}
>
{options.map(option => (
<MenuItem key={option} value={option}>
<Checkbox
checked={valueOptions.includes(option)}
/>
<ListItemText primary={option} />
</MenuItem>
))}
</Select>
</FormControl>
)}
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</DialogContent>
<DialogActions style={{ justifyContent: 'space-between' }}>
<Button disabled={disabled} onClick={closeDialog}>
Cancel
</Button>
<Button
color="primary"
variant="contained"
disabled={disabled || internalValue.length < 1}
onClick={() => {
setDisabled(true);
handleEdit(internalValue);
}}
>
{disabled && <CircularProgress size={18} />}
Save
</Button>
</DialogActions>
</Dialog>
);
}
Answering my own question as I found a solution and could be ok at least doing the same as before.
To avoid having redundant code I added the following 2 lines inside the beginning of the component
const availableContactFields = { ...defaultContactFields };
delete availableContactFields.preferredContactWay;
const [internalValue, setInternalValue] = useState(
get(editItem, 'value') || Object.values(availableContactFields),
);
In this way I'm deleting from the default fields the preferredContactWay and then the switch is kept off in default session.
Not sure is the right solution but at this moment work

Cant turn on switch inside Material-Table

I am trying to create material-table with switches, that onclick changes the state of the component.
Here is component with table, which has state of the parent component as a props. I pass rows variable to the material table data prop. Switch is custom rendered field. Whenever I click on it, it triggers changeRow, which finds index of row in rows variable changes it and saves into new variable. ChangeRows is then called to change the state.
The problem is, the switch is not changing. It looks like nothing is happenning, even though I can clearly see new state in console.
const StuffTable = ({rows, changeRows}) => {
const changeRow = (oldRow, e) => {
const changeData = {[e.target.name]: e.target.checked};
const newRow = {...oldRow, ...changeData};
console.log(oldRow, e);
const index = rows.findIndex(dtaRow => dtaRow.id === oldRow.id);
const newData = rows;
newData[index] = newRow;
console.log(newData);
changeRows(newData);
};
return (
<Container maxWidth="lg">
<Button onClick={() => { changeRow({id: 6}, { target: {name: 'borrowable', checked: true} }) }}>klikni</Button>
<MaterialTable
options={{
actionsColumnIndex: -1,
search: true,
exportButton: true,
exportDelimiter: ";"
}}
actions={[
{
icon: 'edit',
tooltip: 'Edit Study',
onClick: (event, rowData) => alert("Do you want to edit?")
}]}
columns={[
{ title: "Název", field: "title" },
{ title: "Stav", field: "status", render: (data) => <Chip label={data.status} color="primary" avatar={<Avatar src="/static/images/avatar/1.jpg" />} /> },
{ title: "Půjčovat", field: "borrowable", render: (data, id) => (<FormControlLabel control={<Switch checked={data.borrowable} onChange={(e) => changeRow(data, e)} name="borrowable" color="primary"/>} label={data.borrowable ? 'půjčovat' : 'nepůjčovat'} />) },
{ title: "Vidí všichni", field: "active", render: (data, id) => (<FormControlLabel control={<Switch checked={data.borrowable} onChange={(e) => changeRow(data, e)} name="borrowable" color="primary"/>} label={data.borrowable ? 'půjčovat' : 'nepůjčovat'} />) },
{ title: "Uskladněno", field: "location" },
]}
data={rows}
title="Moje věci"
/>
</Container>
);
};
export default StuffTable;
I tried to add button, which on click changes state to empty array, and table did show nothing. But when I triggered changeRow (mockup data) with this button, result was the same - no change on the switch.
import React, {useEffect, useState} from 'react';
import StuffTable from "../components/stuffTable";
let rows = [
{id:5, title: "prošívanice", borrowable: false, surname: "Baran", status: "zapůjčeno", location: "Praha" },
{id:6, title: "prošívanice 2", borrowable: false, surname: "Baran", status: "zapůjčeno", location: "Praha" },
{id:7, title: "prošívanice 3", borrowable: false, surname: "Baran", status: "zapůjčeno" , location: "Brno"}
];
Here is Parent component
const MyStuffPage = () => {
const [data, setData] = useState(rows);
return (
<div>
<StuffTable rows={data} changeRows={(data) => {setData(data); console.log("hou",data);}} />
</div>
);
};
export default MyStuffPage;
Here is Codesandbox with this problem:
https://codesandbox.io/s/festive-gould-i1jf7
You need to call onQueryChange whenever you want to render new data or state to the datatable, make these changes:
at the begining create a ref like so:
const tableRef = useRef(null);
then use it in the material table:
<MaterialTable
//add this
tableRef={tableRef}
options={{
actionsColumnIndex: -1,
search: true,
exportButton: true,
exportDelimiter: ";"
}}
then inside your changeRow function after updating the start and the necessary work add this:
tableRef.current.onQueryChange()
this will tell the table to render the new data with the correct state of the switch

Custom filter the children of antd table dataSource

I'm using antd's table component. I want to be able to filter the nodes down to any descendant.
I'm following the example of antd's custom filter.
The example is not a tree structure, so here's a codesandbox with the right setup.
onFilter seems to only loop over the root nodes. I don't know how to display Tesla > Model 3 it I type '3' in the search input. I know I have to return true for Tesla, but I still don't understand how to then also return true for Model 3
data
const data = [
{
key: "13",
name: "Tesla",
data: {
description: "Car manufacturer",
type: "Organization"
},
children: [
{
key: "4444",
name: "Model S",
data: {
description: "The fastest",
type: "Project"
}
},
{
key: "1444323",
name: "Model 3",
data: {
description: "The cheapest",
type: "Project"
}
}
]
},
{
key: "1",
name: "Microsoft",
data: {
description: "#1 software company",
type: "Organization"
}
}
];
App
class App extends React.Component {
state = {
searchText: '',
};
getColumnSearchProps = (dataIndex) => ({
filterDropdown: ({
setSelectedKeys, selectedKeys, confirm, clearFilters,
}) => (
<div style={{ padding: 8 }}>
<Input
placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => this.handleSearch(selectedKeys, confirm)}
/>
<Button
type="primary"
onClick={() => this.handleSearch(selectedKeys, confirm)}
icon="search"
>
Search
</Button>
</div>
),
// onFilter seems to only loop over root nodes
onFilter: (value, record) => record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()),
render: (text) => (
<Highlighter
...
/>
),
})
render() {
const columns = [{
title: 'Name',
dataIndex: 'name',
key: 'name',
...this.getColumnSearchProps('name'),
}];
return <Table columns={columns} dataSource={data} />;
}
}
You can write a function to get a node's descendant values (based on dataIndex) and filter for any of those having the search text too.
This would go inside of getColumnSearchProps before the return value:
const getDescendantValues = (record) => {
const values = [];
(function recurse(record) {
values.push(record[dataIndex].toString().toLowerCase());
record.children.forEach(recurse);
})(record);
return values;
}
...and this would be the updated onFilter:
onFilter: (value, record) => {
const recordName = record[dataIndex] || record.data[dataIndex];
const searchLower = value.toLowerCase();
return recordName
.toString()
.toLowerCase()
.includes(searchLower)
||
getDescendantValues(record).some(descValue => descValue.includes(searchLower));
}
I've forked your sandbox here: https://codesandbox.io/s/10jp9wql1j
...it includes all the root nodes with any descendant that satisfies your search condition. However it does not filter out the other descendant nodes that do not satisfy your search condition, unfortunately.
After some searching, it seems like there isn't a great way to do this.
I'm using mobx and React, but the basic principles can be applied elsewhere. The idea is to give the rows you want to filter out a CSS class name to hide them.
I used mobx to create an observable of the checked filter values. You could also use React.useState. Using your example, let's say the observable is called ModelFilters. Then, on the table component, add rowClassName:
<Table
...
rowClassName={(record, index) => {
if (ModelFilters.includes(record.model) ) {
return ''
}
return 'some-css-class-with-display-none'
}}
/>
Any record that doesn't have a model that matches the selected filters will be hidden. Of course, if you are using the table to manipulate/submit data, you will have to handle that elsewhere. This is just for displaying purposes.

Resources