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.
Related
In my React app I am showing a banner yes or no, based on React state and some values set in localStorage.
After close button is clicked, it's state showBanner is saved to localStorage and doesn't show the banner anymore
After 2 times using a page url in the React app with query param redirect=my-site it doesn't show the banner anymore:
import queryString from 'query-string';
const location = useLocation();
const queryParams = queryString.parse(location.search);
const [showBanner, setShowBanner] = useState(true);
const handleClick = () => {
setShowBanner(false);
localStorage.removeItem('redirect');
};
const hasQp = queryString
.stringify(queryParams)
.includes('redirect=my-site');
const initialCount = () => {
if (typeof window !== 'undefined' && hasQp) {
return Number(localStorage.getItem('redirect')) || 0;
}
return null;
};
const [count, setCount] = useState(initialCount);
const show = showBanner && hasQp && count! < 3;
useEffect(() => {
const data = localStorage.getItem('my-banner');
if (data !== null) {
setShowBanner(JSON.parse(data));
}
}, []);
useEffect(() => {
localStorage.setItem('my-banner', JSON.stringify(showBanner));
}, [showBanner]);
useEffect(() => {
let pageView = count;
if (pageView === 0) {
pageView = 1;
} else {
pageView = Number(pageView) + 1;
}
if (hasQp && showBanner === true) {
localStorage.setItem('redirect', String(pageView));
setCount(pageView);
}
}, []);
This is working fine (when you see some good code improvements let me know :) ).
But as soon the user clicks the close button I don't want the localStorage item redirect no longer appears. Now after refreshing the page it appears again.
How do i get this to work?
If this is executing when the page loads:
localStorage.setItem('redirect', String(pageView));
Then that means this is true:
if (hasQp && showBanner === true)
The hasQp value is true, which means this is true:
const hasQp = queryString
.stringify(queryParams)
.includes('redirect=my-site');
And showBanner is true because it's always initialized to true:
const [showBanner, setShowBanner] = useState(true);
It's not 100% clear to me why you need this state value, but you could try initializing it to false by default:
const [showBanner, setShowBanner] = useState(false);
But more to the point, I don't think you need this state value at all. It's basically a duplicate of data that's in localStorage. But since both state and localStorage are always available to you, I don't see a reason to duplicate data between them.
Remove that state value entirely and just use localStorage. An example of checking the value directly might be:
if (hasQp && JSON.parse(localStorage.getItem('my-banner')))
Which of course could be refactored to reduce code. For example, consider a helper function to get the value:
const showBanner = () => {
const data = localStorage.getItem('my-banner') ?? false;
if (data) {
return JSON.parse(data);
}
};
Then the check could be:
if (hasQp && showBanner())
There are likely a variety of ways to refactor the code, but overall the point is to not duplicate data. In thie case a value is being stored in localStorage instead of React state because it needs to persist across page loads. Just keep that value in localStorage and use it from there.
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 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);
}
}
}
I have code as below.
I need to break the loop when first match is found.
const [isCodeValid, setIsCodeValid] = useState(false);
for (let i = 0; i < properyIds.length; i++) {
if (isCodeValid) {
break; // this breaks it but had to click twice so state would update
}
if (!isCodeValid) {
firestore().collection(`properties`)
.doc(`${properyIds[i]}`)
.collection('companies').get()
.then(companies => {
companies.forEach(company => {
if (_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())) {
console.log("should break here")
// updating state like this wont take effect right away
// it shows true on second time click. so user need to click twice right now.
setIsCodeValid(true);
}
});
})
}
}
state won't update right away so if (!isCodeValid) only works on second click.
Once I find match I need to update state or variable so I can break the for loop.
I tried to use a variable but its value also not changing in final if condition, I wonder what is the reason? can anyone please explain ?
You should try and rewrite your code such that you will always call setIsCodeValid(value) once. In your case it could be called multiple times and it might not get called at all
const [isCodeValid, setIsCodeValid] = useState(false);
function checkForValidCode() {
// map to an array of promises for companies[]
const companiesPromises = properyIds.map(propertyId =>
firestore()
.collection(`properties`)
.doc(propertyId)
.collection('companies').get())
Promise.all(companiesPromises)
// flatten the 2d array to single array, re-create to JS array because of firestores internal types?
.then(companiesArray => [...companiesArray].flatMap(v => v))
// go through all companies to find a match
.then(companies =>
companies.find(
company => _.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())
))
.then(foundCompany => {
// code is valid if we found a matching company
setIsCodeValue(foundCompany !== undefined)
})
}
Try something like this:
import { useState } from 'react';
function YourComponent({ properyIds }) {
const [isCodeValid, setIsCodeValid] = useState(false);
async function handleSignupClick() {
if (isCodeValid) {
return;
}
for (let i = 0; i < properyIds.length; i++) {
const companies = await firestore()
.collection(`properties`)
.doc(`${properyIds[i]}`)
.collection('companies')
.get();
for (const company of companies.docs) {
if (_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())) {
setIsCodeValid(true);
return;
}
}
}
}
return (<button onClick={handleSignupClick}>Sign Up</button>);
}
If you await these checks, that will allow you to sequentially loop and break out with a simple return, something you can't do inside of a callback. Note that if this is doing database queries, you should probably show waiting feedback while this is taking place so the user knows that clicking did something.
Update:
You may want to do all these checks in parallel if feasible so the user doesn't have to wait. Depends on your situation. Here's how you'd do that.
async function handleSignupClick() {
if (isCodeValid) {
return;
}
const allCompanies = await Promise.all(
properyIds.map(id => firestore()
.collection(`properties`)
.doc(`${properyIds[i]}`)
.collection('companies')
.get()
)
);
setIsCodeValid(
allCompanies.some(companiesSnapshot =>
companiesSnapshot.docs.some(company =>
_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())
)
)
);
}
Can you not break it after setIsCodeValid(true);?
Use some:
companies.some(company => {
return _.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase());
});
If some and forEach are not available then companies is not an array but an array-like object. To iterate through those, we can use for of loop:
for (const company of companies){
if (_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())) {
// do something
break;
}
}
I tired below and it worked for me to break the loop.
I declared and tried to change this variable let codeValid and it was just not updating its value when match found. (not sure why)
But all of a sudden I tried and it just works.
I didnt change any actual code except for variable.
let codeValid = false;
let userInformation = []
for (let i = 0; i < properties.length; i++) {
console.log("called")
const companies = await firestore().collection(`properties`)
.doc(`${properties[i].id}`)
.collection('companies').get()
.then(companies => {
companies.forEach(company => {
if (_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())) {
// a += 1;
codeValid = true;
userInformation.registrationCode = registrationCode.toUpperCase();
userInformation.companyName = company.data().companyName;
userInformation.propertyName = properties[i].propertyName;
}
});
})
if (codeValid) {
break;
}
}