How to add function to "X" icon in Material UI Searchbar? - javascript

I have the following code:
import SearchBar from "material-ui-search-bar";
const data = [
{
name: "Jane"
},
{
name: "Mark"
},
{
name: "Jason"
}
];
export default function App() {
const [results, setResults] = useState();
const filteredResults = data.filter((item) => {
return Object.keys(item)?.some((key) => {
return item[key].includes(results?.toLowerCase());
});
});
return (
<div className="App">
<SearchBar
value={results}
onChange={(value) => setResults(value)}
placeholder="Please enter name..."
/>
{filteredResults.map((item) => {
return <li>{item.name}</li>;
})}
</div>
);
}
codesandbox
when I delete name from search bar using delete keyboard all names from data are displayed below the search bar, but if I click X button, it clears the search bar but doesn't display all names. Is there a way to add a function to this X button so when I click it, it acts the same way as delete keyboard?

You can pass a function to the onCancelSearch prop to reset the results state variable.
<SearchBar
value={results}
onChange={(value) => setResults(value)}
onCancelSearch={() => setResults('')}
/>
Suggestions
It's better to initialize results with an empty string. You can now remove the ? in results?.toLowerCase() since results will never be nullish (undefined or null).
const [results, setResults] = useState('')
You should pass the key prop to the li element. You can add an id property to the items array to use as the key or use the item index.
{
filteredResults.map((item) => (
<li key={item.id}>{item.name}</li>
))
}
And there are a couple of issues with the filtering logic.
You're converting the search query to lowercase but not the name. In your example, if you search for 'ja', nothing would show up even though matches exist (Jane and Jason).
filteredResults will throw an error if any of the object values do not have the includes method (You can reproduce the issue by adding a numeric id field to the array items). You could fix it by using a searchableKeys array to only perform the search in specific fields.
const searchableKeys = ['name']
const filteredResults = data.filter((item) =>
searchableKeys.some((key) =>
item[key].toLowerCase().includes(results.toLowerCase())
)
)
I would recommend renaming results to query or searchQuery for clarity.

Hello upon checking your problem, the reason why its remapping the list on delete key (in keyboard) because it triggers the onChange event of the searchBar to have the same event as the searchBar, i've tried it on my end and it seems that this solution can be solve your issue
<SearchBar
value={results}
onChange={(value) => setResults(value)}
placeholder="Please enter name..."
closeIcon={<button onClick={() => setResults("")}>clear</button>}
/>
the closeIcon props - overrides the close icon and its methods..
here is the documentation that i check material-ui-search-bar
here also the replicated/solved code-sandbox

Related

react-export-excel how to hide column in excel when exported?

Hi I hope the title is enough to understand my problem. There is no error when exporting data, I just want that if the checkbox is unchecked or false it will hide the excel column of excel when exported. And if the checkbox is checked or true it will display or show the excel column when the excel is exported.
const [filteredData, setfilteredData] = useState(false)
const [checked, setChecked] = React.useState(false);
const handleDisplay = (event) => {
console.log(event.target.checked)
setChecked(event.target.checked);
}
....,
<Checkbox checked={checked} onChange={handleDisplay} /> Display Name<br></br>
<ExcelExported exportClass={'admin-add-btn'} filteredData={filteredData} checked={checked}></ExcelExported>
ExcelExported.js
import React from 'react'
import ReactExport from 'react-export-excel'
import { Button } from '#material-ui/core'
const ExcelFile = ReactExport.ExcelFile
const ExcelSheet = ReactExport.ExcelFile.ExcelSheet;
const ExcelColumn = ReactExport.ExcelFile.ExcelColumn;
function ExcelExported(props) {
const { exportClass, filteredData, checked } = props
console.log(checked)
const fileNameFormatted = 'Student Information'
return (
<div>
<ExcelFile filename="Student Information" element={
<Button style={{ textTransform: 'none', minWidth: '240px' }}
className={exportClass}
variant='contained'
// onClick={handlePrint}
>
Export
</Button>
}>
<ExcelSheet data={filteredData} name="Users" >
<ExcelColumn label="Student Name" value="name" hide={checked}/>
</ExcelSheet>
</ExcelFile>
</div >
)
}
export default ExcelExported
this is my https://www.npmjs.com/package/react-export-excel library for exporting excel sheet
what i've tried so far is putting hide={checked} there is no error but even the checkbox is unchecked, it display the column, and i also tried this
{checked ? (<ExcelColumn label="Student Name" value="name")/>:(null)}
and i received this error
It's because you pass in null as a child component of ExcelFile.
Let's locate the (approximate) line the stacktrace is showing in react-export-excel's source. It loops over all children of that component and will always try to access the props property of them.
// src/ExcelPlugin/components/ExcelFile.js
const columns = sheet.props.children;
const sheetData = [
React.Children.map(columns, column => column.props.label)
];
So this component will only ever work (i.e. not crash) if all of its children are objects with this expected structure.
You'll get the same error with checked && <ExcelColumn/>, where it receives false as a child. In a regular React component both null and false are "ignored" (they don't have output). But because this component loops over it with React.children, it just gets the exact list you put into it.
Further down the code it looks like it also has no way to prevent a column from being included. You can try passing in other things, but it's likely still going to be included as an extra comma separate column.
Perhaps this could be facilitated by this library by ignoring these values too. It could get tricky, though. As the amount of columns always needs to match the data. So it's understandable that this component does not provide this level of flexibility at the moment.
Alternative
Instead of using <ExcelColumn/> components, you can define the columns in the dataSet object. An example from their documentation:
const multiDataSet = [
{
columns: [
{ value: "Name", widthPx: 50 }, // width in pixels
{ value: "Salary", widthCh: 20 }, // width in charachters
],
data: [
// ...
],
},
// ...
];
// ...
<ExcelSheet dataSet={multiDataSet} name="Organization"/>
This way you have direct control over the columns list and can filter it depending on certain hook values.
You'll also have to guarantee the rows have the same filters applied.
Here's a quick example implementation showing how to manage the state of the hidden rows. This could be done completely different depending on your use case / existing state management solution.
const allColumns = [{value: "columnA"}, {value: "columnB"} /* ... */];
const [hiddenColumns, setHiddenColumns] = useState({});
const isVisible = col => !hiddenColumns[col.name];
const toggleColumn = col => setHiddenColumns({
...hiddenColumns,
[col.name]: !hiddenColumns[col.name],
})
// Get indexes of hidden columns to hide them in data too.
const hiddenIndexes = Object.entries(hiddenColumns).filter(([,hidden]) => hidden).map(
([hiddenCol]) => allColumns.findIndex(column => column.value === hiddenCol)
)
const dataSet = {
// ...
columns: allColumns.filter(isVisible);
data: rows.map(
cells => cells.filter(
(cell, index) => !hiddenIndexes.includes(index)
)
),
}
return <Fragment>
<ExcelFile><ExcelSheet {...{dataSet}}/></ExcelFile>
{allColumns.map(
col =>
<Checkbox
checked={!isVisible(col)}
onChange={()=> toggleColumn(col.name)}
>
col.value
</Checkbox>
)}
</Fragment>
Hope this will work
{checked && <ExcelColumn label="Student Name" value="name"/>}

Adding, deleting, and editing input values dynamically with Reactjs

I have a UserLists component where the user types into an input and adds the value onto the bottom of the screen.
The input value is added into the whitelist state. The state is then mapped and creates more inputs where the user can decide to delete said input or edit it.
I am having trouble deleting the inputs. I thought I could delete each input individually by splicing the state, but my implementation of the deleteItem deletes multiple inputs when any single one of them is clicked.
I also cannot edit any of the inputs because their value is set by my addItem function.
import React, { useEffect, useState } from "react";
export const UserLists = () => {
const [whitelist, setWhiteList] = useState([]);
const addItem = () => {
let newValue = document.getElementById("whiteList").value;
setWhiteList([...whitelist, newValue]);
};
useEffect(() => {
console.log(whitelist, "item changed");
}, [whitelist]);
const deleteItem = (index) => {
let test = whitelist.splice(index, 1);
setWhiteList(test);
console.log("index:", index);
};
const editItem = () => {};
return (
<div>
<h2>WhiteList</h2>
<input id="whiteList" type="text" />
<button onClick={addItem}>Add</button>
{whitelist.map((item, index) => {
return (
<div>
<input type="text" value={item} onChange={editItem} />
<button onClick={() => deleteItem(index)}>Delete</button>
<p>{index}</p>
</div>
);
})}
</div>
);
};
How can I revise my code to successfully individually delete and edit inputs?
My codesandbox
You need to change your editItem and deleteItem functions in order to make your edit and delete functionality work properly. Here's a code sandbox link to the solution to your problem:
https://codesandbox.io/s/whitelist-forked-y51w8
Don't do:
let test = whitelist.slice(index, 1);
setWhiteList(test);
Do this instead:
whitelist.splice(index, 1);
setWhiteList([...whitelist]);

react-select dynamic dropdown with async options loaded as user is typing

I'm new to React and I'm trying to merge 2 different features. A dynamic form where you can add and/or remove inputs AND one with async react-select where you can start typing a word and options appear and get filtered based on an API source (based on connected user for example)
I'm almost done (I think) BUT :
When I start typing I correctly see my options...and options get filtered correctly BUT when I click on an item (to select this item) I get an error.
The error I got is Cannot read property 'name' of undefined but I don't understand it and I'm not sure it's the only problem I got. I have no clue how to get my choice to cprrectly get selected and correctly put into my array of objects (inputFields)
Here are the 2 different sources I try to mix (They both work perfectly put independantly)
React-Select Async dropdown : https://stackblitz.com/edit/react-select-async-component?file=index.js
Dynamic form field : https://www.youtube.com/watch?v=zgKH12s_95A
Thank you for helping me understand what's the problem !!!
Here is my code :
function AsyncDynamicForm(props) {
const [inputFields, setInputFields] = useState([
{ firstName: '' },
]);
const [inputValue, setValue] = useState("");
const handleChangeInput = (index, event) => {
const values = [...inputFields];
values[index][event.target.name] = event.target.value;
setInputFields(values);
};
const AddFields = () => {
setInputFields([...inputFields, { firstName: '' }]);
};
const RemoveFields = (index) => {
const values = [...inputFields];
values.splice(index, 1);
setInputFields(values);
};
const loadOptions = (inputValue) => {
return fetch(
`http://127.0.0.1:8000/api/Objects/?q=${inputValue}`
).then((res) => res.json());
};
const handleInputChange = (value) => {
setValue(value)
};
const handleSubmit = (e) => {
e.preventDefault();
console.log("inputFields", inputFields); // Nothing for now
};
return (
<div>
<Container>
<Form onSubmit={handleSubmit}>
{inputFields.map((inputField, index) => (
<div key={index}>
<Form.Field inline>
<AsyncSelect
name="firstName"
value={inputField.firstName}
onChange={(event) => handleChangeInput(index, event)}
cacheOptions
defaultOptions
getOptionLabel={(e) => e.name.toString()}
getOptionValue={(e) => e.id}
loadOptions={loadOptions}
onInputChange={handleInputChange}
/>
</Form.Field>
<Button.Group basic size="small">
<Button icon="add" onClick={() => AddFields()} />
<Button
icon="x"
onClick={() => RemoveFields(index)}
/>
</Button.Group>
</div>
))}
<Button type="submit" onClick={handleSubmit}>
click
</Button>
</Form>
</Container>
</div>
);
}
export default AsyncDynamicForm
The documentation is very helpful here. The onChange prop takes a method with a specific signature.
const onChange = (option, {action}) => {
/* The `option` value will be different, based on the Select type
* and the action, being one of `option`, an array of `option`s
* (in the instance of a multiselect), `null` (typical when clearing
* an option), or `undefined`.
* What you actually get will depend on the `action` the select passes,
* being one of:
* - 'select-option'
* - 'deselect-option'
* - 'remove-value'
* - 'pop-value'
* - 'set-value'
* - 'clear'
* - 'create-option'
*/
// example uses the `useState` hook defined earlier
switch (action) {
case 'select-option',
case 'remove-value',
case 'clear':
setColor(option);
break;
default:
// I'm not worried about other actions right now
break;
}
};
Remember that React-Select treats value as the entire option object, not just the option value you define in getOptionValue. If you're looking at setting a true form 'value', you'll probably wrap Select in some way to handle that.
React-Select is incredibly powerful, and incredibly complex. The documentation is your friend here. I find it helpful to play around in CodeSandbox, when trying out features I don't fully understand yet.

how to update FieldArray elements with redux store variable

I am using redux-form with a FieldArray.By default 1 element will be there in array and it is populated from JSON. I can add upto 3
elements in FieldArray component.
In below code, 'elementList'
property is coming from JSON and also I have store variables named
as'elements' and 'elementList'. I initialize these store variable with elementList
from JSON at first and then keep updating store variables when 'Add
Element' is clicked on. I can see store variables are updating
properly but on screen Field array elements are not updating.It may be because name property 'elementList' in FieldArray may refer to
JSON element.
Is it possible, if I can refer to store variables 'elementList' or 'elements' in name property
of 'FieldArray'. Please advice.
Main page
<div>
<FieldArray name="elementList" component={Elements}
props={this.props}/>
<button type="button" className="btn btn-primary"
onClick={event => this.addElement(elementDTO)}>Add Element
</button>
<br/>
</div>
addElement(elementDTO){
if(this.props.elements && this.props.elements!=undefined && this.props.elements.length >= 3){
return;
}
this.props.addReqElement(this.props.elements);
}
Field Array page
const elements= ({props, meta: {error, submitFailed}}) => {
const {fields} = props;
return (
{fields.map((element, index) => (
<div>
//Field definitions
</div>
))}
Thank you
Update:
Adding method from Redux Action and Reducer
export function addReqElement(childList) {
let state = store.getState()
let newChild=
state.requestReducer.DTOobj.requestDoc; //this is an empty element coming from backend with few properties and adding rest of the //proerties as below to create a new child
newChild.prop1 = null
newChild.prop2 = null
childList.push(newChild)
return (dispatch) => {
dispatch(setDoc(childList));
}
}
export function setDoc(payload) {
return {
type: ADD_DOC,
payload: payload
}
}
Update 2: I tried to remove push and used spread operator , but it did not work. I have inner array also, that is working as I am using different strategy. I take pull parent array ,it's index and update parent array with the new inner array. It works but parent array I am not getting how should I make it work. I tried to set the main array to the form props and render full page by dispatching an action but it did not work. Any suggestions plz?
From the main array page:
render() {
const {fields, parentArrayFromStore} = this.props;
return (
<div className="col-sm-12">
{fields.map((doc, index) => (
<div key={index}>
<div className="col-sm-12">
<FieldArray name={`${doc}.innerArrayList`} component={CustomInnerArrayComponent}/>
</div>
<div className="row">
<div className="col-sm-4">
<button type="button" className="btn btn-primary"
onClick={event => this.addInnerArray(index, parentArrayFromStore ,fields.get(index).innerArrayList)}>Add Printer
</button>
</div>
</div>
</div>
))}
</div>)
}
In Action class
export function addInnerArray(index, parentArrayFromStore, innerArrayList) {
let newInnerItem= {};
newInnerItem.prop1 = null
newInnerItem.prop2 = null
innerArrayList.push(newInnerItem)
parentArrayFromStore[index].innerArrayList = innerArrayList;
return (dispatch) => {
dispatch(setParentArray(parentArrayFromStore));
}
}
export function setParentArray(payload) {
return {
type: ADD_DOC,
payload: payload
}
}
Hi the issue is with the push statement in your function when updating states in the constructor or reducer use concat or spread operator[...]>
I have made a sample over here
please check
onAddItem() {
let list = [...this.state.items, {text:`Check 1`}];
this.setState({items:list});
}
in your case you can do the following
let arr = [...childList, newChild]
then dispatch the arr
dispatch(setDoc(arr));

How to pass props to subcomponent in Reactjs

I am attempting to pass props from a parent component function to a child in React without any luck. I am utilizing the React Data Table Component's filtering functionality. The example documentation in Storybook is great, however I want to change the search box into a list of tags, that when clicked, will filter the data table just the same.
The data I'm trying to parse into individual "tags" is structured like this:
[{"id":"09090","first_name":"Cynthia","last_name":"McDonald","email":"email1#gmail.com","profile_url":"https:myprofile.com/1","types":["professor","science"]},
{"id":"03030","first_name":"Ryan","last_name":"Burke","email":"email2#gmail.com","profile_url":"https://myprofile.com/2","types":["student","science"]},
{"id":"05050","first_name":"Stewart","last_name":"Hook","email":"email3#gmail.com","profile_url":"https://myprofile.com/3","types":["professor","math"]}]
I am trying to create a unique tag list of the "types" attribute that acts as a filter onClick instead of onChange, just like the original search textbox example. So based on the sample data, we would end up with tags for professor, science, student, and math.
If you look at the code in Storybook, more specifically, line 45:
const subHeaderComponentMemo = React.useMemo(() =>
<Filter onFilter={value => setFilterText(value)} />, []);
My data is loaded via an API call and I am trying to pass that data to the subHeaderComponentMemo with props, i.e.,
const subHeaderComponentMemo = React.useMemo(() =>
<Filter data={people} onFilter={value => setFilterText(value)} />, []);
I am then trying to receive and loop through that data on line 20 and am replacing most of that code so that it will render the unique tags from the types attribute of the data.
Storybook code:
const Filter = ({ onFilter }) => (
<TextField id="search" type="search" role="search" placeholder="Search Title" onChange={e => onFilter(e.target.value)} />
);
My failed code
const Filter = ({props, onFilter }) => {
// Get a unique array of types
const types = [...new Set(props.types.map(type => type))];
return types.map(type => (
<span
className="badge badge-primary"
onClick={() => onFilter({ type })}
onKeyDown={() => onFilter({ type })}
tabIndex="0"
role="button"
>
{type}
</span>
));
};
This is of course resulting in an epic fail. The module isn't even displaying.
I'm new to React, so any insight/help would be greatly appreciated.
Updated code based on feedback
const Filter = ({ data, onFilter }) => {
console.dir(data);
if (data.length === 0) {
return <div>No data</div>;
}
const types = [...new Set(data.types.map(type => type))];
return (
<>
{types.map(type => (
<span
className="badge badge-primary"
onClick={() => onFilter({ type })}
onKeyDown={() => onFilter({ type })}
tabIndex="0"
role="button"
>
{type}
</span>
))}
;
</>
);
};
One of the errors I got when I made the changes was "cannot read property of 'map' undefined", which I equated to the fact that this component may be rendering before the data gets there and it is empty. So I added an if with a return in case this happens.
I also rewrote the return the statement in Filter based on the feedback because it did look like it would loop and try to render a new component for each iteration.
However, the issue still stands with the data not being present. The table is now loading with the data, but this component is just returning the "No data" div as if nothing ever happened. I'm not sure if this is due to the useMemo hook or what. Besides that, no errors.
Thank you for your time and feedback.
Another update
UseMemo needed the prop to be a dependency in its array... I'm finally seeing data when I console.log it.
const subHeaderComponentMemo = useMemo(
() => <Filter data={people} onFilter={value => setFilterText(value)} />,
[people]
);
However, I'm getting the 'map' undefined error again, as if my data is not in the structure I expect it to be in. I have not changed it and this is the code I'm using to try to access it (as shared before):
const types = [...new Set(data.types.map(type => type))];
Thanks again everyone... getting closer!
you should rewrite you component signature to match the one you are trying to use
const Filter = ({props, onFilter }) => // you are expecting a props name props
<Filter data={people} onFilter={value => setFilterText(value)} />, []); // you are passing data and setFilterText
so you should change it to
const Filter = ({data, onFilter }) =>

Categories

Resources