algolia instantsearch only show result that matches particular query (react) - javascript

I'm using react instant search to show list of users in select options. I managed to show all the users in select option but I want to only show users with admin role (role === 'admin').
Ofcourse I can do this in client side by filtering the hits on role but is there
any way to achieve this with react instant search?
This is how I showed all user.
AlgoliaProvider.js
import React from "react";
import algoliasearch from "algoliasearch/lite";
import { InstantSearch } from "react-instantsearch-dom";
const searchClient = algoliasearch(
process.env.REACT_APP_ALGOLIA_APP_ID,
process.env.REACT_APP_ALGOLIA_SEARCH_KEY
);
const AlgoliaProvider = ({ indexName, children, ...rest }) => {
return (
<InstantSearch indexName={indexName} searchClient={searchClient} {...rest}>
{children}
</InstantSearch>
);
};
export default AlgoliaProvider;
AutoCompleteSelect.js
import React from 'react';
import { Select } from 'antd';
import { connectAutoComplete } from 'react-instantsearch-dom';
const { Option } = Select;
const AutoCompleteSelect = ({
hits,
refine,
...rest
}) => {
const handleSuggestion = value => {
refine(value);
};
return (
<Select showSearch onSearch={handleSuggestion} {...rest}>
{hits.map(user => (
<Option
key={user.uid}
value={user.uid}
>
<span>{user.name}</span>
</Option>
))}
</Select>
);
};
export default connectAutoComplete(AutoCompleteSelect);
App.js
<AlgoliaProvider indexName="User">
<AutoCompleteSelect placeholder="user search" />
</AlgoliaProvider>

You need the faceting feature to achieve this.
Read more about it here: https://www.algolia.com/doc/guides/managing-results/refine-results/faceting/
I have configured one such facet via the UI (algolia frontend) and then I use it like this in instantSearch:
<InstantSearch searchClient={.... >
<Configure
hitsPerPage={<Number>}
filters={`role:${user.role}`} />
...
...
</InstantSearch>
See the filters part in the code, 'role' needs to be added as a facet in the configuration (via ui or code).

using Searchstate
<InstantSearch
....
searchState={{
role: 'admin',
}}
}}
More on: https://www.algolia.com/doc/api-reference/widgets/ui-state/react/

Related

Refactoring Search component from App js to Search js

So im following along with a book "The Road To React" By: Robin Wieruch. The book has you do everything inside the App.js file...which is extremely disappointing because this is obviously terrible practice. Anyway, ive extracted everything else to their own component files and i cant figure out how to extract the search to its own file. Ive attached an img showing the folder structure.
In App.js im trying to extract everything, leaving a return that returns the components like it should be. As it is the App.js file looks like this:
import React, { useState } from 'react';
import './App.css';
import Greeting from '../Greeting/Greeting';
import Search from '../Search/Search';
import List from '../List/List';
const useSemiPersistentState = (key, initialState) => {
const [value, setValue] = React.useState(
localStorage.getItem(key) || initialState
);
React.useEffect(() => {
localStorage.setItem(key, value);
}, [value, key]);
return [value, setValue]
};
// App component
const App = () => {
const stories = [
{
title: 'React',
url: 'https://reactjs.org/',
author: 'Jordan Walke',
num_comments: 3,
points: 4,
objectID: 0,
},
{
title: 'Redux',
url: 'https://redux.js.org/',
author: 'Dan Abramov, Andrew Clark',
num_comments: 2,
points: 5,
objectID: 1,
},
];
// Set state on searchTerm, setSearchTerm with custom hook
const [searchTerm, setSearchTerm] = useSemiPersistentState(
'search',
'React'
);
// Get the value of search input
const handleSearch = (event) => {
setSearchTerm(event.target.value);
};
// Check if user input matches stories array
// toLowerCase() both values
const searchedStories = stories.filter((story) => story.title.toLowerCase().includes(searchTerm.toLowerCase())
);
// Render
return (
<div className="App">
<Greeting name="Colin" age="28" occupation="Front-end developer" />
<InputWithLabel
id="search"
value={searchTerm}
isFocused
onInputChange={handleSearch}
>
<strong>Search:</strong>
</InputWithLabel>
<hr />
<List list={searchedStories} />
</div>
);
}
// Search bar
// Destructure props search, onSearch
const InputWithLabel = ({ id, value, type = "text", onInputChange, isFocused, children, }) => {
const inputRef = React.useRef();
React.useEffect(() => {
if (isFocused && inputRef.current) {
inputRef.current.focus();
}
}, [isFocused]);
return (
<>
<label htmlFor={id}>{children}</label>
<input
ref={inputRef}
id={id}
type={type}
value={value}
autoFocus={isFocused}
onChange={onInputChange}
/>
</>
)
}
export default App;
The List component which is a parent of the Item component looks like so:
import React from 'react';
import Item from '../Item/Item';
function List({ list }) {
return (
<ul>
{list.map(({ objectID, ...item }) => (
<Item key={objectID} {...item} />
))}
</ul>
);
}
export default List;
The Item component looks like so:
import React from 'react';
function Item({ title, url, author, num_comments, points }) {
return (
<div>
<li>
<span>
<a href={url}>{title}</a>
</span>
<span> {author}</span>
<span> {num_comments} comments,</span>
<span> {points} points.</span>
</li>
</div>
);
}
export default Item;
The Greeting Component and Helpers folder are unrelated in any way so i wont post them.
Just as a note all of the code in here works... and at the time im not really interested in refactoring this unless you care to. Im just trying to extract all of that nonsense that relates to what should be a separate Search component out of App.js and into Search.js. Have been trying and ive hit a wall with this as im still new with react.
Here are the errors shown once i move all of the Search content related code from App.js to Search.js and attempt to import to the Search component into App.js
****Failed to compile****
src/Components/App/App.js
Line 60:8: 'InputWithLabel' is not defined react/jsx-no-undef
Line 62:16: 'searchTerm' is not defined no-undef
Line 64:24: 'handleSearch' is not defined no-undef
Line 69:19: 'searchedStories' is not defined no-undef
Search for the keywords to learn more about each error.

React testing: Change select value in test

I am trying to write a test to check a value after a change in a <select> dropdown. This is my component:
import React from 'react';
const BasicDropdown = ({ key, value, options }) => {
return (
<div className={`basic-dropdown-${key}`}>
<select
className={`basic-dropdown-${key}-select`}
name={`basic-dropdown-${key}-select`}
{...{value}}
>
{options.map(option =>
<option
className={`basic-dropdown-${key}-select-${option}-option`}
key={option}
value={option}
>
{option.charAt(0).toUpperCase() + option.slice(1)}
</option>
)}
</select>
</div>
);
};
export default BasicDropdown;
So far it's very simple. The reason for having a component is that I will expand this later depending on the props and other things. So I decided to write a test for this component to start with:
import React from 'react'
import TestUtils from 'react-dom/test-utils';
import BasicDropdown from './BasicDropdown';
const options = ['option-A', 'option-B', 'option-C'];
describe("BasicDropdown", () => {
let renderedCmpt;
beforeAll(() => {
renderedCmpt = TestUtils.renderIntoDocument(
<BasicDropdown key="test" value="option-A" options={options} />
)
});
it("Should have correct value after change", () => {
const select = TestUtils.findRenderedDOMComponentWithClass(renderedCmpt, 'basic-dropdown-test-select');
expect(select.value).toEqual("option-A");
TestUtils.Simulate.change(select, {target: {value: 'option-C'}});
const selectChanged = TestUtils.findRenderedDOMComponentWithClass(renderedCmpt, 'basic-dropdown-test-select');
expect(selectChanged.value).toEqual("option-C");
});
});
My problem is that when running this test using jest (jest --coverage=false --config ~/projects/Beehive/config/jest.config.js ~/projects/Beehive/tests/BasicDropdown-test.js) I get the following error:
Expected: "option-C"
Received: "option-A"
at line 21, which means that the value of the select is never changed during the Simulate.
What am I doing wrong?
You need to add onChange on select element to reflect the change on the test, else it will always be option-A.

Send searchParam data from one Component to another component in reactjs

I have one Component which shows a list of data in a dropdown and there is an option to search these data which works as a filter. Here is my code:
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Popover from '../../Popover';
import Input from '../../Input';
import Icon from '../../Icon';
import IconButton from '../../IconButton';
const DropDownFilter = props => {
const { label, options, onChange, isSearchEnabled } = props;
const [activeOption, setActiveOption] = useState({});
const [filter, setfilter] = useState('');
const searchFilter = event => {
setfilter(event.target.value);
};
const removeFilter = () => {
setfilter('');
};
const lowercasedFilter = filter.toLowerCase();
const filteredData = options.filter(item => {
return Object.keys(item).some(
key => typeof item[key] === 'string' && item[key].toLowerCase().includes(lowercasedFilter)
);
});
const labelText = activeOption.label ? activeOption.label : label;
const handleSelectedOption = option => {
setActiveOption(option);
onChange(option);
};
return (
<div className="filter">
<Popover linkText={labelText} size="small" direction="bottom-left">
{isSearchEnabled && (
<div className="filter__search">
<Input
value={filter}
onChange={searchFilter}
preIcon={
<div role="presentation">
<Icon name="search" />
</div>
}
placeholder="Search"
postIcon={
filter.length > 0 && (
<IconButton
icon={<Icon name="close" />}
size="tiny"
onClick={removeFilter}
standalone={true}
isIconOnly={true}
/>
)
}
/>
</div>
)}
<ul className="filter__options filter__options--scrollbar">
{filteredData.map(option => (
<li
key={option.value}
role="presentation"
className={classNames('filter__options-option', {
'filter__options-option--active': option.value === activeOption.value,
})}
onClick={() => handleSelectedOption(option)}
>
{option.label}
</li>
))}
</ul>
</Popover>
</div>
);
};
DropDownFilter.defaultProps = {
label: 'Filter Menu',
options: [],
isSearchEnabled: true,
};
DropDownFilter.propTypes = {
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
options: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
})
),
onChange: PropTypes.func.isRequired,
isSearchEnabled: PropTypes.bool,
};
export default DropDownFilter;
Here is a gif of it: https://recordit.co/HtalUtuPsj
Now during searching I want to send the value of the search param to another component, the value will be used to search from a DB or any other external data source which is being handled in that new component. Such as, if I am searching for Ratings, this component should search for it in the existing options list it has in its own component, as well as the same time it will search for Ratings in any other external data source or DB. This external network call, search or any other functionality will be processed in the other component. So this component will only send the search param; for example Ratings to the other component in real time.
I can think of an idea like I will get the searchParam in a state and pass the setState value to a new props which will be called through an onSearchParamChange function, this new function will pass the data through a callback and the other component will get the data through calling that props of this component. I am not sure if this is the correct way and also I am not able to implement this thought in the code either. Is there any better way to do it? if so what would be that coding implementation?
If you need to pass to a parent component you should be able to use for example the onChange prop which is passed to your component, like you are doing in the handleSelectedOption function. That function is in fact passing the chosen option to the parent component. If you want to pass to the parent component when the user is typing, then you should call the onChange function also in searchFilter:
const searchFilter = event => {
const option = event.target.value);
setfilter(option);
onChange(option);
};
If you want to pass it to a child component, the you can just pass it as prop:
<ChildComponent filter={ filter } />

How do I add the ability to edit text within a react component?

So here's the user function I'm trying to create:
1.) User double clicks on text
2.) Text turns into input field where user can edit text
3.) User hits enter, and upon submission, text is updated to be edited text.
Basically, it's just an edit function where the user can change certain blocks of text.
So here's my problem - I can turn the text into an input field upon a double click, but how do I get the edited text submitted and rendered?
My parent component, App.js, stores the function to update the App state (updateHandler). The updated information needs to be passed from the Tasks.jsx component, which is where the text input is being handled. I should also point out that some props are being sent to Tasks via TaskList. Code as follows:
App.js
import React, {useState} from 'react';
import Header from './Header'
import Card from './Card'
import cardData from './cardData'
import Dates from './Dates'
import Tasks from './Tasks'
import Footer from './Footer'
import TaskList from './TaskList'
const jobItems= [
{
id:8,
chore: 'wash dishes'
},
{
id:9,
chore: 'do laundry'
},
{
id:10,
chore: 'clean bathroom'
}
]
function App() {
const [listOfTasks, setTasks] = useState(jobItems)
const updateHandler = (task) => {
setTasks(listOfTasks.map(item => {
if(item.id === task.id) {
return {
...item,
chore: task.chore
}
} else {
return task
}
}))
}
const cardComponents = cardData.map(card => {
return <Card key = {card.id} name = {card.name}/>
})
return (
<div>
<Header/>
<Dates/>
<div className = 'card-container'>
{cardComponents}
</div>
<TaskList jobItems = {listOfTasks} setTasks = {setTasks} updateHandler = {updateHandler}/>
<div>
<Footer/>
</div>
</div>
)
}
export default App;
Tasks.jsx
import React, {useState} from 'react'
function Tasks (props) {
const [isEditing, setIsEditing] = useState(false)
return(
<div className = 'tasks-container'>
{
isEditing ?
<form>
<input type = 'text' defaultValue = {props.item.chore}/>
</form>
: <h1 onDoubleClick ={()=> setIsEditing(true)}>{props.item.chore}</h1>
}
</div>
)
}
export default Tasks
TaskList.jsx
import React from 'react'
import Tasks from './Tasks'
function TaskList (props) {
const settingTasks = props.setTasks //might need 'this'
return (
<div>
{
props.jobItems.map(item => {
return <Tasks key = {item.id} item = {item} setTasks = {settingTasks} jobItems ={props.jobItems} updateHandler = {props.updateHandler}/>
})
}
</div>
)
}
export default TaskList
You forgot onChange handler on input element to set item's chore value.
Tasks.jsx must be like below
import React, {useState} from 'react'
function Tasks (props) {
const [isEditing, setIsEditing] = useState(false)
const handleInputChange = (e)=>{
// console.log( e.target.value );
// your awesome stuffs goes here
}
return(
<div className = 'tasks-container'>
{
isEditing ?
<form>
<input type = 'text' onChange={handleInputChange} defaultValue = {props.item.chore}/>
</form>
: <h1 onDoubleClick ={()=> setIsEditing(true)}>{props.item.chore}</h1>
}
</div>
)
}
export default Tasks
So, first of all, I would encourage you not to switch between input fields and divs but rather to use a contenteditable div. Then you just use the onInput attribute to call a setState function, like this:
function Tasks ({item}) {
return(
<div className = 'tasks-container'>
<div contenteditable="true" onInput={e => editTask(item.id, e.currentTarget.textContent)} >
{item.chore}
</div>
</div>
)
}
Then, in the parent component, you can define editTask to be a function that find an item by its id and replaces it with the new content (in a copy of the original tasks array, not the original array itself.
Additionally, you should avoid renaming the variable between components. (listOfTasks -> jobItems). This adds needless overhead, and you'll inevitably get confused at some point which variable is connected to which. Instead say, <MyComponent jobItems={jobItems} > or if you want to allow for greater abstraction <MyComponent items={jobItems} > and then you can reuse the component for listable items other than jobs.
See sandbox for working example:
https://codesandbox.io/s/practical-lewin-sxoys?file=/src/App.js
Your Task component needs a keyPress handler to set isEditing to false when enter is pressed:
const handleKeyPress = (e) => {
if (e.key === "Enter") {
setIsEditing(false);
}
};
Your updateHandler should also be passed to the input's onChange attribute, and instead of defaultValue, use value. It also needs to be reconfigured to take in the onChange event, and you can map tasks with an index to find them in state:
const updateHandler = (e, index) => {
const value = e.target.value;
setTasks(state => [
...state.slice(0, index),
{ ...state[index], chore: value },
...state.slice(index + 1)
]);
};
Finally, TaskList seems like an unnecessary middleman since all the functionality is between App and Task; you can just render the tasks directly into a div with a className of your choosing.
react-edit-text is a package I created which does exactly what you described.
It provides a lightweight editable text component in React.
A live demo is also available.

Reactjs Multiple Dropdown

I'm working on React JS project. I have 4 dropdown button (select option). And all the dropdown will be coming from DB dynamically. So wanted to know what is the right method to implement.
Initially I had only 1 dropdown box, so implemented it with ajax call and append the values with <option> tag under <select> tag. Now I have 3 more dropdown, so do I need to call multiple ajax calls for all 4 box ? or is there any other ways to implement it ?
Please do suggest here. Because I don't want to implement in wrong way and revert back again.
If you create a small component for your dropdowns, like so:
import React, { Component } from 'react';
class SelectOption extends Component {
render() {
return (
<option value={this.props.dataItem.key}>{this.props.dataItem.val}</option>
)
}
}
class SimpleDropdown extends Component {
render() {
let options = [];
if (this.props.selectableData) {
const selectableData = this.props.selectableData;
options = selectableData.map((dataItem) =>
<SelectOption key={'option_' + dataItem.key} dataItem={dataItem} />
);
}
return (
<div>
<select onChange={this.props.handleInputChange} name={this.props.name} >
{options}
</select>
</div>
)
}
}
export default SimpleDropdown;
You can use it in your parent component, something like this...
import React, { Component } from 'react';
import SimpleDropdown from './common/SimpleDropdown';
class Parentextends Component {
componentDidMount() {
// here you handle your ajax call or calls, depending on what you choose to go with
}
handleInputChange = (e) => {
const target = e.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
const ajaxResultFirst = ajaxResult.First;
const ajaxResultSecond = ajaxResult.Second;
const ajaxResultThird = ajaxResult.Third;
const ajaxResultFourth = ajaxResult.Fourth;
return (
<section className="form">
<SimpleDropdown
name="FirstDropdown"
selectableData={ajaxResultFirst}
handleInputChange={this.handleInputChange}
/>
<SimpleDropdown
name="SecondDropdown"
selectableData={ajaxResultSecond}
handleInputChange={this.handleInputChange}
/>
<SimpleDropdown
name="ThirdDropdown"
selectableData={ajaxResultThird}
handleInputChange={this.handleInputChange}
/>
<SimpleDropdown
name="FourthDropdown"
selectableData={ajaxResultFourth}
handleInputChange={this.handleInputChange}
/>
</section>
);
}
};
export default Parent;
Something like this should work. But I still recommend using a different plugin than jquery for making ajax requests, my first choice is axios https://github.com/mzabriskie/axios.

Categories

Resources