I'm using React to build a form and I'm trying to filter a list with the SearchInput (which works the same as TextInput) located in the child component Header. But everytime I type a character the SearchInput gets unfocused
function index() {
const list = [//data\\]
const [search, setSearch] = useState("");
const [filteredResults, setFilteredResults] = useState([]);
const searchItems = (searchValue) => {
setSearch(searchValue);
if (search !== "") {
const filteredData = partners.filter((item) => {
return Object.values(item)
.join("")
.toLowerCase()
.includes(search.toLowerCase());
});
setFilteredResults(filteredData);
} else {
setFilteredResults(partners);
}
};
const Header = () => (
<Box>
<SearchInput
placeholder="Search"
value={search}
onChange={(e) => searchItems(e.target.value)}
/>
</Box>
);
return (
<Parent
headerContent={<Header />}
>
<Box>
<Table data={search.length > 1 ? filteredResults : list} />
</Box>
</Parent>
);
}
export default index;
Oh, I think I can see the problem now - it's the way you're rendering the <SearchInput /> component. You're inadvertantly creating a new functional component on every render. Either inline the Header directly into the Parent control's headerContent property, or create an entirely separate component:
const Header = ({ search, onSearchChange }) => {
const handleChange = (e) => onSearchChange(e.target.value);
return (
<Box>
<SearchInput
placeholder="Search"
value={search}
onChange={handleChange}
/>
</Box>
);
}
function index() {
// ----- 8< -----
return (
<Parent
headerContent={<Header search={search} onSearchChange={searchItems} />}
>
{/* ... */}
</Parent>
);
}
While you're there, you have a subtle bug with your comparison - it looks like you're searching your partners effectively as a list of strings; but, since you're joining them, if you had partners with the names:
'one'
'two'
You're creating a search string as 'onetwo' - so searching for 'et' would match, even though you don't actually have a partner matching that. You can fix that by just checking each partner individually... something like:
const searchItems = (searchValue) => {
setSearch(searchValue);
if (search !== "") {
const searchValueLower = searchValue.toLowerCase();
const filteredData = partners.filter((item) => {
return Object.values(item)
.some(item => item.toLowerCase().includes(searchValueLower);
});
setFilteredResults(filteredData);
} else {
setFilteredResults(partners);
}
};
Related
I want to use an MUI stepper to replace a Select component. The select component is used to indicate the status of the document the user is working in (New, In Progress, Complete, etc.). I have managed to display the correct status in the stepper, but I cannot interact with it to move the status forward or back.
This is my stepper file. I am passing the status value through props:
export default function IntakeStatusBar(props) {
const { status } = props;
const classes = useStyles();
const [activeStep, setActiveStep] = useState(0);
const steps = ["New", "In Progress", "Completed"];
useEffect(() => {
if (status === "In Progress") {
setActiveStep(1);
} else if (status === "Completed") {
setActiveStep(2);
} else setActiveStep(0);
}, [status, activeStep]);
const handleStep = (step) => () => {
setActiveStep(step);
};
return (
<div className={classes.root}>
<Stepper activeStep={activeStep} alternativeLabel>
{steps.map((label, index) => (
<Step key={label}>
<StepButton onClick={handleStep(index)}>{label}</StepButton>
</Step>
))}
</Stepper>
</div>
);
}
This is where I call and display the stepper:
export default function IntakeDetails() {
const [details, setDetails] = useState("");
const onTextChange = (e) => {
var id = e.target.id ? e?.target.id : e?.target.name;
var value = e.target.value;
setDetails({ ...details, [id]: value });
}
....
return (
<IntakeStatusBar status={details?.Status} onChange={onTextChange} />
// This is the Select drop down menu I have been using
<TextField
label="Status"
error={requiredField && details?.Status?.length <= 0}
value={details?.Status}
disabled={!(adminRole && isSolutionsTab && details?.Status !== "In Plan")}
select
onChange={onTextChange}
>
{details.StatusList?.map((choice) => {
return (
<MenuItem key={choice} value={choice}>
{choice}
</MenuItem>
);
})}
</TextField>
)
}
This is what the status field looks like in JSON:
{
Status: "New"
}
besides changing this:
<StepButton onClick={() => handleStep(index)}>{label}</StepButton>
you have to change this:
const handleStep = (step) => {
setActiveStep(step);
};
and set Stepper to nonLinear if you want user to click on steps:
<Stepper nonLinear activeStep={activeStep} alternativeLabel>
I also commented out useEffect since I had no idea what its purpose is and it's messing with activeStep state.
I want to use search filter in sidebar so i created search filter and implemented but its now working shown only input field. and i stuck with how to map the items to search filter.
here i attached some of my working code:
state = {
search : ""
}
onchange = e =>{
this.setState({search : e.target.val })
}
const Menu = ({ resources, onMenuTap, translate }) => {
const {search}=this.state;
if (search !== "" && resources.name.toLowerCase().indexof(search.toLowerCase()) === -1 ){
return null
}
onchange = e =>{
this.setState({search : e.target.val })
}
return (
<Card className={classes.sidebarCard}>
{/* Search */}
<input placeholder="Search" onChange={this.onchange} />
//
....
//
);
};
for your onchange, try to use that :
onchange = e =>{
this.setState({search : e.target.value })
}
and in your search input :
{/* Search */}
<input placeholder="Search" value={this.state.search} onChange={(e) => this.onchange(e)} />
Because you're using functional component. You need to use useState hook:
const [search, setState] = useState("");
if (
search !== "" &&
resources.name.toLowerCase().indexof(search.toLowerCase()) === -1
) {
return null;
}
onchange = (e) => {
setState(e.target.val);
};
The attached sanbox code is based on your code. But it seems it doesn't render:
You are using functional component. So you need to change your code in this way:
const Menu = ({ resources, onMenuTap, translate }) => {
const [search, setSearch] = useState(""); //<-- this is your state
const onchange = e =>{
setSearch(e.target.val); //set the state
}
if (search !== "" && resources.name.toLowerCase().indexof(search.toLowerCase()) === -1 ){
return null
}
return (
<Card className={classes.sidebarCard}>
{/* Search */}
<input placeholder="Search" onChange={onchange} value={search} /> //<-- set value equal to search state
{permissions === "Administrator" && (
<>
<MenuItemLink
className={classes.MenuItemLink}
activeClassName={classes.active}
to="/_apps"
primaryText={translate("easyadmin.apps")}
leftIcon={
<AppsOutlined fontSize="small" className={classes.iconColor} />
}
onClick={onMenuTap}
/>
<MenuItemLink
className={classes.MenuItemLink}
activeClassName={classes.active}
to="/_entitys"
primaryText={translate("easyadmin.customobjects")}
leftIcon={
<SettingsOutlinedIcon
fontSize="small"
className={classes.iconColor}
/>
}
onClick={onMenuTap}
/>
)}
);
};
I have 2 details tag, each has a control to toggle it on/off. Code snippet here. Clicking Control A should toggle on/off page A, clicking Control B should toggle on/off page B.
I did it with an if else if plus 2 useState, this would not be feasible when there are multiple details. How can I refactor the code such that maybe the if else if can be avoided and it detects which Control I click in a cleverer way?
Page.js
const Page = ({ name, isOpen, setIsOpen }) => {
return (
<>
<details
open={isOpen}
onToggle={(e) => {
setIsOpen(e.target.open);
}}
>
<summary>Page {name} title</summary>
<div>Page {name} contents</div>
</details>
</>
);
};
export default Page;
Control.js
const Control = ({ toggle }) => {
return (
<>
<a onClick={() => toggle("A")} href="#/">
Control A
</a>
<br />
<a onClick={() => toggle("B")} href="#/">
Control B
</a>
</>
);
};
App.js
export default function App() {
const [isOpenA, setIsOpenA] = useState(false);
const [isOpenB, setIsOpenB] = useState(false);
const toggle = (name) => {
if (name === "A") {
setIsOpenA((prevState) => !prevState);
} else if (name === "B") {
setIsOpenB((prevState) => !prevState);
}
};
return (
<div className="App">
<Control toggle={toggle} />
<Page name={"A"} isOpen={isOpenA} setIsOpen={setIsOpenA} />
<Page name={"B"} isOpen={isOpenB} setIsOpen={setIsOpenB} />
</div>
);
}
You can use an array to represent open ones
const [openPages, setOpenPages] = useState([])
And to toggle filter the array
const toggle = (name) => {
if(openPages.includes(name)){
setOpenPages(openPages.filter(o=>o!=name))
}else{
setOpenPages(pages=>{ return [...pages,name]}
}
}
I would personally use an object as a map for your toggles as in something like:
const [isOpen, _setIsOpen] = useState({});
const setIsOpen = (pageName,value) => _setIsOpen({
...isOpen,
[pageName]: value
});
const toggle = (name) => setIsOpen(name, !isOpen[name]);
and then in the template part:
<Page name={"A"} isOpen={isOpen["A"]} setIsOpen={toggle("A")} />
In this way you can have as many toggles you want and use them in any way you want
I think this would be quite cleaner, also you should put the various page names in an array and iterate over them as in
const pageNames = ["A","B"];
{
pageNames.map( name =>
<Page name={name} isOpen={isOpen[name]} setIsOpen={toggle(name)} />)
}
At least that's how I would go about it
Adithya's answer worked for me.
For future reference, I put the full working code here. The onToggle attribute in Page.js is not needed. All required is passing correct true/false to open={isOpen} in Page.js.
App.js:
export default function App() {
const [openPages, setOpenPages] = useState([]);
const toggle = (name) => {
if (openPages.includes(name)) {
setOpenPages(openPages.filter((o) => o !== name));
} else {
setOpenPages((pages) => {
return [...pages, name];
});
}
};
return (
<div className="App">
<Control toggle={toggle} />
<Page name={"A"} isOpen={openPages.includes("A")} />
<Page name={"B"} isOpen={openPages.includes("B")} />
<Page name={"C"} isOpen={openPages.includes("C")} />
</div>
);
}
Page.js
const Page = ({ name, isOpen }) => {
return (
<>
<details open={isOpen}>
<summary>Page {name} title</summary>
<div>Page {name} contents</div>
</details>
</>
);
};
Control.js remains the same.
I have created a list using material UI and reactjs, and when a new element is added to the list the new element will go on top of the list.
I have a requirement where when I click on an element on the list the element should be strike-through and that now that element should be listed on the bottom of the list.
I was able to strike-through the element when clicked, but I am confused as to how to bring the element to the bottom of the list
How should I approach this problem?
The code of the listlayout.js is presented here, In this code, the added items are listed, and I need to find the way to change the list order when an element is stricked
app.js
class App extends Component {
constructor(props) {
super(props);
this.state={
items:[],
newItem:{
id:'',
itemText:''
},
updateItem:false
};
this.handleInput = this.handleInput.bind(this);
this.addItem = this.addItem.bind(this);
}
handleInput = e =>{
this.setState({
newItem:{
id:1 + Math.random(),
itemText: e.target.value
}
});
};
addItem = e =>{
e.preventDefault();
const typedItem = this.state.newItem;
if(typedItem.itemText !==""){
const typedItems=[...this.state.items,typedItem];
this.setState({
items:typedItems,
newItem:{
id:'',
itemText: ''
},
updateItem:false
})
}
};
render() {
return (
<div >
<HeaderBar/>
<ListLayout items={this.state.items}
/>
</div>
);
}
}
export default App;
ListLayout.js
const ToDoList = props => {
const clearList = props.clearList;
const deleteItem = props.deleteItem;
const updateItem = props.updateItem;
const strikeList = props.strikeList;
const listItems = item => {
return <div key={item.id}>{item.itemText}</div>;
};
const completed = id => {
document.getElementById(id).style.textDecoration = "line-through";
return true;
};
const strikeTextMethod = id => {
completed(id);
};
return (
<div>
<Grid container justify="center" alignContent="center">
<Grid item xs={12} md={6}>
<Typography variant="h6" className={classes.title}>
To do List
</Typography>
<div className={classes.demo}>
<List dense={dense}>
{items
.slice(0)
.reverse()
.map(x => (
<ListItem
key={x.id}
button
id={x.id}
onClick={() => strikeTextMethod(x.id)}
divider
>
<ListItemText primary={listItems(x)} />
<ListItemSecondaryAction></ListItemSecondaryAction>
</ListItem>
))}
</List>
</div>
</Grid>
</Grid>
<br />
</div>
);
};
export default ToDoList;
You have to mainatain the strike event for each item in the array. You can add an additional property to the array items, like isStriked or status.. something like that.
Then you can sort them accordingly..
Your code doesn't seem to be the entire solution. I don't see the definition of items as an example.
but something like this could be a workaround.
const ToDoList = props => {
const [items, setItems] = React.useState(props.items || []); // Initial values
// Maybe you need to these lines to sync the items state.
React.useEffect(
() => {
setItems(items)
},
[props.items]
)
const completed = id => {
document.getElementById(id).style.textDecoration = "line-through";
return true;
};
const strikeTextMethod = id => {
const index = items.findIndex(x => x.id === id);
const newItems = [items[index], ...items.slice(0, index - 1), ...items.slice(index + 1)]
setItems(newItems);
completed(id);
};
return (
)
}
I have the following functions which renders a set of fields:
const renderFields = (data: CustomerDetails) => {
return Object.keys(data).map((s: string) => {
const key = s as keyof CustomerDetails
return Object.keys(data[key]).map(fieldKey => {
const name = `${key}.${fieldKey}`
const id = `customer-details-form-${fieldKey}`
return (
<FormItem key={name}>
<Label htmlFor={id}>{camelCaseToTitleCase(fieldKey)}</Label>
<Field name={`${key}.${fieldKey}.value`} validate={validate(fieldKey)}>
{props =>
<TextField
disabled={
data.contact[fieldKey] !== undefined
? data.contact[fieldKey].disabled
: true
}
// disabled={
// data.contact[fieldKey]?.disabled ?? true
// }
{...props}
data-bdd={`customer_details_field_${fieldKey}`}
id={id}
/>
}
</Field>
</FormItem>
)
})
})
}
however the disabled status is dependent on the redux structure at the moment. Is there a way to make fields disabled/enabled on click without having to dispatch an action saying which fields should be enabled or not?
Using local state here would be appropriate. Here's a simplified example:
function WrappedField(props){
const [disabled, setDisabled] = useState(false);
return <Field {...props} disabled={disabled} onClick={() => setDisabled(!disabled)} />
}
You can use the wrapped version wherever you would have used the Field component.