How to not allow the user to not add the same language? - reactjs

What I'm trying to do is to not allow the user to add two same languages.
The user has the possibility to click the button, that generates the input fields, and puts the language.
export default class AddLanguage extends React.Component {
constructor(props) {
super(props);
this.state = {
language: [],
};
}
handleClick() {
var language = this.state.language;
this.setState({
language: language,
});
}
handleLanguageChanged(i, event) {
var language= this.state.language;
language[i] = event.target.value;
this.setState({
language: language,
});
sessionStorage.setItem('items', JSON.stringify({language}));
}
handleLanguageDeleted(i) {
var language= this.state.language;
language.splice(i, 1);
this.setState({
language: language,
});
}
componentDidMount() {
this.userItems = JSON.parse(sessionStorage.getItem('items'));
if (sessionStorage.getItem('items')){
this.setState({
items: this.userItems.items,
})
}else {
this.setState({
items: [],
})
}
}
renderRows() {
var context = this;
How to not allow the user to not add the same language?

Assuming the new language entered by the user is processed using handleLanguageChanged and that it is added/updated at index i (with value being sourced from event.target.value) the below may be helpful to achieve the desired objective:
handleLanguageChanged(i, event) {
// hold the event.target.value for future reference
const updLang = event.target.value;
// use setState with a function
this.setState(prev => {
const currList = prev.language;
// check if currList has updLang except at location "i"
const isDupe = currList.some(
(lang, idx) => (idx !== i && lang.toLowerCase() === updLang)
);
// if it is dupe, simply return "prev" (no actual update)
if (isDupe) return prev;
else {
// update the currList & use it to set the new state
currList[i] = updLang;
return {...prev, language: currList};
}
});
};
Also, kindly reconsider if the below function is necessary as-is:
handleClick() {
var language = this.state.language;
this.setState({
language: language,
});
}

Related

How to stop the state variable to be overwritten from previous value in reactjs?

i am storing the data got from server in a state variable. It works fine if i open items having large data display correct information. however after opening item with no data available for it displays previous item value.
consider the scenario,
item 1 has no data, item2 has large data.
open item1 displays no data for it.
now open item2 displays data for it (which is large)
now open item1 displays item2 data instead of showing no data.
Not sure where i am going wrong.
Below is the code,
class ItemDetails extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
item_info: null,
item_info_loading: false,
};
componentDidMount() {
this.unmount = new Promise((resolve) => { this.on_unmount =
resolve;});
this.load_item_info();
this.unlisten_path_change = this.props.history.listen((location) =>
{this.handle_path_change(location.pathname);});
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.item_id !== this.props.item_id) {
this.setState({item_info: null}, this.load_item_info);}
}
componentWillReceiveProps(nextProps, nextState) {
if(nextProps.item_id !== this.props.item_id) {
this.setState({item_info: null}, this.load_item_info);
}}
componentWillUnmount() {
this.on_unmount();
this.unlisten_path_change();
}
load_item_info = () => {
const file_name = 'item_info.json';
this.setState({item_info_loading: true});
client.get_item_file(this.props.model_id, file_name, 'json',
this.unmount).finally(() => this.setState({item_info_loading: false}))
.then((request) => {
this.setState({item_info: request.response})
})};
render () {
<ItemInfoTool
item_info={state.item_info}
item_info_loading={this.state.item_info_loading}/>}}
export default class ItemInfoTool extends React.Component {
state = {
open_item_data: null,};
componentDidMount() {
this.set_open_item_data();
}
componentDidUpdate(prevProps) {
if (prevProps.selected_id !== this.props.selected_id) {
this.set_open_item_data();
}
}
set_open_item_data = () => {
if (!this.props.item_info) {
return;
}
if (this.props.selected_id === this.empty_id) {
this.setState({open_item_data: null});
return;
}
let open_item_data = {
info: [],
values: [],
};
const item_info = this.props.item_info;
for (let i=0, ii=item_info.length; i < ii; i++) {
if (item_info[i].somekey.includes(this.props.selected_id)) {
const info = item_info[i].info;
const values = object_info[i].values;
open_item_data = {
info: typeof info === 'string' ? info.split('\r\n') : [],
values: values ? values : [],
};
break;
}
}
this.setState({open_item_data:open_item_data);
};}
export function get_item_file(item_id, file_name, response_type,
on_progress, cancel) {
const local_override_defined = item_files[item_id] &&
item_files[item_id][file_name];
if (local_override_defined) {
const file = item_files[item_id][file_name];
const reader = new FileReader();
return new Promise(resolve => {
if (response_type === 'blob') {
resolve({response: file});
} else {
reader.onload = () => {
if (response_type === 'json') {
resolve({response: JSON.parse(reader.result)});
} else {
resolve({response: reader.result});
}};
reader.readAsText(file);}});}
return new Promise((resolve, reject) => {
item_file_get_url(item_id, file_name).then(({response}) => {
const request = new XMLHttpRequest();
request.addEventListener('progress', on_progress);
request.open('GET', response.url);
request.responseType = response_type;
send_request(request, undefined, cancel,
response.url).then(resolve).catch(reject);})});}
Could someone help me solve it. Thanks. i doubt there is some asynchronous requests happening.
You need to clear data while closing operation

Cannot see firebase data in application - react native

I have set up a call to fetch data from my firebase database using react native.
Database structure
Code inside FirebaseList.js
constructor(props) {
super(props);
this.state = {
data: []
};
}
componentWillMount() {
firebase.database().ref('/signposts/items').on('value', snapshot => {
const dataArray = [];
const result = snapshot.val();
for (const data in result) {
dataArray.push(data);
}
this.setState({ data: dataArray });
console.log(this.state.data);
});
}
render() {
return (
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<Text>{item}</Text>
)}
keyExtractor={item => item}
/>
);
}
I believe the connection to firebase is successful as I can build and run the application. However, when the component renders, I do not see my two rows of data 'row1' and 'row2'.
You said your code is right then also check that this rules are set
{
"rules": {
"foo": {
".read": true,
".write": false
}
}
}
Note : - When you use the above rule your database is open for all Read more here. Make you use update the rules once you push to production.
If console.log(dataArray) shows an empty array (assuming that console.log() works...), try checking your connection:
componentDidMount() {
const ref = firebase.database().ref('/signposts');
const checkConnection = firebase.database().ref(`.info/connected`);
checkConnection.on('value', function(snapshot) {
if (snapshot.val() === true) { /* we're connected! */
firebase.database().ref('/signposts').on('value', snapshot => {
const dataArray = [];
const result = snapshot.val();
for (const data in result) {
dataArray.push(data);
}
if (dataArray.length === 0)
console.log("No data.")
else
this.setState({ listViewData: dataArray });
});
} else { /* we're disconnected! */
console.error("Check your internet connection.")
}
}
}

How do I setState of a deeply nested property in React?

I'm trying to increment the tally property of a given habit, in this context I'm targeting tally: 4. On a related note, I'm open to suggestions for better ways of structuring my data.
class App extends Component {
constructor(props){
super(props)
this.state = {
displayedHabit: "Exercise",
currentUser: "Achilles",
userList: [
{
user: "Achilles",
id: 0,
habits: [
{
habit: "Exercise",
tally: 123
},
{
habit: "Eat Vegetables",
tally: 4
},
]
}
]
}
}
Here is the implementation I've tried after searching for solutions. I don't believe it's working because assignments that use .find() that work after mounting are broken after I call the increment function through an event handler - leading me to believe .find() is no longer being called on an array.
increment = () => {
let newCount = this.state.userList[0].habits[1].tally + 1
this.setState({
userList: {
...this.state.userList[0].habits[1],
tally: newCount
}
})
}
In React, it's very important that you don't mutate your data structures. This essentially means that you have to copy the data structure at each level of your nested data. Since this is too much work, I suggest you use a library made for this purpose. I personally use object-path-immutable.
Example:
import {set} from 'object-path-immutable';
increment = ({userIndex, habitIndex}) => this.setState({
userList: set(this.state.userList, [userIndex, 'habits', habitIndex, 'tally'], this.state.userList[userIndex].habits[habitIndex].tally + 1)
});
I would recommend restructuring your data such that you have objects which actually map the data in accessible ways ie:
this.state = {
displayedHabit: "Exercise",
currentUser: "Achilles",
userList: {
"Achilles": { // Could also be id
id: 0,
habits: {
"Exercise": 123,
"EatVegetables": 4
}
}
}
}
This would allow you to do something like
increment = () => {
const {userList} = this.state;
this.setState({
userList: {
...userList,
Achilles: {
...userList.Achilles
habits: {
...userlist.Achilles.habits
'EatVegetables': userlist.Achilles.habits.EatVegetables + 1
}
}
}
})
}
This would be further simplified by using object-path-immutable which would allow you to do something simple like:
increment = () => {
const {userList} = this.state;
this.setState({
userList: immutable.set(userList, 'Achilles.id.habits.Exercise', userList.Achilles.id.habits.Exercise + 1)
})
}
In order to make this more generic though I would recommend doing what something similar to what earthling suggested:
import {set} from 'object-path-immutable';
incrementUserHabit = ({userIndex, habitIndex}) => this.setState({
userList: set(this.state.userList, [userIndex, 'habits', habitIndex, 'tally'], this.state.userList[userIndex].habits[habitIndex].tally + 1)
});
This way your code is more reusable
Something like this?
class App extends React.Component {
constructor() {
super();
this.state = {
userList: {
'12': {
name: 'Achilles',
habits: {
Exercise: {
tally: 123
},
'Eat Vegetables': {
tally: 231
}
}
}
}
}
}
inc() {
const {userList} = this.state;
userList['12'].habits['Exercise'].tally++;
this.setState({userList});
}
render() {
return (
<div>
<h2>{this.state.userList['12'].habits['Exercise'].tally}</h2>
<button onClick={() => this.inc()}>Increment</button>
</div>
)
}
};
Play with it here:
https://codesandbox.io/s/10w0o6vn0l

React state variable changes unpredictably

I'm trying to write a front-end using React for the first time.
I stumbled upon a problem trying to give users the option to order a list of React components and later undo the ordering.
So what I tried to do is save the initial list order in a separate state variable skills_initial. This is done by cloning the skills list. If the ordering gets undone, the skills_initial state variable is used to reset it to the initial order.
class Day extends React.Component {
constructor(props) {
super(props);
var skills = [];
var skill;
var xp;
for (var i in skill_names) {
skill = skill_names[i];
xp = this.props.end[skill] - this.props.start[skill];
skills.push(<Skill name={skill} xp={xp} key={skill}/>);
}
var skills_clone = [];
for (var k=0; k < skills.length; k++) {
skills_clone.push(skills[k]);
}
this.state = {skills: skills, skills_initial: skills_clone, descend: false, ascend: false};
this.descend = this.descend.bind(this);
this.ascend = this.ascend.bind(this);
}
descend() {
document.getElementById('ascend').checked = false;
if (this.state.descend) {
document.getElementById('descend').checked = false;
this.setState(prevState => ({
skills: prevState.skills_initial,
descend: false,
ascend: false
}));
} else {
this.setState(prevState => ({
skills: prevState.skills.sort(
function(skill1, skill2) {
return skill2.props.xp - skill1.props.xp;
}),
descend: true,
ascend: false
}));
}
}
ascend() {
document.getElementById('descend').checked = false;
if (this.state.ascend) {
document.getElementById('ascend').checked = false;
this.setState(prevState => ({
skills: prevState.skills_initial,
ascend: false,
descend: false,
}));
} else {
this.setState(prevState => ({
skills: prevState.skills.sort(
function(skill1, skill2) {
return skill1.props.xp - skill2.props.xp;
}),
ascend: true,
descend: false
}));
}
}
render() {
return (
<ol id="skill_list_vertical" style={{listStyleType: "none"}}>
<input id="descend" type="radio" onClick={this.descend}/>descend
<input id="ascend" type="radio" onClick={this.ascend}/>ascend
{this.state.skills}
</ol>
);
}
}
var skill_names = [
"attack",
"defence",
"strength",
"constitution",
"ranged",
"prayer",
"magic",
"cooking",
"woodcutting",
"fletching",
"fishing",
"firemaking",
"crafting",
"smithing",
"mining",
"herblore",
"agility",
"thieving",
"slayer",
"farming",
"runecrafting",
"hunting",
"construction",
"clue_easy",
"clue_medium",
"bounty_rogue",
"bounty_hunter",
"clue_hard",
"LMS",
"clue_elite",
"clue_master"
];
So the first two times the radio button is checked, the list is correctly ordered. Only upon trying to undo the ordering the second time, the list remains ordered. In no place the state variable skills_initial is changed unless in the constructor which I thought to be only called once.
Any further advice on my code structure / react habits is greatly appreciated.

Filter out existing objects in an array of objects

I don't think this is difficult, I just can't figure out the best way to do it. This function is creating an array, from a group of checkboxes. I then want to break up the array and create an array of objects, because each object can have corresponding data. How do I filter out existing rolesInterestedIn.roleType.
handleTypeOfWorkSelection(event) {
const newSelection = event.target.value;
let newSelectionArray;
if(this.state.typeOfWork.indexOf(newSelection) > -1) {
newSelectionArray = this.state.typeOfWork.filter(s => s !== newSelection)
} else {
newSelectionArray = [...this.state.typeOfWork, newSelection];
}
this.setState({ typeOfWork: newSelectionArray }, function() {
this.state.typeOfWork.map((type) => {
this.setState({
rolesInterestedIn: this.state.rolesInterestedIn.concat([
{
roleType: type,
}
])
}, function() {
console.log(this.state.rolesInterestedIn);
});
})
});
}
UDPATE
rolesInterestedIn: [
{
roleType: '',
experienceYears: ''
}
],
Because each time you do setState you are concatenating the new value to the prev one in rolesInterestedIn array. Add new value only when you are adding new item, otherwise remove the object from both the state variable typeOfWork and rolesInterestedIn.
Try this:
handleTypeOfWorkSelection(event) {
const newSelection = event.target.value;
let newSelectionArray, rolesInterestedIn = this.state.rolesInterestedIn.slice(0);
if(this.state.typeOfWork.indexOf(newSelection) > -1) {
newSelectionArray = this.state.typeOfWork.filter(s => s !== newSelection);
rolesInterestedIn = rolesInterestedIn.filter(s => s.roleType !== newSelection)
} else {
newSelectionArray = [...this.state.typeOfWork, newSelection];
rolesInterestedIn = newSelectionArray.map((workType) => {
return {
roleType: workType,
experienceYears: '',
}
});
}
this.setState({
typeOfWork: newSelectionArray,
rolesInterestedIn: rolesInterestedIn
});
}
Suggestion: Don't use multiple setState within a function, do all the calculation then use setState once to update all the values in the last.

Resources