How to update input value on click of button from another page? - javascript

In React project I've certain list of data populated in grid component. The grid has columns of varied data types like text, input field, link etc. In one such column an input field is mapped which has its respective default values.
Now my intention is to change name of that particular input field of a particular record which navigates to another page which, has a button when clicked changes the name on the grid, but, the name of all records are changed. I need to change name of only that single record which is clicked and navigated based on its 'id'. Please refer to the code below.
const newCompData = [
{
id: 1,
comp: "McDonalds",
feedback: "Best Food Chain",
name: "Mike John",
est: "YYYY/MM",
store: "Burger Store"
},
{
id: 2,
comp: "KFC",
feedback: "Best Chicken Products",
store: "Chicken Food",
name: "Steve Williams",
est: "YYYY/MM"
},
{
id: 3,
comp: "Dominos",
feedback: "Best Pizza Store",
store: "Pizza Store",
name: "Mark Rays",
est: "YYYY/MM"
},
{
id: 4,
comp: "Star Bucks",
feedback: "Best Coffee Store",
store: "Coffee Store",
name: "Patrick Right",
est: "YYYY/MM"
},
{
id: 5,
comp: "Burger King",
feedback: "Best Burgers",
store: "Burger Store",
name: "Williams Wills",
est: "YYYY/MM"
},
{
id: 6,
comp: "Lays",
feedback: "Best Chips Factory",
store: "Chips Store",
name: "Sam Andrews",
est: "YYYY/MM"
}
];
const [dataAll, setDataAll] = useState([]);
useEffect(() => {
const newData = newCompData?.map((data) => {
return [
{ id: data.id },
data.comp,
data.store,
data.name,
data.est,
data.feedback
];
});
setDataAll(newData);
}, []);
return (
<div className="App">
<Table newData={dataAll} />
</div>
);
};
As seen above.... a list of records are mapped into the table component
Here is the table component, where all the data is mapped
const Table = ({ newData }) => {
useEffect(() => {
setGridData({
data: newData,
page_info: {
total_pages: 5,
current_page: 1
}
});
}, [newData]);
let GridConfig = {};
GridConfig = TableConfig;
const [gridConfigData, setGridConfigData] = useState(GridConfig);
const [gridData, setGridData] = useState(newData);
return (
<>
<Grid GridConfig={gridConfigData} GridData={gridData} />
</>
);
};
Following is the Grid component in table format (only showing the required part)
const Grid = (props) => {
let colConfig = props.GridConfig.column_config;
let gridData = props.GridData?.data;
const { newValue } = useContext(GlobalContext);
const navigate = useNavigate();
return (
....
....
....
{colConfig[cIndex].data_type === "input_text" &&
!colConfig[cIndex].cell_click_callback && (
<input
type="text"
defaultValue={
newValue != undefined ? newValue : colData
}
/>
)}
....
....
....
)
}
export default Grid
This is the page when clicked on record is navigated here.
const Test = () => {
const location = useLocation();
const navigate = useNavigate();
const newId = location.state?.id;
const { setNewValue } = useContext(GlobalContext);
const handleClick = () => {
navigate("/");
// When clicked on this setValue shows the same value across all records
setNewValue("John Spencer");
};
return (
<>
<h2>Test Page</h2>
<p>ID: {newId}</p>
<button onClick={handleClick}>Go Back</button>
</>
);
};
I want to show the value for input text which was clicked on Test page. Only specific record should be updated. What is the best solution to tackle this issue?
Please refer to the Codesandbox link: https://codesandbox.io/s/elated-varahamihira-xpjtdb

Related

How to input and create two (or more than two) fields using React-Admin?

The following code provided in React-Admin docs lets me pick a record and enter only ONE field into the database:
const choices = [
{ id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
{ id: 456, first_name: 'Jane', last_name: 'Austen' },
];
const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;
<AutocompleteInput source="author_id" choices={choices} optionText={optionRenderer} optionValue = "first_name" />
So in this case, a field called "first_name" would be inserted into my database table.
Is there a way to enter all three fields as an input? For example, I would want 3 separate fields
id: 456,
first_name: 'Jane',
last_name: 'Austen'
to be inserted into the database(not as a dictionary of 3 fields but 3 independent fields).
<AutocompleteInput> allows to select an existing record related to the current one (e.g. choosing the author for a post).
I understand that you want to create a new record instead. You can do so via the onCreate prop, as explained in the doc:
import { AutocompleteInput, Create, SimpleForm, TextInput } from 'react-admin';
const PostCreate = () => {
const categories = [
{ name: 'Tech', id: 'tech' },
{ name: 'Lifestyle', id: 'lifestyle' },
];
return (
<Create>
<SimpleForm>
<TextInput source="title" />
<AutocompleteInput
onCreate={(filter) => {
const newCategoryName = window.prompt('Enter a new category', filter);
const newCategory = { id: categories.length + 1, name: newCategoryName };
categories.push(newCategory);
return newCategory;
}}
source="category"
choices={categories}
/>
</SimpleForm>
</Create>
);
}

How to use drag and drop in ant design?

What I want is an example about how to make the drag and drop of my Table that works properly, but I cannot figure out how to make it works in (functional components)
My code :
function Preview() {
const { name } = useParams();
const [fieldsOfForm, setFieldsOfForm] = useState([]);
const [selectedForm, setSelectedForm] = useState([]);
const columns = [
{
title: 'Posição',
dataIndex: 'pos',
width: 30,
className: 'drag-visible',
render: () =>
<MenuOutlined style={{ cursor: 'grab', color: '#999' }}/>
},
{
title: "Form Name",
dataIndex: "field",
key: "field",
render: (text) => <a>{text}</a>,
},
{
title: "Tipo",
dataIndex: "fieldtype",
key: "fieldtype",
},
];
useEffect(() => {
let mounted = true;
let loadingStates = loading;
if (mounted) {
setFieldsOfForm(location.state);
loadingStates.allFields = false;
setLoading(false);
}
return () => (mounted = false);
}, [selectedForm]);
return (
//Some jsx....
<Row gutter={1}>
<Col span={1}></Col>
<Table dataSource={fieldsOfForm}
columns= {columns}/>
</Row>
// More jsx...
);
}
export default Preview;
Everything that I found on internet about this drag and drop from the lib of antd is in class component , but I wanted to make it works in my functional one.
Example that I found :
multi row drag-able table (antd)
I want some example in function component if someone has tried it already and could help me ?
Here's a functional working example:
import React from "react";
import "antd/dist/antd.css";
import { Table } from "antd";
import {
sortableContainer,
sortableElement,
sortableHandle
} from "react-sortable-hoc";
import { MenuOutlined } from "#ant-design/icons";
const data = [
{
key: "1",
name: "John Brown",
age: 32,
address: "New York No. 1 Lake Park",
index: 0
},
{
key: "2",
name: "Jim Green",
age: 42,
address: "London No. 1 Lake Park",
index: 1
},
{
key: "3",
name: "Joe Black",
age: 32,
address: "Sidney No. 1 Lake Park",
index: 2
},
{
key: "4",
name: "4",
age: 32,
address: "New York No. 1 Lake Park",
index: 3
},
{
key: "5",
name: "5",
age: 42,
address: "London No. 1 Lake Park",
index: 4
},
{
key: "6",
name: "6",
age: 32,
address: "Sidney No. 1 Lake Park",
index: 5
}
];
const DragHandle = sortableHandle(({ active }) => (
<MenuOutlined style={{ cursor: "grab", color: active ? "blue" : "#999" }} />
));
const SortableItem = sortableElement((props) => <tr {...props} />);
const SortableContainer = sortableContainer((props) => <tbody {...props} />);
function SortableTable() {
const [dataSource, setDataSource] = React.useState(data);
const [selectedItems, setSelectedItems] = React.useState([]);
const getColumns = () => {
return [
{
title: "Sort",
dataIndex: "",
width: 30,
className: "drag-visible",
render: (d, dd, i) => (
<>
<DragHandle active={selectedItems.includes(i)} />
</>
)
},
{
title: "Name",
dataIndex: "name",
className: "drag-visible"
},
{
title: "Age",
dataIndex: "age"
},
{
title: "Address",
dataIndex: "address"
}
];
};
const merge = (a, b, i = 0) => {
let aa = [...a];
return [...a.slice(0, i), ...b, ...aa.slice(i, aa.length)];
};
const onSortEnd = ({ oldIndex, newIndex }) => {
let tempDataSource = dataSource;
if (oldIndex !== newIndex) {
if (!selectedItems.length) {
let movingItem = tempDataSource[oldIndex];
tempDataSource.splice(oldIndex, 1);
tempDataSource = merge(tempDataSource, [movingItem], newIndex);
} else {
let filteredItems = [];
selectedItems.forEach((d) => {
filteredItems.push(tempDataSource[d]);
});
let newData = [];
tempDataSource.forEach((d, i) => {
if (!selectedItems.includes(i)) {
newData.push(d);
}
});
tempDataSource = [...newData];
tempDataSource = merge(tempDataSource, filteredItems, newIndex);
}
setDataSource(tempDataSource);
setSelectedItems([]);
}
};
const DraggableContainer = (props) => (
<SortableContainer
useDragHandle
disableAutoscroll
helperClass="row-dragging"
onSortEnd={onSortEnd}
{...props}
/>
);
const DraggableBodyRow = ({ className, style, ...restProps }) => {
// function findIndex base on Table rowKey props and should always be a right array index
const index = dataSource.findIndex(
(x) => x.index === restProps["data-row-key"]
);
return (
<SortableItem
index={index}
{...restProps}
selected={selectedItems.length}
onClick={(e) => {
if (e.ctrlKey || e.metaKey) {
selectedItems.includes(index)
? selectedItems.splice(selectedItems.indexOf(index), 1)
: selectedItems.push(index);
setSelectedItems(selectedItems);
} else {
setSelectedItems([]);
}
}}
/>
);
};
return (
<>
<h3>"CNTRL + Click" to select multiple items</h3>
<Table
pagination={false}
dataSource={dataSource}
columns={getColumns()}
rowKey="index"
components={{
body: {
wrapper: DraggableContainer,
row: DraggableBodyRow
}
}}
/>
{selectedItems.length ? <>{selectedItems.length} items selected </> : ""}
</>
);
}
You can play with it in Sandbox

Show dynamically added row in expanded form in ant tables

I have an ant table where I pass a parameter "defaultExpandAllRows=true" which renders the table in an expanded form.
<Table
columns={columns}
pagination={false}
expandable={{
expandedRowRender: (record) => (
<p style={{ margin: 0 }}>{record.description}</p>
),
defaultExpandAllRows: true,
rowExpandable: (record) => record.name !== "Not Expandable"
}}
dataSource={customScopeTableData}
/>
My use case is to show the dynamically added row in expanded form.
Here is the working sample code
https://codesandbox.io/s/dynamic-expandable-row-issue-f6bn5?file=/index.js
I couldn't find something in the API doc.
Any help on this would be appreciable.
I think you should use expandedRowKeys instead of defaultExpandAllRows. Because of defaultExpandAllRows only apply to the initial data, so newly added rows won't be affected.
expandable={{
expandedRowRender: (record) => (
<p style={{ margin: 0 }}>{record.description}</p>
),
rowExpandable: (record) => record.name !== "Not Expandable",
expandedRowKeys: expandedRowKeys,
onExpand: (expanded, record) => {
updateExpandedRowKeys({ record });
}
}}
First create a state for expandedRowKeys and a function for update expandedRowKeys
const [expandedRowKeys, setExpandedRowKeys] = useState(data.map(({ key }) => key));
const updateExpandedRowKeys = ({ record }) => {
const rowKey = record.key;
const isExpanded = expandedRowKeys.find(key => key === rowKey);
let newExpandedRowKeys = [];
if (isExpanded) {
newExpandedRowKeys = expandedRowKeys.reduce((acc, key) => {
if (key !== rowKey) acc.push(key);
return acc;
}, []);
} else {
newExpandedRowKeys = expandedRowKeys;
newExpandedRowKeys.push(rowKey);
}
setExpandedRowKeys(newExpandedRowKeys);
};
Then insert new expandedRowKey when new row added
const addCustomField = () => {
const newIndex = customScopeTableData.slice(-1)[0].key + 1;
setCustomScopeTableData([...customScopeTableData, newEntry(newIndex)]);
setExpandedRowKeys([...expandedRowKeys, newIndex])
};
Column key for newEntry should be unique, Date.now() seems will be duplicated. I have changed it to incremental index.
const newIndex = customScopeTableData.slice(-1)[0].key + 1;
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Table, Button } from "antd";
const columns = [
{ title: "Name", dataIndex: "name", key: "name" },
{ title: "Age", dataIndex: "age", key: "age" },
{ title: "Address", dataIndex: "address", key: "address" },
{
title: "Action",
dataIndex: "",
key: "x",
render: () => <a>Delete</a>
}
];
const data = [
{
key: 1,
name: "John Brown",
age: 32,
address: "New York No. 1 Lake Park",
description:
"My name is John Brown, I am 32 years old, living in New York No. 1 Lake Park."
},
{
key: 2,
name: "Jim Green",
age: 42,
address: "London No. 1 Lake Park",
description:
"My name is Jim Green, I am 42 years old, living in London No. 1 Lake Park."
},
{
key: 3,
name: "Not Expandable",
age: 29,
address: "Jiangsu No. 1 Lake Park",
description: "This not expandable"
},
{
key: 4,
name: "Joe Black",
age: 32,
address: "Sidney No. 1 Lake Park",
description:
"My name is Joe Black, I am 32 years old, living in Sidney No. 1 Lake Park."
}
];
const newEntry = (key) => {
return {
key: key,
name: "John Brown",
age: 32,
address: "New York No. 1 Lake Park",
description:
"My name is John Brown, I am 32 years old, living in New York No. 1 Lake Park."
}
};
const TableComponent = () => {
const [customScopeTableData, setCustomScopeTableData] = useState(data);
const addCustomField = () => {
const newIndex = customScopeTableData.slice(-1)[0].key + 1;
setCustomScopeTableData([...customScopeTableData, newEntry(newIndex)]);
setExpandedRowKeys([...expandedRowKeys, newIndex])
};
const [expandedRowKeys, setExpandedRowKeys] = useState(data.map(({ key }) => key));
const updateExpandedRowKeys = ({ record }) => {
const rowKey = record.key;
const isExpanded = expandedRowKeys.find(key => key === rowKey);
let newExpandedRowKeys = [];
if (isExpanded) {
newExpandedRowKeys = expandedRowKeys.reduce((acc, key) => {
if (key !== rowKey) acc.push(key);
return acc;
}, []);
} else {
newExpandedRowKeys = expandedRowKeys;
newExpandedRowKeys.push(rowKey);
}
setExpandedRowKeys(newExpandedRowKeys);
};
return (
<>
<Table
columns={columns}
pagination={false}
expandable={{
expandedRowRender: (record) => (
<p style={{ margin: 0 }}>{record.description}</p>
),
rowExpandable: (record) => record.name !== "Not Expandable",
expandedRowKeys: expandedRowKeys,
onExpand: (expanded, record) => {
updateExpandedRowKeys({ record });
}
}}
dataSource={customScopeTableData}
/>
<Button type="dashed" onClick={() => addCustomField()}>
Add field
</Button>
</>
);
};
ReactDOM.render(<TableComponent />, document.getElementById("container"));

ReactJS hooks re-render functional components

When using functional component, dropdown re-render every time I set a state and the value in the dropdown does not change.
Edit:
I am using semantic ui react library. (https://react.semantic-ui.com/collections/form/#shorthand-subcomponent-control)
const Table = () => {
const [tickers, setTickers] = useState("")
const test = [{
key: 1,
text: "0001",
value: "0001"
},{
key: 2,
text: "0002",
value: "0002"
}]
const DropdownSelection = () => {
return (
<Form.Select
fluid
search
multiple
name="ticker"
options={test}
onChange={(e, { value }) => setTickers(value)}
placeholder="Tickers"
/>
)
}
return(
<Form>
<DropdownSelection />
</Form>)
However, it works when I put the component inside the return function. Although dropdown still being re-rendered, but the value is in it.
const Table = () => {
const [tickers, setTickers] = useState("")
const test = [{
key: 1,
text: "0001",
value: "0001"
},{
key: 2,
text: "0002",
value: "0002"
}]
return(
<Form>
<Form.Select
fluid
search
multiple
name="ticker"
options={test}
onChange={(e, { value }) => setTickers(value)}
placeholder="Tickers"
/>
</Form>)
What could be the problem?

return array of objects from props React JS

so I have the following component that is a dropdown list created using react-select.
import React from 'react'
import Select from 'react-select';
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' }
];
class MealsFilters extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedOption: null,
};
}
handleChange = (selectedOption) => {
this.setState({ selectedOption });
console.log(`Option selected:`, selectedOption);
}
render() {
const { selectedOption } = this.state;
return (
<div className="container my-3">
<div className="row">
<div className="col-lg-4 col-md-6 col-sm-8">
<Select
isMulti
isSearchable
placeholder={"catégories"}
value={selectedOption}
onChange={this.handleChange}
options={options}
/>
</div>
</div>
</div>
)
}
}
export default MealsFilters;
the options variable is the default one from the docs. I actually need to replace its values by each meal category available.
To do so, as you can see, I need to create an array of objects with a value and a label.
this component accesses meal categories through props called meals that are like so:
console.log(this.props.meals);
=> [{
id: 0,
name: spaghettis,
category: italian,
price: 5.99},
{
id: 1,
name: hamburger,
category: american,
price: 7.99},
{
etc.
}, {}]
How can I take advantage of this.props.meals to get my options array of objects ?
EDIT: multiple meals can have the same category, and I need each category to only appear once in the options.
Map over your this.props.meals array, and create the needed options array,
<Select
isMulti
isSearchable
placeholder={"catégories"}
value={selectedOption}
onChange={this.handleChange}
options={this.props.meal.map(item=>({value: item.id, label: item.name}))}
/>
You could do something like this:
options={this.props.meals.map(
({id, name})=>({value:id,label:name})
)}
You could also use redux connect to create a container that will map the data to dropdown values for you
You can merge the data by category in the following way:
var items = [
{
id: 0,
name: 'spaghettis',
category: 'italian',
price: 5.99,
},
{
id: 1,
name: 'hamburger',
category: 'american',
price: 7.99,
},
{
id: 2,
name: 'other hamburger',
category: 'american',
price: 7.99,
},
];
console.log(
[
...items.reduce(
(result, item) => (
result.get(item.category)
? result.get(item.category).push(item.id)
: result.set(item.category, [item.id]),
result
),
new Map(),
),
].map(([label, value]) => ({ label, value })),
);
In the component it'll look like this:
options={[
...this.props.meals.reduce(
(result, item) => (
result.get(item.category)
? result.get(item.category).push(item.id)
: result.set(item.category, [item.id]),
result
),
new Map(),
),
].map(([label, value]) => ({ label, value }))}
You only need the "name" property so when you map through meals, simply retrieve it. Then upper case the first letter.
const meals = [{
id: 0,
name: "spaghettis",
category: "italian",
price: 5.99
},
{
id: 1,
name: "hamburger",
category: "american",
price: 7.99
}
]
const result = meals.map(({name}) => ({
label: `${name[0].toUpperCase()}${name.slice(1)}`,
value: name
}))
console.log(result);
You can use getOptionLabel and getOptionValue props.
<Select
options={this.props.meals},
getOptionLabel={m => m.name}
getOptionValue={m => m.id} />
https://react-select.com/props
getOptionLabel generic = (option) => string
Resolves option data to a string to be displayed as the label by components
getOptionValue generic = (option) => string
Resolves option data to a string to compare options and specify value attributes

Categories

Resources