I want to do a little dictionary searcher in React and I have a problem with saving all data in searchResult state.
My code:
const Search = () => {
const [searchResult, setSearchResult] = useState({});
const handleSearch = e => {
const finded = e.target.value;
console.log(finded);
if (finded === "") {
setSearchResult({});
} else {
for (let i = 0; i < datas.length; i++) {
if (
datas[i].pol.includes(finded) ||
datas[i].eng.includes(finded) ||
datas[i].ger.includes(finded) ||
datas[i].ned.includes(finded) ||
datas[i].spa.includes(finded) ||
datas[i].fra.includes(finded) ||
datas[i].ita.includes(finded)
) {
console.log(datas[i].pol, datas[i].eng, datas[i].ger);
setSearchResult({
...searchResult,
pol: datas[i].pol,
eng: datas[i].eng,
ger: datas[i].ger,
ned: datas[i].ned,
spa: datas[i].spa,
fra: datas[i].fra,
ita: datas[i].ita
});
console.log("searchResult", searchResult);
}
}
}
};
return (
<>
<div className="main__header">
<div className="main__header-circle"></div>
<input
type="text"
className="search"
placeholder="Search a word..."
onChange={handleSearch}
/>
</div>
<div>{searchResult.pol}</div>
<div>{searchResult.eng}</div>
<div>{searchResult.ger}</div>
<div>{searchResult.ned}</div>
<div>{searchResult.spa}</div>
<div>{searchResult.fra}</div>
<div>{searchResult.ita}</div>
</>
);
};
What's the problem? For example if I write "one" in searcher I want to show every words with "one" but in console.log is working good because it showing all possible words but in setSearchResult is saving only the last one. I was trying to do this by useEffect but also not working. How can I fix that, to save the same results to serachResult like in console.log ?
(in 'datas' are all words)
The issue of the searchResult getting overwritten every time there is a new input from user is due to these 2 reasons:
searchResult variable is declared as an object, instead of as an array
The function to update searchResult is updating the old value (overwriting) instead of concatenating new data to the existing value.
The issue can be resolved by making these 2 changes to the above code:
1. Declare the variable as an array.
const [searchResult, setSearchResult] = useState([])
2. Concatenate new data to old value.
const handleSearch = (e) => {
const found = e.target.value
if (found !== "") {
for (let i = 0; i < datas.length; i++) {
if (datas[i].pol.includes(found)) {
setSearchResult([
...searchResult,
{pol: datas[i].pol, eng: datas[i].eng}
])
}
}
}
// ...
}
More information:
https://www.youtube.com/watch?v=RZ5wKYbOM_I
https://medium.com/javascript-in-plain-english/how-to-add-to-an-array-in-react-state-3d08ddb2e1dc
Because setSearchResult is an object, you cant store same key multiple times, it will just overrride the value of given key, you need to change searchResult to be initialized as an array.
} else {
const filteredData = datas.map(data => (
data.pol.includes(finded) ||
data.eng.includes(finded) ||
data.ger.includes(finded) ||
data.ned.includes(finded) ||
data.spa.includes(finded) ||
data.fra.includes(finded) ||
data.ita.includes(finded)
)}
console.log(filteredData);
setSearchResult(filteredData);
console.log("searchResult", searchResult);
}
}
}
Related
So i am using this package "react-checkbox-tree" to make a checkbox, but since this is made on classes components and i need to do it with functions and hooks, this is being a bit tricky to my actual skills.
//Checkbox Tree
const [checkedTree, setCheckedTree] = useState([]);
const [expandedTree, setExpandedTree] = useState(["1"]);
const [filterText, setFilterText] = useState("");
const [nodesFiltered, setNodesFiltered] = useState();
///FILTER LOGIC /////
const onFilterChange = (e) => {
setFilterText(e.target.value);
if (e.target.value) {
filterTree();
}
};
const filterTree = () => {
// Reset nodes back to unfiltered state
if (!filterText || filterText === "" || filterText.length === 0) {
setNodesFiltered(nodes);
return;
}
const nodesFiltered = (nodes) => {
return nodes.reduce(filterNodes, []);
};
setNodesFiltered(nodesFiltered);
};
const filterNodes = (filtered, node) => {
const children = (node.children || []).reduce(filterNodes, []);
if (
// Node's label matches the search string
node.label.toLocaleLowerCase().indexOf(filterText.toLocaleLowerCase()) >
-1 ||
// Or a children has a matching node
children.length
) {
filtered.push({ ...node, ...(children.length && { children }) });
}
return filtered;
};
//
My first problem is that when i search for the parent, i only get the last children of the array for some reason.
The Second is that when i use the backspace button, the filter stops working until i clean every char.
I made a codesandbox to help you guys to understand the problems:
https://codesandbox.io/s/checkboxtree-6gu60
This is the example on with classes:
https://github.com/jakezatecky/react-checkbox-tree/blob/master/examples/src/js/FilterExample.js
Tks in advance!
For your second problem, I solved it by passing through onKeyDown as well as onChange from my search input:
<input
type="text"
onChange={onFilterChange}
onKeyDown={onBackspace}
/>
which calls
// If the user deletes the search terms, reset to unfiltered
const onBackspace = e => {
var key = e.keyCode || e.charCode
// magic numbers are backspace and delete. Naming them didn't work.
if (key == 8 || key == 46) {
setFilterText("")
filterTree()
}
}
I am using a JS class, I have following code:
class Field {
public Value = null;
public Items = [];
public UniqueKey = null;
public getItems() {
let items = [...this.Items];
items = items.filter((item) => {
if (item.VisibleIf) {
const matched = item.VisibleIf.match(/\$\[input:(.*?)\]/g);
if (matched?.length) {
const srv = Service.getInstance();
for (let match of matched) {
match = match.slice(8, -1);
if (srv.Fields?.length) {
let found = srv.Fields.find((x) => x.UniqueKey === match);
if (found) {
item.VisibleIf = item.VisibleIf.replace(
`$[input:${match}]`,
found.Value ?? ''
);
return JSON.parse('' + eval(item.VisibleIf));
}
}
}
}
}
return true;
});
return items;
}
public getInputTitle() {
let title = this.Title;
const matched = title.match(/\$\[input:(.*?)\]/g);
if (matched?.length && title) {
const srv = Service.getInstance();
for (let match of matched) {
match = match.slice(8, -1);
if (srv.Fields?.length) {
let found = srv.Fields.find((x) => x.UniqueKey === match);
if (found) {
title = title.replace(`$[input:${match}]`, found.Value ?? '');
}
}
}
}
return title;
}
}
Now I have a Vue component:
<div v-for="Field in Fields" :key="Field.UniqueKey">
<v-select
v-if="Field.Type == 'Select'"
:label="Field.getInputTitle()"
v-model="Field.Value"
:items="Field.getItems()"
item-text="Value"
item-value="Id"
/>
<v-input
v-else-if="Field.Type == 'Input'"
v-model="Field.Value"
:label="Field.getInputTitle()"
/>
</div>
// JS
const srv = Service.getInstance();
Fields = srv.getFields(); // <- API call will be there.
So basically, data comes from an API, having Title as Input $[input:uniqueKey], in a component I am looping over the data and generating the fields. See getInputTitle function in Field class, it works very well. All the fields which are dependent on the $[input:uniqueKey] are changing when I start typing into that field on which other fields are dependent.
Now I have pretty much same concept in the getItems function, so basically, what I want to do is whenever I type into a field and that field exists in the VisibleIf on the Items, the VisibleIf will be like '$[input:uniqueKey] < 1', or any other valid JavaScript expression which can be solved by eval function. But the getItems function is called only 1st time when page gets loaded, on the other hand the getInputTitle function which is pretty much same, gets called every time when I type into the field.
I tried to explain at my best, I will provide any necessary information if needed.
Any solution will be appreciated. Thanks.
You are updating the Object itself in here:
item.VisibleIf = item.VisibleIf.replace( `$[input:${match}]`, found.Value ?? '' );
Even though you tried to copy the array, but you have done shallow copy of the object in here: let items = [...this.Config.Items];
I suggest the following solution:
const visibleIf = item.VisibleIf.replace(
`$[input:${match}]`,
found.Value ?? ''
);
const val = '' + helpers.evalExp('' + visibleIf);
if (helpers.isJSON(val)) {
return JSON.parse(val);
}
Means instead of changing the VisibleIf object, just store it into the variable and just use that.
I hope that it will fix your issue. Let me know if it works.
I have an API response that returns a list of records each with a 'status' attribute. The status can be 'current', 'former', 'never'.
I have a set of 3 checkboxes that each carry a value for the same that a user would click to filter the list of records accordingly.
The way I'm trying to achieve the filtering functionality is by using a hook for const [statuses, setStatuses] = useState<string[]>([]);
And then populating that array with the value of each checkbox from:
<div>FILTER BY STATUS</div>
<FilterSection>
<span><input type="checkbox" value="Current" onClick={handleStatusChange}/> Current</span>
<span><input type="checkbox" value="Former" onClick={handleStatusChange}/> Former</span>
<span><input type="checkbox" value="Never" onClick={handleStatusChange}/> Never </span>
</FilterSection>
</div>
I then have the onClick method that calls handleStatusChange:
const handleStatusChange = e => {
setStatuses([...statuses, e.target.value]);
props.onFilterChange(statuses, state)
console.log('status value: ', e.target.value)
};
Which passes it's values up to the container component and feeds into the filter function which looks like:
const handleFilterChange = (status: string[], state: string) => {
store.set('currentPage')(0);
const allLocations = store.get('locations');
let filteredLocations = allLocations.slice();
const pageSize = store.get('pageSize');
if (status && status.length > 0) {
filteredLocations = filteredLocations
.filter(l => {
l.financialDetails && l.financialDetails.locationStatus === status;
})
.slice();
}
store.set('filteredLocations')(filteredLocations);
const pagedLocations = filteredLocations.slice(0, pageSize);
store.set('pagedLocations')(pagedLocations);
store.set('locationsLoading')(false);
};
The problem I'm seeing is that I get a TypeScript error inside handleFilterChange saying This condition will always return 'false' since the types 'string' and 'string[]' have no overlap.
EDIT
I've updated the handleStatusChange function and it's populating the array but only on the second click of a checkbox. So on first click it produces an empty array, on second click it grabs whatever the first clicked value was and pushes it into the array so that it's always one click behind what a user actually selected.
I think something like this would work.
const handleStatusChange = e => {
const status = e.target.value;
if (!statuses.includes(status)) {
setStatuses(statuses.concat(status));
}
else {
const statusIndex = statuses.findIndex(status);
statuses.splice(statusIndex, 1);
setStatuses(statuses);
}
props.onFilterChange(statuses)
};
And for the handleFilterChange...
const handleFilterChange = (statuses: string[]) => {
const allLocations = store.get('locations');
let filteredLocations = allLocations.slice();
if (statuses && statuses.length) {
statuses.forEach((status) => {
filteredLocations = filteredLocations
.filter(l => (l.financialDetails && l.financialDetails.locationStatus !== status));
}
const pagedLocations = filteredLocations.slice(0, pageSize);
};
I removed some of the code I thought was irrelevant to the problem.
const handleStatusChange = e => {
const updatedStatus = [...statuses, e.target.value];
props.onFilterChange(updatedStatus, state)
setStatuses(updatedStatus);
console.log('status value: ', e.target.value)
};
you always should call parent (props function) to pass state to parent because when you call setStatuses at that time statuses array is empty so call that
props.onFilterChange
first before setting state.
or in functional component you can use useEffect like this
useEffect(()=>{
props.onFilterChange(statuses, state);
},[statuses]);
I am new to react js. I am creating a comparison between user typing and actual sentence to be typed Somehow I am able to achieve this but It is not perfect like nested map is not rendering properly if letter typed correctly it should render green background My state is updated properly But my nested map Kinda not working there is a delay
Component Code
renderLine = () => {
let test = this.props.test.get('master')
return test.map(line => {
return line.check.map( (ltr,i) => ltr.status ? <span key={i} className="correct">{ltr.letter}</span> : ltr.letter )
})
};
handleKeyPress = e => {
if(e.charCode === 32) {
this.setState({
pushToNext:true,
currentTyping:""
})
}
};
handleInput = e => {
if(e.target.value !== " "){
let {storeValue} = this.state;
console.log(storeValue.length);
let updatedWord = e.target.value;
let updateArr = [];
if(storeValue.length === 0){
updateArr = storeValue.concat(updatedWord)
}else {
if(this.state.pushToNext){
updateArr = storeValue.concat(updatedWord)
}else {
storeValue.pop();
updateArr = storeValue.concat(updatedWord);
}
}
this.setState({
currentTyping:updatedWord,
storeValue:updateArr,
pushToNext:false
},() => {
let {storeValue} = this.state
let lastWordIndex = storeValue.length === 0 ? storeValue.length : storeValue.length - 1;
let lastLetterIndex = storeValue[lastWordIndex].length === 0 ? storeValue[lastWordIndex].length : storeValue[lastWordIndex].length - 1;
let lastWordValue = storeValue[lastWordIndex];
let lastLetterValue = lastWordValue[lastLetterIndex];
// console.log(lastWordIndex,lastLetterIndex,lastWordValue,lastLetterValue,"After tstae")
return this.props.compareCurrentTextWithMater(lastWordIndex,lastLetterIndex,lastWordValue,lastLetterValue)
});
}
};
Redux Reducer
import {FETCH_USER_TYPING_TEXT,COMPARE_TEXT_WITH_MASTER} from "../actions/types";
import {fromJS} from 'immutable';
const initialState = fromJS({
text:null,
master:[],
inputBoxStatus:false
});
export default function (state = initialState,action) {
switch (action.type){
case FETCH_USER_TYPING_TEXT:
return setTextManipulated(state,action);
case COMPARE_TEXT_WITH_MASTER:
return compareTextWithMaster(state,action)
default:
return state
}
}
const compareTextWithMaster = (state,action) => {
let {lastWordIndex,lastLetterIndex,lastLetterValue} = action;
let masterWord = state.get('master')[lastWordIndex];
let masterLetter = masterWord.check[lastLetterIndex];
let newState = state.get('master');
if(typeof masterLetter !== "undefined"){
if(masterLetter.letter === lastLetterValue){
masterWord.check[lastLetterIndex].status = true;
newState[lastWordIndex] = masterWord;
return state.set('master',newState)
}else {
masterWord.check[lastLetterIndex].status = false;
newState[lastWordIndex] = masterWord;
return state.set('master',newState)
}
}else {
console.log('Undefinedd Set Eroing or wrong Space Chratced set Box Red Colot',newState);
}
};
UPDATE
I did the same Logic with plain React.js it works Perfectly and nested map rendering the if else logic properly there is no on letter delay
https://codesandbox.io/s/zx3jkxk8o4
But the same logic with Redux State with immutable js Does'nt take effect with nested loop if else statement I don't know where the problem Relies ..and My Code Snippet will be little bit different from CodeSanbox COde But the Logic is Same
Probably, the diffing algorithm of react does see that oldState === newState and skips the re rendering. To avoid that situation, use a new object in the root of the state so that the above check returns false. I see that you use immutableJs, so maybe force re-render with componentShouldUpdate method instead.
Also consider using dev tools to step through the code line by line to see what is going on.
If nothing at all works, switch to something simpler with less dependencies and go from there, incrementally adding what you need.
From official docs I know about 2 methods: get entity by its key and get last created entity. In my case, I also need a method to access all entities from current ContentState.
Is there any method that could perform this? If not, is there a one that can provide all entities keys?
const getEntities = (editorState, entityType = null) => {
const content = editorState.getCurrentContent();
const entities = [];
content.getBlocksAsArray().forEach((block) => {
let selectedEntity = null;
block.findEntityRanges(
(character) => {
if (character.getEntity() !== null) {
const entity = content.getEntity(character.getEntity());
if (!entityType || (entityType && entity.getType() === entityType)) {
selectedEntity = {
entityKey: character.getEntity(),
blockKey: block.getKey(),
entity: content.getEntity(character.getEntity()),
};
return true;
}
}
return false;
},
(start, end) => {
entities.push({...selectedEntity, start, end});
});
});
return entities;
};
How I get the all entities keys:
const contentState = editorState.getCurrentContent()
const entityKeys = Object.keys(convertToRaw(contentState).entityMap)
result:
[0, 1]
then you can call the getEntity(key) method to get the responding entity.
this is how convertToRaw(contentState) looks:
Bao, You will find it inside key called 'blocks'.
convertToRaw(contentState).blocks.map(el=>el.text)
It will give you an array of raw text.
Unfortunatelly your suggested way using convertToRaw doesnt work because it reindexes all keys to ["0", .., "n"], but the real keys differ when you act with the editor. New ones > n will be added and unused will be omitted.
const rawState = convertToRaw(contentState)
const { entityMap } = rawState;
This entityMap will have list of all entities. But this is an expensive conversion. Because, it will convert whole thing to raw. A better way is loop through blocks and check for entity.
You'll have to look at every character:
const { editorState } = this.state; // assumes you store `editorState` on `state`
const contentState = editorState.getCurrentContent();
let entities = [];
contentState.getBlockMap().forEach(block => { // could also use .map() instead
block.findEntityRanges(character => {
const charEntity = character.getEntity();
if (charEntity) { // could be `null`
const contentEntity = contentState.getEntity(charEntity);
entities.push(contentEntity);
}
});
});
Then you could access it via:
entities.forEach((entity, i) => {
if (entity.get('type') === 'ANNOTATION') {
const data = entity.get('data');
// do something
}
})