Store values from multiple select in react - javascript

I have a variable with the number of select items, this number depends on some API, and I want to store the selection of each question into localstorage so each time the page reloads the selection stays remained, I don't know how to do it.
{ Object.entries(this.state.liste).map( ([ke,values]) => {
let question=ke
return (
<div name = "question" >
<h6 className="left">{ke}</h6>
< select className="right" key={values} name = "answer to each question" onChange={(event) =>
this.handleChangeaa(question,event)} >
<option value={this.state.selected} selected disabled hidden onChange={(value)=>
console.log(value)}> Not answered </option>
{Object.values(values).map(key=>{
return (<option key={key.id} id={key}>{key}</option>) })}
</select>
</div>
) })}

In localStorage, you can only set a value as a string. You simply have to insert in the localStorage some pairs of key/values for each questions that have a selected option. In this case, the key will be the id of your question and the value the value of your selected option.
In your handleChangeaa callBack, you will have something like this :
handleChangeaa(question, event) {
// Check before if the localStorage is available
localStorage.setItem(question.id, event.target.value);
}
Then when your form component will be instantiated, you will simply have to retrieve this information from your localeStorage with the localStorage.getItem function :
componentDidMount() {
// Check if localStorage is available
const stateListCpy = {...this.state.liste}
["question1", "question2"].forEach((questionId) => {
const questionValue = localStorage.getItem(questionId);
if (questionValue) {
const questionIndex = stateListCpy.findIndex((questionBlock) => questionBlock.ke === questionId )
if (questionIndex !== -1) {
stateListCpy[questionIndex].values = questionValue;
}
}
})
this.setState({
liste = stateListCpy;
})
}
And with that you will have your selects initialized with the right values from your localStorage.
Good luck !

Related

Angular ngbTypeahead filter of list

I've got a ngbTypeahead which when typing in the field should be able to get a list of strings.
<ng-template #rt let-r="result" let-t="term">
<a>
<span class="ml-1">
<ngb-highlight [result]="r" [term]="t"></ngb-highlight>
</span>
</a>
</ng-template>
<div class="form-group row mb-1">
<label for="libelle" class="col-md-3 text-right pr-0">Employeur</label>
<div class="col-md-3">
<input class="form-control ml-1" id="libelle" name="libelle"
[(ngModel)]="libelle"
placeholder="Employeur"
[ngbTypeahead]="search"
(selectItem)="selectItemLibelle($event)"
[inputFormatter]="formatMatches" [resultTemplate]="rt"
(focus)="focus$.next($event.target.value)"
>
</div>
</div>
the typscript code :
search = (text$: Observable<string>) => {
const debouncedText$ = text$.pipe(debounceTime(200), distinctUntilChanged() );
const inputFocus$ = this.focus$;
return merge(debouncedText$, inputFocus$).pipe(
mergeMap((term) => {
return this.getEmp(term).pipe(map((emp: any) => {
return ((!term || false || term === '') ? emp
: emp.filter(v => v.toLowerCase().indexOf(term.toLowerCase()) > -1));
}));
})
);
}
selectItemLibelle($event) {
this.libelle = $event.item;
$event.preventDefault();
}
formatMatches = (value: any) => value || '';
the method getEmp, get the list of strings :
getEmp(term): Observable<any[]> {
this.fe2ModeleService.getEmployeurs(term).subscribe((employeurs) => {this.listEmp = employeurs;});
return of(this.listEmp);}
everything works fine, when I search with a character or a phrase I get the list , my issue is I want when I click on the input (when the input is empty) I want to have the complete list.
the problem is when I come the first time in the page or I refresh the page, and I click on the input nothing is displayed, but when I click elsewhere (on another input or on anywhere on the page) and I come back and click on the input, and there I have the list displayed.
how can I have this behavior from the first time I come to the page
get the complete list when I click on the input
I used combineLatest instead of mergeMap:
search: OperatorFunction<string, readonly PostCategory[]> = (text$: Observable<string>) => {
const debouncedText$ = text$.pipe(debounceTime(200), distinctUntilChanged());
const clicksWithClosedPopup$ = this.click$.pipe(filter(() => !this.instance.isPopupOpen()));
const operations$ = merge(debouncedText$, clicksWithClosedPopup$, this.focus$, this.clear$);
return combineLatest([
operations$,
this.categories$
]).pipe(
map(([text, categories]) =>
text === '' ? categories : categories.filter(c => new RegExp(`^${text}`, 'i').test(c.name)))
);
}
You can find the working code here: https://github.com/DeborahK/Angular-posts

Increment and decrement counter according to select or unselect tag

I am working with the next js to handle a question form, I have multiple questions that have multiple answers, and the user may select one or multiple tags for a question.
if a user selects one or more tag to answer a question it must increment the counter just one time, If a user unselect a tag it must not decrement until unselect the last tag of the related question.
1 - When the user selects a tag of a question at first increment the counter, but it must not increment when I select the second tag in the same question.
2 - When the user unselects tags of question it must not decrement if any one of them is selected it must not decrement until the last one is unselected.
Now when selecting the first tag it increments the counter but after that, if I select or unselect, it does not decrement or increment.
Please help me to handle this.
My code:
<TageQuestion
key={item.attributes.Number}
id={item.attributes.Number}
data={item}
name={`${item.attributes.Number}`}
errors={errors}
touched={touched}
handleSelectOption={handleSelectOption}
/>
The component:
<div className="mt-3">
{Object.keys(attributes.options).map(item => (
<div className="inline" key={item}>
<input
{...feild}
{...props}
className="hidden"
id={item}
value={item}
type="checkbox"
name={`${name}[answer]`}
/>
<label
htmlFor={item}
id={item + "lable"}
onClick={e => {
selectTage(e.target);
handleSelectOption(`question${id}`, item);
}}
className="inline-flex items-center px-3 py-0.5 rounded-full text-sm font-medium bg-gray-100 text-gray-800 mr-3 mb-2 cursor-pointer"
>
{item}
</label>
</div>
))}
</div>
The function:
function handleSelectOption(title, item) {
setCompareTagTitle(title);
if (title !== compareTagTitle) {
setTagItem([...tagItem, item]);
setCounter(++counter);
} else if (title === compareTagTitle) {
setCounter(counter);
} else {
setCounter(--counter);
}
}
Data that come from the backend:
we should consider 2 steps first in child component TagQuestions then in parent component [type].js
step1:
//TagQuestions.js
const [intialRender, setIntialRender] = useState(true);
// if component render for first time
const selectTag = (e) => {
// we should change intialRender to false to say that user select somthing
....
if (tage.length > 0) {
const isExit = tage.filter((item) => item.name === el.innerHTML);
if (isExit.length === 0) {
setTage([...tage, { name: el.innerHTML }]);
if (intialRender) {
setIntialRender(false);
}
}
} else {
setTage([...tage, { name: el.innerHTML }]);
if (intialRender) {
setIntialRender(false);
}
}
....
}
//use effect function to if user unselect all tags after selctions
useEffect(() => {
if (tage.length === 0 && !intialRender) {
//call parent function and set empty param to true.
handleSelectOption(`question${id}`, "", true);
}
}, [tage]);
step2(parent component):
const [compareTagTitle, setCompareTagTitle] = useState([]);
function handleSelectOption(title, item, empty = false) {
if (empty) {
const filter = compareTagTitle.filter(
(value) => value.toString() !== title.toString()
)
setCompareTagTitle(filter);
setCounter(--counter);
} else {
if (!compareTagTitle.includes(title)) {
setCompareTagTitle([...compareTagTitle, title]);
setCounter(++counter);
}
}
}

vue 3 radio button revert back if user canceled

I am having some issues with some radio buttons in vue 3
From the parent component I am passing an object to the child where is the data displayed and where I want to make one version default:
<template>
<table>
...
<tr v-for="(vf, index) in version.files" :key="vf.id">
...
<td>
<input type="radio" name="defaultVersion" v-model="vf.default" :checked="vf.default" #change="onChangeDefault(index)" #click="onClickDefault">
</td>
</tr>
</table>
</template>
props: {
version: Object // the prop object received
},
setup(props) {
const {version} = toRefs(props) // version should be now reactive
// use this variable to know where is the index that contains the default value
let defaultVersionIndex = 0
const onClickDefault = () => {
// check on click what is the current version default and store the index
for(let i = 0; i < version.value.files.length; i++) {
if(version.value.files[i].default) {
defaultVersionIndex = i
}
}
}
const onChangeDefault = (index) => {
let text = "Change default?\nEither OK or Cancel.";
if (confirm(text) == true) {
// user press ok => make API call
} else {
// if the user canceled => set the default radio checked (previously)
version.value.files[index].default = false // change back from what user has selected
version.value.files[defaultVersionIndex].default = true // set back the original radio checked (previously)
}
}
return {
version,
onClickDefault,
onChangeDefault
}
}
When click on a radio (unchecked) it changes visually - but in the version.files no changes ... what I am missing ???
You get the idea .... when the user cancel the action to revert back the radio button. I choose radio because only one can be default.
UPDATE 1
make some changes on input by binding the value (to make sure the value is boolean not on, off)
<input type="radio" :value="vf.default" name="defaultVersion" v-model="vf.default" :checked="vf.default" #change="onChangeDefault(index)" #click="onClickDefault">
and on canceled action :
version.value.files[index].default = !version.value.files[index].default
version.value.files[defaultVersionIndex].default = !version.value.files[defaultVersionIndex].default
the values in the object version.files now changes but visually not working like it should...
Solved.
On input:
<input type="radio" :value="vf.id" name="defaultVersion" v-model="defaultValue" :checked="vf.id === defaultValue" #change="onChangeDefault(vf.id)" #click="onClickDefault">
Created a vmodel that hold the id for the default value
const defaultValue = ref(0)
// store the previous default value (id)
let previousDefault = 0
const onClickDefault = () => {
previousDefault = defaultValue.value // this is the magic :)
}
the when the user canceled:
defaultValue.value = previousDefault

react hooks: input onChange first character can not be deleted

I got some inputs to create/update a formular,
1) if value={question}, everything works except that I can not delete the last character (= first character of the input)
2) if I dont mention value, it's all good except when I want to change questions orders with Chevron icon button, database is well changed but input value is still displayed at the last place.
<input
type="text"
value={question}
placeholder="blabla"
onChange={event => {
event.preventDefault();
const value = event.target.value;
setUpdatedQuestion(value);
}}
/>
I tried to add if (event.target.value == "" || event.target.value) to onChange but does not work either
OK I found something, but it is a McGyver tip, not very clean : adding one space before all new add question ahah. But then I can't see my placeholder anymore :/
FYI : question is coming from a questions.map, setUpdatedQuestion do update questions, newOrder also do update questions
//in QuestionsContent.js (questions is a questions tab)
const QuestionsContent = props => {
return (
<form onSubmit={onSubmit}>
{isLoading === false && questions.length > 0
? questions.map((question, i) => {
return (
<div key={i}>
<QuestionLine
{...question}
questions={questions}
setQuestions={setQuestions}
setNewOrder={setNewOrder}
/>
</div>
);
})
: null}
<button
onClick={event => {
event.preventDefault();
setAddQuestion({
question: null,
type: "texte"
});
}}
>
Add a question
</button>
</form>
);
};
//in QuestionLine.js:
const QuestionLine = ({
question,
setNewOrder,
setQuestions,
questions
}) => {
const [updatedQuestion, setUpdatedQuestion] = useState("");
// * UPDATE QUESTION *******************************
useEffect(() => {
if (updatedQuestion !== "") {
const tab = [];
for (let j = 0; j < questions.length; j++) {
if (j === i) {
const newObject = {
question: updatedQuestion,
type: type
};
console.log("adding updatedQuestion ===>", newObject);
tab.push(newObject);
} else {
tab.push(questions[j]);
}
}
setQuestions(tab);
setUpdatedQuestion("");
}
}, [updatedQuestion]);
return (
<div >
{/* QUESTION */}
<input
type="text"
value={question}
placeholder="blabla"
onChange={event => {
event.preventDefault();
const value = event.target.value;
setUpdatedQuestion(value);
}}
/>
</div>
);
};
thanks for your precious help
The problem probably comes from the condition in the useEffect : when you want to delete the last character of the string, the state of updatedQuestion is empty and therefore the condition is not executed

How to make a function that can be edited and saved

In this situation when I add a todo item to the list and try to edit the added todo, it will only add one new letter and typing the next letter it will only replace the latest letter all the time. For instance, the word is hello, no matter how many letters you type there will be only one letter added (for instance hellop, hellow, hellol, hellop). Check the console.log to understand the problem.
link to snippet
const addTodo: AddTodo = newTodo => {
newTodo.trim() !== "" && setTodos([...todos, { text: newTodo, complete: false, edit: false }]);
}
const getEditText: GetEditText = getEditedTodo => {
console.log('getEditText ' + getEditedTodo);
}
const saveEditedTodo: SaveEditedTodo = currentTodo => {
console.log('saveEditedTodo ' + JSON.stringify(currentTodo.text));
}
return (
<div>
<input type="input" onChange={(e) => getEditText(e.target.value)} value={todo.text} />
<span onClick={() => saveEditedTodo(todo)}>Save</span></div> :
<li>
)
You are not assigning the new value to variable todo.next
You need to update the variable to which value is binded for input field. So please try to create a fiddler for these kind of questions

Categories

Resources