how to watch field in react (dynamically add useEffect)? - javascript

I am trying to watch a field which have watch:true field.In other words I want add useEffect dynamically.I have one json (which is coming from server).I want to watch field value (which have property watch:true).using it's value I want to update other field value .
here is my code
https://codesandbox.io/s/crimson-violet-uxbzd
see this object have watch: true, so I need to watch or check if it value is changed or not
{
label: "First",
name: "first",
type: "select",
remove: true,
watch: true,
options: ["", "one", "two"]
},
if it's value is changed then call this function
const getSecondDropdownValue = function(value) {
return new Promise((resolve, reject) => {
if (value === "one") {
setTimeout(function() {
resolve(["three", "four"]);
}, 1000);
}
if (value === "two") {
setTimeout(function() {
resolve(["five", "six"]);
}, 1000);
}
});
};
any update?.

Check if the item has watch property, if it does pass getSecondDropdownValue to onChange event of the select option. something like
<select onChange={hasWatch ? getSecondDropdownValue : () => {}}>...</select>
Create a component that will render select options.
// options = array of list options
// onChange = onchange event handler
// name = name of the select
const Option = ({ options, onChange, name }) =>
(options.length && (
<select onChange={(e) => onChange(e.target.value, name)}>
{Array.isArray(options) &&
options.map((option, index) => (
<option value={option} key={option + index}>{option}</option>
))}
</select>
)) ||
false;
Add useState for storing the data from the api.
// initial data from the api
const [data, updateData] = useState(apiData);
// update select options and the list
const updateSelectData = (list, updated) => {
const index = list.findIndex(item => item.name === updated.name);
return [
...list.slice(0, index),
Object.assign({}, list[index], updated),
...list.slice(index + 1)
];
};
getSecondDropdownValue function
const getSecondDropdownValue = function(value, name) {
const updated = data.find(
item => item.dependentField && item.dependentField[0] === name
);
// return new Promise((resolve, reject) => {
if (value === "one") {
// setTimeout(function() {
// resolve(["three", "four"]);
// }, 1000);
updated.options = ["three", "four"];
}
if (value === "two") {
// setTimeout(function() {
// resolve(["five", "six"]);
// }, 1000);
updated.options = ["five", "six"];
}
// });
updateData(updateSelectData(data, updated));
};
Example
// Get a hook function
const {useState} = React;
const apiData = [
{
label: "First",
name: "first",
type: "select",
watch: true,
options: ["", "one", "two"]
},
{
label: "Second",
name: "second",
options: [],
dependentField: ["first"],
type: "select"
}
];
// option component
const Option = ({ options, onChange, name }) =>
(options.length && (
<select onChange={(e) => onChange(e.target.value, name)}>
{Array.isArray(options) &&
options.map((option, index) => (
<option value={option} key={option + index}>{option}</option>
))}
</select>
)) ||
false;
function App() {
const [data, updateData] = useState(apiData);
const updateSelectData = (list, updated) => {
const index = list.findIndex(item => item.name === updated.name);
return [
...list.slice(0, index),
Object.assign({}, list[index], updated),
...list.slice(index + 1)
];
};
const getSecondDropdownValue = function(value, name) {
const updated = data.find(
item => item.dependentField && item.dependentField[0] === name
);
// return new Promise((resolve, reject) => {
if (value === "one") {
// setTimeout(function() {
// resolve(["three", "four"]);
// }, 1000);
updated.options = ["three", "four"];
}
if (value === "two") {
// setTimeout(function() {
// resolve(["five", "six"]);
// }, 1000);
updated.options = ["five", "six"];
}
// });
updateData(updateSelectData(data, updated));
};
return (
<div className="App">
{data.map((options, index) => (
<Option
name={options.name}
key={index}
onChange={options.watch ? getSecondDropdownValue : () => {}}
options={options.options}
/>
))}
</div>
);
}
// Render it
ReactDOM.render(
<App />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Related

Why am I not able to post my data with react?

For now, only apply:0 data are displayed by default in my table.I'm trying to send to my API when you click on Apply button, a list of all ids applied and not applied for instance : data:{apply: [3], notApply:[1,2]}.
Hereapply field in my data means the value is equal to 1 --> not displayed in the table; and notApply - i.e equal to 0 --> displaying data.
So when I click on the toggle button under the show column, it should turns to 1 the statusapply (not displaying the row data in my table which is not the case). The Cancel button sends nothing to my API.
Here what I've tried (but not working):
export default function MenuDisplayApi() {
const { menuId } = useParams();
const { match } = JsonData;
const [selected, setSelected] = useState({});
const [hidden, setHidden] = useState({});
const [menus, setMenus] = useState([]);
const [applyStatus, setApplyStatus]=useState(false)
useEffect(() => {
axios.post(url,{menuId:parseInt(menuId)})
.then(res => {
console.log(res)
setMenus(res.data.menus)
})
.catch(err => {
console.log(err)
})
}, [menuId]);
// If any row is selected, the button should be in the Apply state
// else it should be in the Cancel state
const [data, setData]= useState({
notApply:data.notApply,
apply: data.apply
})
function submit(e){
e.preventDefault()
axios.post(url,{
notApply:data.notApply,
apply: data.apply
})
.then(res => {
console.log(res)
})
}
// useEffect(() => {
// const data = {
// notApply:notApply,
// apply: apply
// }
// console.log('check', data)
// }, [])
// function handle(e){
// const newdata={...data}
// newdata[e.target.id]=e.target.value
// setData(newdata)
// console.log(newdata)
// }
const buttonMode = Object.values(selected).some((isSelected) => isSelected)
? "apply"
: "cancel";
const rowSelectHandler = (id) => (checked) => {
setSelected((selected) => ({
...selected,
[id]: checked
}));
};
const handleClick = () => {
if (buttonMode === "apply") {
// Hide currently selected items
const currentlySelected = {};
Object.entries(selected).forEach(([id, isSelected]) => {
if (isSelected) {
currentlySelected[id] = isSelected;
}
});
setHidden({ ...hidden, ...currentlySelected });
// Clear all selection
const newSelected = {};
Object.keys(selected).forEach((id) => {
newSelected[id] = false;
});
setSelected(newSelected);
} else {
// Select all currently hidden items
const currentlyHidden = {};
Object.entries(hidden).forEach(([id, isHidden]) => {
if (isHidden) {
currentlyHidden[id] = isHidden;
}
});
setSelected({ ...selected, ...currentlyHidden });
// Clear all hidden items
const newHidden = {};
Object.keys(hidden).forEach((id) => {
newHidden[id] = false;
});
setHidden(newHidden);
}
};
const matchData = (
menus.filter(({ _id }) => {
return !hidden[_id];
});
const getRowProps = (row) => {
return {
style: {
backgroundColor: selected[row.values.id] ? "lightgrey" : "white"
}
};
};
const data = [
{
Header: "id",
accessor: (row) => row._id
},
{
Header: "Name",
accessor: (row) => (
<Link to={{ pathname: `/menu/${menuId}/${row._id}` }}>{row.name}</Link>
)
},
{
Header: "Description",
//check current row is in hidden rows or not
accessor: (row) => row.description
},
{
Header: "Dishes",
//check current row is in hidden rows or not
accessor: (row) => row.dishes,
id: "dishes",
Cell: ({ value }) => value && Object.values(value[0]).join(", ")
},
{
Header: "Show",
accessor: (row) => (
<Toggle
value={selected[row._id]}
onChange={rowSelectHandler(row._id)}
/>
)
}
];
const initialState = {
sortBy: [
{ desc: false, id: "id" },
{ desc: false, id: "description" }
],
hiddenColumns: ["dishes", "id"]
};
if (menus.apply === 0) {
setApplyStatus(true)
}
if (menus.apply === 1) {
setApplyStatus(false)
}
return (
<div>
<button type="submit" onClick={handleClick}>
{buttonMode === "cancel" ? "Cancel" : "Apply"}
</button>
<Table
data={matchData && (applyStatus ? true : i.apply !== 1)}
columns={data}
initialState={initialState}
withCellBorder
withRowBorder
withSorting
withPagination
rowProps={getRowProps}
/>
</div>
);
}
Here my json from my api for menuId:1:
[
// ...other menus
{
"_id": 3,
"name": "Cucumber Soup ",
"description": "Cucumber Soup",
"dishes": [
{
"meat": "N/A",
"vegetables": "cucumber"
}
],
"taste": "Medium",
"comments": "2/4",
"price": "Medium",
"availability": 1,
"trust": 1,
"status": "Not started",
"apply": 0
}
]
Please check my codeSandbox. It works fine on local json files, so you can have a better understanding regarding what I'm trying.
Please see this picture for better understanding :

How can I add multiple select option value in react-select

I am trying to create function that let user can select multiple options in react-select.
Here is my following code for onChange function:
const [searchUsers, setSearchUsers] = useState(false);
const [searchTeams, setSearchTeams] = useState(false);
const [searchOrganizations, setSearchOrganizations] = useState(false);
const find = (value) => {
value.forEach((option) => {
if (option.value === "Users" && value.length === 1) {
setSearchUsers(true);
setSearchTeams(false);
setSearchOrganizations(false);
} else if (option.value === "Teams" && value.length === 1) {
setSearchUsers(false);
setSearchTeams(true);
setSearchOrganizations(false);
} else if (option.value === "Organizations" && value.length === 1) {
setSearchUsers(false);
setSearchTeams(false);
setSearchOrganizations(true);
} else {
setSearchUsers(false);
setSearchTeams(false);
setSearchOrganizations(false);
}
});
};
const findList = [
{ value: "Users", label: "Users" },
{ value: "Teams", label: "Teams" },
{ value: "Organizations", label: "Organizations" },
];
const searchData = [];
findList.forEach((item) =>
searchData.push({ label: item.label, value: item.value })
);
and here is my Select tag:
<Select
className="search-select"
styles={customStyles}
instanceId="long-value-select"
closeMenuOnSelect={false}
components={animatedComponents}
defaultValue="Select"
isMulti
onChange={find}
options={searchData}
theme={(theme) => ({
...theme,
colors: {
...theme.colors,
neutral50: "#fff",
},
})}
/>
My onChange function is only getting one option value at a time right now. How can I make it take multiple option value?
If you want to update state from more than one selected option, you must rewrite you're find function:
const find = (value) => {
setSearchUsers(value.some((element) => element.value === "Users"));
setSearchTeams(value.some((element) => element.value === "Teams"));
setSearchOrganizations(value.some((element) => element.value === "Organizations")
);
};
FYI - Array.prototype.some()
The some() method tests whether at least one element in the array
passes the test implemented by the provided function

Check if Contains in Array in React

I wanted to hide menus conditionally based on userAccess. My problem is if has many roles.
How will I be able to do it?
Pls check my code below
MENU
export default [
{
id: 1,
text: 'Leaders',
url: `/leaders`,
userAccess: true,
},
{
id: 2,
text: 'Things',
url: `/things`,
userAccess: 'Super Admin',
},
{
id: 3,
text: 'Users',
url: `/users`,
userAccess: 'Admin',
},
{
id: 4,
text: 'Products',
url: `/products`,
userAccess: ['Admin', 'Manager'],
},
];
CODE
import { useSelector } from 'react-redux';
const checkAccess = (access, role) => {
if (typeof access === 'string') {
return role === access;
};
return access;
};
const Menu = ({ items, isCollapseMenu }) => {
const classes = useStyles();
const role = useSelector((state) => state.role);
return (
<div className={classes.root}>
{items.map(
(item) =>
checkAccess(item.userAccess, role) && (
<MenuItem
key={item.id}
title={item.title}
icon={item.icon}
text={item.text}
shortText={item.shortText}
url={item.url}
submenu={item.submenu}
isCollapseMenu={isCollapseMenu}
/>
)
)}
</div>
);
};
You need to use includes method to check access and also try to filter array before doing map like below:-
import { useSelector } from 'react-redux';
const checkAccess = (access, role) => {
if (typeof access === 'string') {
return role === access;
};
if(Array.isArray(access)){
return access.includes(role);
}
return access;
};
const Menu = ({ items, isCollapseMenu }) => {
const classes = useStyles();
const role = useSelector((state) => state.role);
return (
<div className={classes.root}>
{items.filter(itemToFilter => checkAccess(itemToFilter.userAccess, role)).map(
(item) => (
<MenuItem
key={item.id}
title={item.title}
icon={item.icon}
text={item.text}
shortText={item.shortText}
url={item.url}
submenu={item.submenu}
isCollapseMenu={isCollapseMenu}
/>
)
)}
</div>
);
};
Try to re-write checkAccess function as follows
and make sure your roles are always in an array either it contains single value or multiple values
const checkAccess = (access, role) => {
if (typeof access === 'string') {
return role.indexOf(access) > -1;
};
return access;
};
You have to handle 3 conditions,
String.
Array
boolean
const checkAccess = (access, role) => {
if (typeof access === 'string') { // String check
return role === access;
};
if(Array.isArray(access)){ //Array check
return access.includes(role);
}
return access; // Boolean check
};

How to remove children objects recursively from object?

I am working on solution
I have created basic tree kind of table whenever user click on expand data related to clicked row will appear under it based on row data
I have achieved basic functionality of expand/collapse upto N nested levels.
But i am stuck with only one problem, so basically all row have conditional expand button based on array having multiple values
Lets say it is split array having 3 entries county,city,state
Default loaded data will be fetched from api, now i have to check array that is there any split available! if yes than i make expand button visible
Consider this scenario
const split = ["country","city","state"]
this is Ui will look like
+ Data_1
+ Data_2
on click of button + new data table row will be rendered based on next split available in our case it is country so visual representation will be like
- Data_1
Country_1
Country_2
+ Data_2
Here country does not have expand button as user have not added next split yet, lets add city, and assume user have clicked Country_1 so data will be like
- Data_1
- Country_1
City_1
City_2
+ Country_2
+ Data_2
My solution works fine till this level now lets say user have removed country from split that all nodes of country and city should be removed and - icon of data_1 should be changed to +
Here is my code
import React, {useState, useEffect, useRef, Fragment} from "react";
import _ from "lodash";
import axios from "axios";
class TableRowData extends React.Component {
state = {
showIcon: false,
selection: [],
data: [],
splitOption: ["campid"]
};
constructor(props) {
super(props);
}
componentDidMount() {
const checkIfSplitExistOnMount = (currentSplit) => {
const i = _.findIndex(this.state.splitOption, function(el) {
return el === currentSplit;
});
if (this.state.splitOption[i + 1]) {
return this.state.splitOption[i + 1];
} else {
return null;
}
}
const getReportData = () => {
axios.get("https://jsonplaceholder.typicode.com/users?_start=0&_limit=1").then((res) => {
const rowData = res.data.map((row) => {
row.name = this.state.splitOption[0];
row.isExpanded = false;
row.currentSplit = this.state.splitOption[0];
row.nextSplit = checkIfSplitExistOnMount(this.state.splitOption[0])
row.parentId = 0;
row.isVisble = true;
//console.log(row)
return row;
});
this.setState({
data: rowData
}, () => { //console.log(this.state.data)
});
});
}
getReportData()
}
render() {
// update state function
const updateState = () => {
this.setState({
data: [...this.state.data],
splitOption: [...this.state.splitOption],
selection: [...this.state.selection],
}, () => {})
}
// recusively update parent and child
const recursion = (obj) => {
let row = obj;
row.isExpanded = row.isExpanded;
row.currentSplit = row.currentSplit;
row.nextSplit = checkIfSplitExist(row.currentSplit)
if (row.children && row.children.length > 0) { // check if has children
row.children.forEach(v => { // if has children do the same recursion for every children
recursion(v);
});
}
return row; // return final new object
}
const recursionDel = (obj,split) => {
var row = obj;
row.currentSplit = row.currentSplit;
row.nextSplit = checkIfSplitExist(row.currentSplit)
if (row.children && row.children.length > 0) { // check if has children
row.children.forEach(v => { // if has children do the same recursion for every children
recursionDel(v);
});
}
return row; // return final new object
}
// function to check if next split is there or not if there than return nextsplit
const checkIfSplitExist = (currentSplit) => {
const i = _.findIndex(this.state.splitOption, function(el) {
return el === currentSplit;
});
if(i !== -1) {
if (this.state.splitOption[i + 1]) {
return this.state.splitOption[i + 1];
} else {
return null;
}
}
}
// recursive update whenever split added
const recursiveUpdate = (data) => {
const prevData = [...data];
return prevData.map((row) => {
const updatedData = recursion(row);
return row;
});
}
// function to delete child and parent node recursively
const recursiveDelete = (data,split) => {
const prevData = [...data];
return prevData.map((row) => {
const data = recursionDel(row,split);
return row;
});
}
const addNewSplit = (split) => {
const i = _.findIndex(this.state.splitOption, function(el) {
return el === split;
});
if(i === -1) {
this.setState(
{
splitOption:[...this.state.splitOption,split]
},
()=>{
var rowData = recursiveUpdate(this.state.data)
this.setState({data:rowData})
}
);
} else {
const prevData = [...this.state.splitOption];
var index = prevData.indexOf(split);
prevData.splice(index,1)
if(index!==-1) {
this.setState(
{
splitOption:prevData
},
()=> {
var rowData = recursiveDelete(this.state.data,split)
this.setState({data:rowData})
}
)
}
}
}
// add lazyload expand data
const ExpandableTableRow = ({rows}) => {
const expandRow = (row) => {
row.children = [
{
id: "_" + Math.random().toString(36).substr(2, 5),
name: row.id + "_" + row.nextSplit,
isExpanded: false,
parentId: row.id,
currentSplit: row.nextSplit,
nextSplit: checkIfSplitExist(row.nextSplit),
isVisble:true
}, {
id: "_" + Math.random().toString(36).substr(2, 5),
name: row.id + "_" + row.nextSplit,
isExpanded: false,
parentId: row.id,
currentSplit: row.nextSplit,
nextSplit: checkIfSplitExist(row.nextSplit),
isVisble:true
}
];
row.isExpanded = true;
updateState();
};
// call whenever - click
const collapseRow = (row) => {
delete row.children;
row.isExpanded = false;
updateState();
};
// toggle
const ExpandCollapsToggle = ({row, expandRow, collapseRow}) => {
// display +/- only if nextsplit is not undefined or null
if (row.nextSplit) {
if (row.isExpanded === true) {
return (<button type="button" onClick={() => collapseRow(row)}>
-
</button>);
} else {
return (<button type="button" onClick={() => expandRow(row)}>
+
</button>);
}
} else {
return null;
}
};
if (rows) {
return rows.map((row) => {
// if(!_.isEmpty(row)) {
return (<Fragment key={row.id}>
<tr key={row.id}>
<td>
<ExpandCollapsToggle row={row} expandRow={expandRow} collapseRow={collapseRow}/>{" "}
{row.split}
- {row.id}
</td>
<td>{row.name}</td>
</tr>
<ExpandableTableRow rows={row.children}/>
</Fragment>);
// }
});
} else {
return null;
}
};
const splitData = this.state.splitOption.map((ob) => {
return (<Fragment key={ob}><span>{ob}</span> > </Fragment>)
})
if (this.state.data) {
return (
<Fragment>
{splitData} <br/>
<button onClick = {()=>addNewSplit("name")}>camp name</button>
<button onClick = {()=>addNewSplit("os")}>os</button>
<button onClick = {()=>addNewSplit("country")}>country</button>
<ExpandableTableRow rows={this.state.data} />
</Fragment>
);
} else {
return null;
}
}
}
export default TableRowData;
Also i have create example of codesandbox.io - Link
Here is how you play with ui to replicate scenario
First click on camp name, expand icon will appear
Now expand if you want to, you can see data according split under
Now add one more split OS or Country and you can see expand icon with 2nd level rows
Next step is to remove "Camp Name", Here is issue when camp name is removed, table should be re render according available splits, in our case user's all row should be removed and + icon must be there are we have next split os or country available, i used default split id, it will be there always
import React, { useState, useEffect, useRef, Fragment } from "react";
import axios from "axios";
const test_data = [{
"id":1,
"name":"Leanne Graham",
"username":"Bret",
"email":"Sincere#april.biz",
"address":{
"street":"Kulas Light",
"suite":"Apt. 556",
"city":"Gwenborough",
"zipcode":"92998-3874",
"geo":{
"lat":"-37.3159",
"lng":"81.1496"
}
},
"phone":"1-770-736-8031 x56442",
"website":"hildegard.org",
"company":{
"name":"Romaguera-Crona",
"catchPhrase":"Multi-layered client-server neural-net",
"bs":"harness real-time e-markets"
}
}];
class TableRowData extends React.Component {
constructor(props) {
super(props);
this.state = {
showIcon: false,
selection: [],
data: [],
splitOption: ["campid"]
};
}
// function to check if next split is there or not if there than return nextsplit
checkIfSplitExist = (currentSplit) => {
const i = this.state.splitOption.indexOf(currentSplit);
if (i > -1 && this.state.splitOption[i + 1]) {
return this.state.splitOption[i + 1];
}
return null;
}
getReportData = () => {
// axios.get("https://jsonplaceholder.typicode.com/users?_start=0&_limit=1").then(({data}) => {
this.setState({
data: test_data.map((row) => {
row.name = this.state.splitOption[0];
row.isExpanded = false;
row.currentSplit = this.state.splitOption[0];
row.nextSplit = this.checkIfSplitExist(this.state.splitOption[0])
row.parentId = 0;
row.isVisble = true;
console.log(row)
return row;
})
});
// });
}
componentDidMount() {
this.getReportData()
}
render() {
// update state function
const updateState = () => {
this.setState({
data: [...this.state.data],
splitOption: [...this.state.splitOption],
selection: [...this.state.selection],
}, () => { })
}
const recursionUpdateAndDeleteRow = (parentRow, childRow, split, index = 0) => {
childRow.children && childRow.children.forEach((r) => {
recursionUpdateAndDeleteRow(childRow, r, split, index + 1);
});
if (parentRow && split.indexOf(childRow.currentSplit) == -1) {
delete parentRow.children;
}
childRow.currentSplit = split[index];
childRow.nextSplit = split[index + 1] || null;
if (!childRow.children) {
childRow.isExpanded = false;
}
}
const recursionUpdateAndDeleteRows = (rows, split) => {
const _copy = [...rows];
_copy.forEach((row) => {
recursionUpdateAndDeleteRow(null, row, split);
});
return _copy;
}
const toggleSplit = (split) => {
const index = this.state.splitOption.indexOf(split);
let currentSplitOptions = [...this.state.splitOption];
if (index > -1) {
currentSplitOptions.splice(index, 1)
}
else {
currentSplitOptions.push(split);
}
const _data = recursionUpdateAndDeleteRows(this.state.data, currentSplitOptions);
this.setState({
splitOption: currentSplitOptions,
data: _data
})
}
// add lazyload expand data
const ExpandableTableRow = ({ rows }) => {
const expandRow = (row) => {
row.children = [
{
id: "_" + Math.random().toString(36).substr(2, 5),
name: row.id + "_" + row.nextSplit,
isExpanded: false,
parentId: row.id,
currentSplit: row.nextSplit,
nextSplit: this.checkIfSplitExist(row.nextSplit),
isVisble: true
}, {
id: "_" + Math.random().toString(36).substr(2, 5),
name: row.id + "_" + row.nextSplit,
isExpanded: false,
parentId: row.id,
currentSplit: row.nextSplit,
nextSplit: this.checkIfSplitExist(row.nextSplit),
isVisble: true
}
];
row.isExpanded = true;
updateState();
};
// call whenever - click
const collapseRow = (row) => {
delete row.children;
row.isExpanded = false;
updateState();
};
// toggle
const ExpandCollapsToggle = ({ row }) => {
// display +/- only if nextsplit is not undefined or null
if (row.nextSplit) {
if (row.isExpanded) {
return (
<button type="button" onClick={() => collapseRow(row)}>
-
</button>
);
}
return (
<button type="button" onClick={() => expandRow(row)}>
+
</button>
);
}
return null;
};
if (rows) {
return rows.map((row) => {
return (
<Fragment key={row.id}>
<tr key={row.id}>
<td>
<ExpandCollapsToggle
row={row}
/>
{" "}{row.split} - {row.id}
</td>
<td>{row.name}</td>
</tr>
<ExpandableTableRow rows={row.children} />
</Fragment>
);
});
} else {
return null;
}
};
if (this.state.data) {
return (
<Fragment>
{this.state.splitOption.join(', ')} <br />
<button onClick={() => toggleSplit("name")}>
camp name
</button>
<button onClick={() => toggleSplit("os")}>os</button>
<button onClick={() => toggleSplit("country")}>country</button>
<br />
<ExpandableTableRow rows={this.state.data} />
</Fragment>
);
} else {
return null;
}
}
}
export default function App() {
return (
<div>
<TableRowData />
</div>
);
}
Here working example

Cannot change redux state with a action in a component

Action on button does not perform the action to change the sorting object which sort all itens in a list (another component). I expect the button to perform this changes passing the sortBy variable on this.props.dispatch(orderBy(sortBy)) or another dynamic way without a button.
import React from 'react';
import { connect } from 'react-redux';
import orderBy from '../actions/sorting';
const TYPES = [
{ slug: "title", description: "Title" },
{ slug: "author", description: "Author" },
{ slug: "editionYear", description: "Edition Year" }
];
class BookListSorter extends React.Component {
state = {
sortBy: [{ title: "asc" }]
};
// Helper methods
getSortByKeyForIndex = index =>
Object.keys(this.state.sortBy[index] || {})[0];
getSortByValueForIndex = index =>
Object.values(this.state.sortBy[index] || {})[0];
changeSort = (key, index) => e => {
// This is what is called when an select option changes
const { target } = e; // Save target from event to use in the callback
this.setState(({ sortBy }) => {
// Use a function for atomicness - this prevents state from being out of sync
// Get the type from the event object if the onchange handler is for the type,
// otherwise get from sortBy object
const type =
key === "type" ? target.value : this.getSortByKeyForIndex(index);
// Get the direction from the event object if the onchange handler is for the direction,
// otherwise get from sortBy object
const direction =
key === "direction" ? target.value : this.getSortByValueForIndex(index);
// If both options are set, replace the indexed spot in the sortby object
// Return updated state.
return type || direction
? sortBy.splice(index, 1, { [type]: direction })
: sortBy.splice(index, 1);
});
};
filterTypes = index => ({ slug }) => {
// Filter out already used keys from previous rows
const sortByKeys = this.state.sortBy
.slice(0, index)
.reduce((keys, sortObj) => keys.concat(Object.keys(sortObj)[0]), []);
return !sortByKeys.includes(slug);
};
render() {
const { sortBy } = this.state;
const lastIndex = sortBy.length - 1;
// Only add a new row if the one above it is completely filled out
const shouldAddNewRow =
this.getSortByKeyForIndex(lastIndex) &&
this.getSortByValueForIndex(lastIndex);
const rowCount = shouldAddNewRow ? sortBy.length + 1 : sortBy.length;
return (
<div>
<h1>Choose sort order</h1>
{Array.from(Array(Math.min(rowCount, TYPES.length))).map(
(dummy, index) => (
<div>
<span>Row {index}: </span>
<select
defaultValue={this.getSortByKeyForIndex(index)}
onChange={this.changeSort("type", index)}
>
<option value="">None</option>
{TYPES.filter(this.filterTypes(index)).map(
({ slug, description }) => (
<option value={slug}>{description}</option>
)
)}
</select>
<select
defaultValue={this.getSortByValueForIndex(index)}
onChange={this.changeSort("direction", index)}
>
<option value="">None</option>
<option value="asc">Ascending</option>
<option value="desc">Descending</option>
</select>
<br />
</div>
)
)}
<br />
<button onClick={() => this.props.dispatch(orderBy(sortBy))}>sort</button>
</div>
);
}
};
const mapStateToProps = (state) => {
return {
sorting: state.sorting
};
};
//ACTIONS
//ADD BOOK
const addBook = ({ title = '', author = '', editionYear = 0} = {}) => ({
type: 'ADD_BOOK',
book: {
title,
author,
editionYear
}
});
//SORT BY
const orderBy = (order) => ({
type: 'SORT_BY',
orderBy: order
});
//book reducer
const bookReducerDefaultState = [];
const bookReducer = (state = bookReducerDefaultState, action) => {
switch(action.type) {
case 'ADD_BOOK':
return [
...state,
action.book
];
default:
return state;
};
};
//sorting reducer
const sortingReducerDefaultState = {
orderBy: [{title: 'asc'},{author: 'asc'}]
};
const sortingReducer = (state = sortingReducerDefaultState, action) => {
switch(action.type) {
case 'SORT_BY':
return {
...state,
orderBy: action.orderBy
};
default:
return state;
};
}
function compareBy(a, b, orderBy) {
const key = Object.keys(orderBy)[0],
o = orderBy[key],
valueA = a[key],
valueB = b[key];
if (!(valueA || valueB)) {
console.error("the objects from the data passed does not have the key '" + key + "' passed on sort!");
return 0;
}
if (+valueA === +valueA) {
return o.toLowerCase() === 'desc' ? valueB - valueA : valueA - valueB;
} else {
if (valueA.localeCompare(valueB) > 0) {
return o.toLowerCase() === 'desc' ? -1 : 1;
} else if (valueA.localeCompare(valueB) < 0) {
return o.toLowerCase() === 'desc' ? 1 : -1;
}
}
return 0
}
function getSortedBooks(books, orderBy) {
orderBy = Array.isArray(orderBy) ? orderBy : [orderBy];
return books.sort((a, b) => {
let result
for (let i = 0; i < orderBy.length; i++) {
result = compareBy(a, b, orderBy[i])
if (result !== 0) {
return result
}
}
return result
})
}
//store creation
const store = createStore(
combineReducers({
books: bookReducer,
sorting: sortingReducer
})
);
store.subscribe(() => {
const state = store.getState();
const sortedBooks = getSortedBooks(state.books, state.sorting.orderBy)
console.log(sortedBooks);
});
export default connect(mapStateToProps)(BookListSorter);
Can anyone help with this issue. Since the button i set up is not working?
Note: This was an answer to the original question
The best way to get the value of a select element in React is to add an onChangehandler.
In your example, it might look something like this:
<select onChange={(event) => this.setState({ firstType: event.target.value })}>
<option value="title">Title</option>
<option value="author">Author</option>
<option value="editionYear">Edition Year</option>
</select>
<select onChange={(event) => this.setState({ firstDirection: event.target.value })}>
<option value="asc">Ascending</option>
<option value="desc">Descending</option>
</select>
By changing the above select inputs, the state would look like this:
{
firstType: 'author',
firstDirection: 'desc'
}
(The state wont automatically be set until changes are made, so you would have to initialize separately.)
You would then need to transform that object into the shape you need.
This is just an example, I'll leave it up to you to transform the state into the shape that you need and to connect up redux since it looks like that's what you intend to do with the import of connect.
Note: if the option tags don't have a value attribute set, the value in event.target.value would be the content inside the tags.

Categories