Filter Search in React JS - reactjs

Im having trouble filtering data based on my input search... my goal is to search for 'M' and to see a list of all payload data containing 'M' and as I get closer to the exact string 'Matt' only show the payload data for 'Matt' and bacisally be able to search by first_name, last_name or number. Thank you in advance, ANY feedback is appreciated! Ive been stuck on this >_<
Im using a Custom component library and trying to do a basic search & filter, the issue is not with the custom library but with the filter function. It doesnt seem to retrieve the v.first_name value. Im also open to any other sorts of filter libraries / approaches.
I typed in the letter 'M' into the searchbox UI component and got the following outputs for each console log statement
import React, { Component } from 'react';
import { IS_FETCHING_DBUSERS, FETCH_DBUSERS_SUCCESS } from '../../actions/keys';
import { users } from '../../actions/URI';
import { fetchComponent } from '../../actions/index';
import { connect } from 'react-redux';
import _ from 'lodash';
import {
LumosTheme,
Grid, Form, Icon, Container, Loader
} from '#CustomLibrary/core';
class SearchBar extends Component {
state = {
responseData: " ",
handle: true,
query: "",
filterValues: []
};
searchString = this.state.query;
responseData = this.props.Users.data;
componentDidMount() {
this.props.fetchComponent([IS_FETCHING_DBUSERS, FETCH_DBUSERS_SUCCESS], users).then(() => {
return this.setState({
handle: false
})
})
}
handleInputChange = (value) => {
console.log("In HandleInputFunction, Value= ", value) // Output of value is 'M'
this.setState({query:value}, () => {
console.log("In HandleInputFunction, query= ", this.state.query) // Output of query is 'M'
this.filterArray();
}
)
}
filterArray = () => {
console.log('In Filter fxn')
let searchString = this.state.query;
let responseData = this.props.Users.data;
console.log('This is the responseData in Filter: ', responseData); // output of responseData is '(6)[{...},{...},{...},{...},{...},{...}]'
console.log('This is my filterValues in Filter: ', this.state.filterValues); //output of filterValues is '[]'
console.log('This is my searchString in Filter: ', searchString) //output of searchString is 'M'
if(searchString.length > 0){
const filterData = _.filter(this.state.responseData, (v) => v.first_name === searchString);
console.log('This is my filterData in loop: ',filterData) //output of filterData is '[]'
this.setState({
filterValues : filterData
})
console.log('This is my filterValues in loop: ', this.state.filterValues) //output of filterValues is '[]'
}
}
// for now this drop down 'searchByOptions' is hard coded and just here for UI visual purposes, what I want to do later is depending on the option the user choses I pass the correct payload.
searchByOptions = [
{ label: 'Name or number', value: 'NAME/number' },
{ label: 'Distribution List', value: 'DL' },
{ label: 'Database Schema or Table', value: 'DB' },
{ label: 'Role', value: 'Role' }
];
render() {
if (this.state.handle) {
return <Loader />
}
else {
return (
<LumosTheme>
<Container width='1379px'>
</Container>
<Container width='1379px'>
<Grid paddingTop='10px'>
<Form.Item width='380px'>
<Form.Dropdown
options={this.searchByOptions}
defaultOption={this.searchByOptions[0]}
/>
</Form.Item>
</Grid>
<Grid flexWrap="wrap" width='1000px'>
< Form.SearchBox placeholder='Search' icon={<Icon.SearchRounded />}
userSelectOnClick
openOnClick
onSearch={this.handleInputChange}
value={this.state.query}
>
<Form.SearchList >
{this.state.responseData ?
this.state.filterValues.map(item => (
<Form.SearchOption
value={item.first_name}
/>
)):'null'}
</Form.SearchList>
</ Form.SearchBox>
</Grid>
</Container>
</LumosTheme>
)
}
}
}
const mapStateToProps = state => {
return {
Users: state.users
}
}
export default connect(mapStateToProps, { fetchComponent })(SearchBar);
my payload is being fetched correctly and looks like so
0: {id:1, first_name: "Matt", last_name: "Jones", number:"123",}
1: {id:2, first_name: "Alex", last_name: "Lee", number:"675",}
2: {id:3, first_name: "Adam", last_name: "Khan", number:"733",}
3: {id:4, first_name: "Sue", last_name: "Kid", number:"248",}
4: {id:5, first_name: "Jade", last_name: "Smith", number:"907",}
5: {id:6, first_name: "Luca", last_name: "James", number:"125",}

It looks like you are doing an exact match with that filter condition.
You can use _.filter(this.state.responseData, (v) => v.first_name.includes(searchString));.
Pro tip: once you go lodash, you never go back... That didn't rhyme, but you get the point.

Related

How to add a static object before dynamic data map in React?

I use react-native-element-dropdown package in my app.
I want to add dynamic data in the component. But first, I would like to add an empty dropdown option value like:
{label: '-- Please Select --', value: ""}
const dropdownData = () => {
if(userList){
return userList?.filter(user => user.id != null).map((user, index)=> {
return {label: user.username, value: user.id}
})
}
}
The dropdownData is called in component's data property:
<Dropdown data={ dropdownData() } />
How can I add the empty value before the userList map?
Append your hard-coded option to the array before returning:
const dropdownData = () => {
if (userList) {
return [
{ label: '-- Please Select --', value: "" },
...userList
.filter(user => user.id != null)
.map(user => ({ label: user.username, value: user.id }))
];
}
}
You can do the same in the JSX:
<Dropdown data={[ ...dropdownData(), { label: '-- Please Select --', value: "" } ]} />
try this:
<Dropdown data={[{label: '-- Please Select --', value: ""}, ...dropdownData()]} />

Testing zustand state changes caused by a component in Jest

I am pretty new to using jest and Im trying to test a component that makes a state change which acts upon my global state (using Zustand). Basically im clicking a button and its adding an item to my state.traits. Here is my component code:
import { Flex, useToast } from '#chakra-ui/react'
import { FC } from 'react'
import { useProfileStore } from 'stores/profileStore'
interface DataTrait {
name: string,
id: string
}
type Props = {
trait: DataTrait
}
export const ChipItem: FC<Props> = ({ trait }) => {
const { traits, setTraits } = useProfileStore()
const toast = useToast()
const traitNames = traits.map((trait) => trait.name)
const emptyTraits = traits.filter((trait) => trait.name === "")
const handleClick = (trait: DataTrait) => {
if (!traitNames.includes(trait.name) && emptyTraits.length !== 0) {
let currentItem = traits.filter(trait => trait.name === "")[0]
let items = [...traits]
let item = {position: currentItem.position, id: trait.id, name: trait.name}
items[items.indexOf(currentItem)] = item
setTraits(items)
} else if (emptyTraits.length === 0){
toast({
title: 'Error',
status: 'error',
description: 'Only 5 traits can be selected',
isClosable: true,
duration: 5000
})
} else {
toast({
title: 'Error',
status: 'error',
description: 'Please select unique traits',
isClosable: true,
duration: 5000
})
}
}
return (
traitNames.includes(trait.name) ? (
<Flex mx={4} p={2} cursor="pointer" borderRadius="20px" backgroundColor="green" borderWidth="1px" borderColor="white" textColor="white" onClick={() => handleClick(trait)}>{trait.name}</Flex>
) : (
<Flex mx={4} p={2} cursor="pointer" borderRadius="20px" borderWidth="1px" borderColor="grey" onClick={() => handleClick(trait)}>{trait.name}</Flex>
)
)
}
here is my store code:
import create from 'zustand'
export interface Trait {
position: string,
name: string,
id: string,
}
export type Traits = Trait[]
const initialTraits = [
{position: "0", name: "", id: ""},
{position: "1", name: "", id: ""},
{position: "2", name: "", id: ""},
{position: "3", name: "", id: ""},
{position: "4", name: "", id: ""},
]
export type ProfileStore = {
traits: Traits;
setTraits: (traits: Traits) => void;
clearTraits: () => void;
}
export const useProfileStore = create<ProfileStore>((set) => ({
traits: initialTraits,
setTraits: (traits) => set({ traits }),
clearTraits: () => set({ traits: initialTraits })
}))
and here is my test code:
import React from 'react';
import { ChipItem } from "../../ChipList/ChipItem";
import { act, render, renderHook } from "#testing-library/react";
import { useProfileStore } from "../../../stores/profileStore";
const stubbedTrait = {
name: "Doing Work",
id: "efepofkwpeok"
}
it("displays the trait chip", () => {
const { queryByText } = render(<ChipItem trait={stubbedTrait} />);
expect(queryByText("Doing Work")).toBeTruthy();
})
it("sets the chip information in the store", () => {
act(() => {
const { traits } = renderHook(() => useProfileStore())
const { getByText } = render(<ChipItem trait={stubbedTrait}/>);
getByText(stubbedTrait.name).click()
expect(traits.includes(stubbedTrait)).toBeTruthy()
})
})
whats happening, is that it keeps telling me that renderHook is not a function and traits always comes back undefined. any help would be greatly appreciated!
Currently you must install and import React Testing Hooks separately
The best way to unit test Zustand state changes inside and specific component is not by using Zustand but by mocking the store hook with Jest.
You should create a test case for the Zustand Store using React Hook Testing library and once you verify the hook behaves as expected, then you mock the store with manual traits and setTraits changes.
Once you have the unit tests then you should test the behaviour of the real hook and components together with integration tests.

React GraphQL mutation returning Invalid input

Background: I'm building a Shopify app using React, NextJS, and GraphQL. The functionality is to add an extra privateMetafield with a value for each selected product.
The problem: I'm trying to create or update (if privateMetafield exists) with Mutation using React-Apollo. I have tried to run the GraphQL mutation in Insomnia (like postman for GraphQL) and it works. But when I add it to my code I don't get the GraphQL to receive the mutation data. Instead, I get this error message:
Unhandled Runtime Error
Error: GraphQL error: Variable $input of type PrivateMetafieldInput! was provided invalid value for privateMetafields (Field is not defined on PrivateMetafieldInput), namespace (Expected value to not be null), key (Expected value to not be null), valueInput (Expected value to not be null)
Insomnia Successful GraphQL test (what it should be like)
edit-products.js
...
import gql from 'graphql-tag';
import { Mutation } from 'react-apollo';
...
const UPDATE_EMISSION = gql`
mutation($input: PrivateMetafieldInput!) {
privateMetafieldUpsert(input: $input) {
privateMetafield {
namespace
key
value
}
}
}
`;
...
class EditEmission extends React.Component {
state = {
emission: '',
id: '',
showToast: false,
};
render() {
const { name, emission, id } = this.state;
return (
<Mutation
mutation={UPDATE_EMISSION}
>
{(handleSubmit, { error, data }) => {
const showError = error && (
<Banner status="critical">{error.message}</Banner>
);
const showToast = data && data.productUpdate && (
<Toast
content="Sucessfully updated"
onDismiss={() => this.setState({ showToast: false })}
/>
);
return (
<Frame>
... <Form>
<TextField
prefix="kg"
value={emission}
onChange={this.handleChange('emission')}
label="Set New Co2 Emission"
type="emission"
/>
...
<PageActions
primaryAction={[
{
content: 'Save',
onAction: () => {
const item = store.get('item');
const id = item.id;
const emissionVariableInput = {
owner: id,
privateMetafields: [
{
namespace: "Emission Co2",
key: "Co2",
valueInput: {
value: emission,
valueType: "INTEGER"
}
}
]
};
console.log(emissionVariableInput)
handleSubmit({
variables: { input: emissionVariableInput },
});
}
}
]}
secondaryActions={[
{
content: 'Remove emission'
}
]}
/>
</Form>
...
</Mutation>
);
}
handleChange = (field) => {
return (value) => this.setState({ [field]: value });
};
}
export default EditEmission;
I get this in the console when I log emissionVariableInput, which looks correct. But why is the data not passed properly to the GraphQL mutation and how do I fix it?
I expect the GraphQL mutation to be successful and create/update a privateMetafield owned by a product.
Thanks in advance!
You have different shapes for input in your examples. In Insomnia you pass:
{
owner: id,
namespace: "Emission Co2",
key: "Co2",
valueInput: {
value: emission,
valueType: "INTEGER"
}
}
While in the code your input looks like:
{
owner: id,
privateMetafields: [{
namespace: "Emission Co2",
key: "Co2",
valueInput: {
value: emission,
valueType: "INTEGER"
}
}]
};

Error select based on selection from 1st select in React Hooks and dinamically inputs

I have two selects and I want to populate the second select base in the selection of the first one in react. When I select a countrie I want a select2 be displayed with its states and the value on the second select be updated with the value chose.
I have the following code,
const MyForm = (props) => {
const COUNTRIES = [
{
displayValue: "Country1",
value: "C1"
},
{
displayValue: "Country2",
value: "C2"
}
]
const STATES = {
"": [ {
displayValue: "",
value: ""
}],
"C1": [{
displayValue: "State 1",
value: "S11"
},
{
displayValue: "State 2",
value: "S12"
}],
"C2": [{
displayValue: "State n1",
value: "C21"
},
{
displayValue: "STate n2",
value: "C22"
}]
}
let inputsForms = {
country: {
elementType: 'select',
elementConfig: {
type: 'select',
placeholder: '',
options: COUNTRIES,
firstOption: "-- Choose Country"
},
value: ''
},
states: {
elementType: 'select',
elementConfig: {
type: 'select',
placeholder: '',
options: [], // I need these options depend on the countrie selected STATES["C1"]
firstOption: "-- Choose States"
},
value: ''
}
}
const [myForm, setmyForm] = useState(inputsForms);
const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
};
};
const inputChangedHandler = (e, controlName) => {
const countrieValue = controlName ==="country"?e.target.value:"";
const stateOptions = myForm["states"].elementConfig;
stateOptions["options"] = STATES[countrieValue];
const updatedControls = updateObject(myForm, {
[controlName]: updateObject(myForm[controlName], {
value: e.target.value
})
});
setmyForm(updatedControls);
}
const ElementsArray = [];
for (let key in myForm) {
ElementsArray.push({
id: key,
config: myForm[key]
});
}
let form = (
<form>
{ElementsArray.map(el => (
<Input
key={el.id}
elementType={el.config.elementType}
elementConfig={el.config.elementConfig}
value={el.config.value}
changed={e => inputChangedHandler(e, el.id)}
firstOption={el.config.firstOption}
/>
))}
</form>
);
return(
<div>
{form}
</div>
);
}
export default MyForm;
The options charge on the select2, however when I select an option on the second select, the options dissappear and the value of the select2 is not updated.
Thanks.
As inputChangeHandler is getting called every input change, the data is resetting even there is change in the state. You could check for the contrieValue and set the state data so the data is not reset.
const inputChangedHandler = (e, controlName) => {
const countrieValue = controlName === "country" ? e.target.value : "";
if (countrieValue) {
const stateOptions = myForm["states"].elementConfig;
stateOptions["options"] = STATES[countrieValue];
const updatedControls = updateObject(myForm, {
[controlName]: updateObject(myForm[controlName], {
value: e.target.value
})
});
setmyForm(updatedControls);
}
}
You need to add a kind of conditional to update the state whenever the state is selected so that it does not affect the original country object.
const inputChangedHandler = (e, controlName) => {
if (countrolName === 'states') {
// your logic here
return;
}
const countrieValue = controlName === 'country' ? e.target.value : '';
const stateOptions = myForm['states'].elementConfig;
stateOptions['options'] = STATES[countrieValue];
const updatedControls = updateObject(myForm, {
[controlName]: updateObject(myForm[controlName], {
value: e.target.value,
}),
});
setmyForm(updatedControls);
};

Can't Update Store in Redux

Unable to figure out the issue as to why my store is not updating. My end goal is to have the "here is the store first name" and "here is the store last name" in the Component.js file display the first and last name of a data block I enter in the reducer (Joe and Smith respectively). Whenever I click the login button after entering proper credentials, the console logs "USERNAME AND PASS MATCH" which tells me that it recognizes that there is a match between what I'm supplying and what is in the data. But, it refuses to update the first_name and last_name of the store for some reason...
In my Component.js file:
import React from "react"
import { connect } from "react-redux"
import { setUsername, setPassword, handleLogin } from "../actions/userActions"
#connect((store) => {
return {
loginShow: store.loginShow,
loggedIn: store.loggedIn,
username: store.username,
password: store.password,
first_name: store.first_name,
last_name: store.last_name,
id: store.id,
invalidAttempt: store.invalidAttempt,
};
})
export default class Layout extends React.Component {
render() {
let loginView = null;
if (this.props.loginShow && this.props.invalidAttempt === false) {
loginView = (
<div className="loginBox">
<h3>Enter username and password</h3>
<input type="text" placeholder="Please enter username..."
onChange={(e) => this.props.dispatch(setUsername(e.target.value))} />
<br />
<input type="password" placeholder="Please enter password..."
onChange={(e) => this.props.dispatch(setPassword(e.target.value))} />
<p>Here is the store first name: {this.props.first_name}</p>
<p>Here is the store last name: {this.props.last_name}</p>
<br />
<button onClick={() => this.props.dispatch(handleLogin(this.props.username, this.props.password))}>Login</button>
</div>
)
}
return (
<div>
{loginView}
</div>
)
}
}
Here is the action file:
export function setUsername(name) {
return {
type: "SET_USERNAME",
payload: name,
}
}
export function setPassword(pword) {
return {
type: "SET_PASSWORD",
payload: pword,
}
}
export function handleLogin(uname, pword) {
return {
type: "HANDLE_LOGIN",
payload: {user: uname, pass: pword}
}
}
Now in my reducer file, I'm pretty sure this is where the issue is:
let data = [
{
"username": "imJoe",
"first_name": "Joe",
"last_name": "Smith",
"password": "password123",
"id": 1
}
]
export default function userReducer(state = {
loginShow: true,
loggedIn: false,
username: '',
password: '',
first_name: '',
last_name: '',
id: '',
invalidAttempt: false,
check: false,
}, action) {
switch (action.type) {
case "SET_USERNAME": {
console.log("SET USERNAME REUCER")
return { ...state, username: action.payload }
}
case "SET_PASSWORD": {
console.log("SET PASS REUCER")
return { ...state, password: action.payload }
}
case "HANDLE_LOGIN": {
console.log("HANDLE LOGIN REDUCER")
data.map(xyz => {
if (xyz.username === action.payload.user && xyz.password === action.payload.pass){
console.log("USERNAME AND PASS MATCH")
return {
...state,
first_name: xyz.first_name,
last_name: xyz.last_name,
}
}
})
}
}
return state
}
Your store not update because in your case "HANDLE LOGIN", you return nothing. From redux docs: reducer always should return new state, but you only map and return nothing.
Here is how "HANDLE_LOGIN" should look like:
case "HANDLE_LOGIN": {
console.log("HANDLE LOGIN REDUCER")
let firstName = null;
let lastName = null;
data.map(xyz => {
if (xyz.username === action.payload.user && xyz.password === action.payload.pass){
console.log("USERNAME AND PASS MATCH")
firstName = xyz.first_name;
lastName = xyz.last_name;
}
});
return {
...state,
first_name: firstName,
last_name: lastName,
}
}
So you reducer reŠµurns data, even data items is null your reducer returns items with null.

Resources